
#include "fitsfiles.hpp"
#include "cutout.hpp" // struct fits_card needed

#include "io.hpp"

#include "fits_header.hpp"

#include <cstring>
#include <string>
#include <vector>
#include <stdexcept>

// C libs
#include <stdlib.h>// malloc
#include <glob.h>
#include <fitsio.h>
#include <string.h> // strerror(errno) needed

#include <libgen.h> // basename() needed
#include <sys/stat.h> // stat - filesize needed

using namespace std;




uintmax_t fitsfiles::fileSize(string pathname)
{
   struct stat st; 
   int rc = stat(pathname.c_str(), &st);

   if(rc != 0)
      throw runtime_error(string{__FILE__} + ":" + to_string(__LINE__) + ": " + string{strerror(rc)});

   off_t size = st.st_size;

   return size;
}



double fitsfiles::calc_nullvals(string pathname, unsigned int hdunum,
      unsigned long long & null_cnt, unsigned long long & total_cnt)
{
   LOG_trace(__func__);

   fits::header hdr(pathname, hdunum, READONLY);

   return hdr.calc_nullvals(null_cnt, total_cnt);
}



// FIXME check glob ret value empty & check dir not exist or no access etc...
// (cases when command exe'd on different machine)
// list only files (distinguish files vs dirs)
// hidden files not listed
// no dir recursion
vector<string> fitsfiles::globVector(const string& pattern)
{
   glob_t glob_result;
   glob(pattern.c_str(),GLOB_TILDE,NULL,&glob_result);
   vector<string> files;
   for(unsigned int i=0;i<glob_result.gl_pathc;++i){
      files.push_back(string(glob_result.gl_pathv[i]));
   }
   globfree(&glob_result);
   return files;
}



// class header based



string fitsfiles::cfitsio_errmsg(const char * filename, int line_num, int status)
{
   char errmsg[32]; // cfitsio doc says errmsg is "30 chars max"
   errmsg[0] = 0;
   fits_get_errstatus(status, errmsg);
   string msg{"ERR["+to_string(status)+"] " + string{filename} + "." + to_string(line_num) + ":" + string{errmsg}};

   return msg;
}


string fitsfiles::read_header(string pathname, unsigned int hdunum)
{
   LOG_trace(__func__);

   const bool apply_fixes = true;

   fits::header hdr(pathname, hdunum, READONLY);
   return hdr.get_header(apply_fixes);
}


string fitsfiles::read_card(const std::string pathname, unsigned int hdunum, const string keyname)
{
   fits::header hdr(pathname, hdunum);
   string card{hdr.read_card(keyname)};

   return card;
}


map<string, string> fitsfiles::read_header_wcs_coord_type(const std::string pathname, unsigned int hdunum, char altwcs)
{
   fits::header hdr(pathname, hdunum);

   string alt{ ( altwcs == ' ' ? string{""} : string{altwcs} ) };

   // FIXME does not support alternative calibration (one char at the end of keyname)

   set<string> keys{"SPECSYS" + alt,"SSYSOBS" + alt};

   string keyroot{"CTYPE"};
   int i=1;
   string istr{to_string(i)};
   while( hdr.contains_card( keyroot + istr + alt) )
   {
      keys.insert("CTYPE" + istr + alt);
      keys.insert("CUNIT" + istr + alt);
      istr = to_string(i++);
   }

   return hdr.parse_string_cards(keys);
}



void fitsfiles::add_cards_if_missing(const std::string pathname, unsigned int hdunum, const std::vector<struct fits_card> cards)
{
   fits::header hdr(pathname, hdunum, READWRITE);
   hdr.update(cards);
}


void fitsfiles::fits_hdu_cut(const string infile, const unsigned int hdunum,
      const string outfile)
{
   LOG_trace(__func__);

   LOG_STREAM << "input infile : " << infile << " hdunum: "<< to_string(hdunum) <<endl;
   LOG_STREAM << "input outfile: " << outfile << endl;

   int status = 0;

   fitsfile *infptr, *outfptr; 

   // cut

   if ( !fits_open_file(&infptr, infile.c_str(), READONLY, &status) )
   {
      if ( !fits_create_file(&outfptr, outfile.c_str(), &status) )
      {
         if( !fits_movabs_hdu(infptr, hdunum, NULL, &status) )
         {
            int morekeys = 0; // reserve space for more keys in destination
            if(!fits_copy_hdu(infptr, outfptr, morekeys, &status))
            {
               LOG_STREAM << "fits_copy_hdu  status: " << status << endl;
            }
         }
         else
         {
            LOG_STREAM << cfitsio_errmsg(__FILE__,__LINE__,status) << endl;
         }
         // Reset status after normal error
         if (status == END_OF_FILE) status = 0;

         fits_close_file(outfptr,  &status);
      }
      else
      {
         string errmsg{fitsfiles::cfitsio_errmsg(__FILE__,__LINE__,status)};
         fits_close_file(infptr, &status);
         throw runtime_error(errmsg + " " + outfile);
      }
      fits_close_file(infptr, &status);
   }
   else
   {
      throw runtime_error(fitsfiles::cfitsio_errmsg(__FILE__,__LINE__,status) + " " + infile);
   }

   if(status) 
   {
      throw runtime_error(fitsfiles::cfitsio_errmsg(__FILE__,__LINE__,status) + " " + infile);
   }
}


// adds cards or modyfies card value - replaces run-time fix.c for this in ingestion-timw
int fitsfiles::mod_value(string filename, string token, string keyvalue)
{
   LOG_trace(__func__);

   fitsfile *fptr;         /* FITS file pointer, defined in fitsio.h */
   char card[FLEN_CARD], newcard[FLEN_CARD];
   char oldvalue[FLEN_VALUE], comment[FLEN_COMMENT];
   int status = 0;   /*  CFITSIO status value MUST be initialized to zero!  */
   int iomode, keytype;

   bool do_update = !keyvalue.empty();

   if (do_update)
      iomode = READWRITE;
   else
      iomode = READONLY;

   if (!fits_open_file(&fptr, filename.c_str(), iomode, &status))
   {
      //if (fits_read_card(fptr, keyname.c_str(), card, &status))
      if (fits_read_str(fptr,token.c_str(), card, &status))
      {
         printf("Keyword does not exist\n");
         card[0] = '\0';
         comment[0] = '\0';
         status = 0;  /* reset status after error */
         strcpy(card,token.c_str());
         //goto f_end;
      }
      else
      {
         printf("%s\n",card);
      }

      if (do_update)  /* write or overwrite the keyword */
      {
         char keyname[80];
         int keylength;
         fits_get_keyname(card, keyname, &keylength, &status);

         /* check if this is a protected keyword that must not be changed */
         if (*card && fits_get_keyclass(card) == TYP_STRUC_KEY)
         {
            printf("Protected keyword cannot be modified.\n");
         }
         else
         {
            /* get the comment string */
            if (*card)fits_parse_value(card, oldvalue, comment, &status);

            /* construct template for new keyword */
            strcpy(newcard, keyname);     /* copy keyword name */
            strcat(newcard, " = ");       /* '=' value delimiter */
            strcat(newcard, keyvalue.c_str());     /* new value */
            if (*comment) {
               strcat(newcard, " / ");  /* comment delimiter */
               strcat(newcard, comment);     /* append the comment */
            }

            /* reformat the keyword string to conform to FITS rules */
            fits_parse_template(newcard, card, &keytype, &status);

            /* overwrite the keyword with the new value */
            fits_update_card(fptr, keyname, card, &status);

            printf("Keyword has been changed to:\n");
            printf("%s\n",card);
         }
      }
f_end:
      fits_close_file(fptr, &status);
   }

   /* if error occured, print out error message */
   if (status) fits_report_error(stderr, status);

   return(status);
}

/* FIXME there is also in fits_header - keep only one */
string keytype_to_string2(int keytype)
{
   string keytype_text;

   // ref: cfitsio manual, fits_parse_template() func description
   switch(keytype)
   {
      case -2: keytype_text = "rename keyword"; break;
      case -1: keytype_text = "delete keyword"; break;
      case  0: keytype_text = "append/update key-record"; break;
      case  1: keytype_text = "append/update HISTORY or COMMENT key-record"; break;
      case  2: keytype_text = "END record (not written explicitely)"; break;
      default: throw runtime_error(string{__FILE__} + ":" + to_string(__LINE__)
                     + ": unknown keytype returned from fits_parse_template");
   }

   return keytype_text;
}

bool is_key_in_header(const string header, const string key8)
{
   string::size_type ccount;
   for(ccount = 0; ccount < header.length(); ccount += 80)//FLEN_CARD)
   {
      string curr_key = header.substr(ccount, 8);
      //LOG_STREAM << key8 << " " <<curr_key << endl;
      if(curr_key.compare(key8) == 0) return true;
   }
   return false;
}

/* header string must not contain closing END-card */
string fitsfiles::append_card_if_not_in_header(string header, const vector<fits_card> additional_cards)
{
   LOG_trace(__func__);

   string new_header_str{header};

   LOG_STREAM << "header length: " << strlen(new_header_str.c_str()) << endl;

   for(fits_card new_card : additional_cards)
   {
      int status = 0;
      int keytype;
      const int BUFF_SIZE{ 1024 };
      char buff[BUFF_SIZE];
      char card[FLEN_CARD];
      card[0] = 0;

      const string newcard_template{ new_card.key + " = " + new_card.value + " / " + new_card.comment };
      my_assert(newcard_template.length() < BUFF_SIZE, __FILE__, __LINE__, "formatted card does not fit into buffer");
      strcpy(buff, newcard_template.c_str());

      if(!fits_parse_template(buff, card, &keytype, &status))
      {
         string card_key{ string{card}.substr(0,8) };
         if( is_key_in_header(header, card_key) )
         {
            LOG_STREAM << __func__ << " found card[" << card_key  << "]: " << card << endl;
         }
         else
         {
            LOG_STREAM << __func__ << " appending card[" << keytype  << "] >" << card
               << "< " << keytype_to_string2(keytype) << endl;

            new_header_str += string{ card };
         }
      }
      else
      {
         throw runtime_error(cfitsio_errmsg(__FILE__,__LINE__,status) + " key: " + new_card.key);
      }

   }

   LOG_STREAM << "header length: " << strlen(new_header_str.c_str()) << endl;

   return new_header_str;
}



// deprecated

vector<fitsfiles::Hdu> fitsfiles::fname2hdrstr(
      string filename, unsigned int maxHduPos,
      const keys_by_type* keys)
{
   LOG_trace(__func__);
   LOG_STREAM << filename << endl;

   vector<fitsfiles::Hdu> vec_hdrs;

   // FIXME enhance fits::header to avoid open the file at each cycle
   unsigned int hdupos;
   for(hdupos = 1; hdupos <= maxHduPos; hdupos++)
   {
      fits::header hdr(filename, hdupos);
      // FIXME catch run-time except if not IMAGE_HDU -> how to deal with this?
      // and if hdupos > last_hdu ? ->
      // implement read_all_hdus without prior knowledge how many HDU are in FITSfile:
      // read to file-end but ignore (only) FILE-end error

      const bool apply_fixes = true;
      string header_str{hdr.get_header(apply_fixes)};

      key_values_by_type key_values;

      if(keys != nullptr)
      {
         key_values = hdr.parse_cards(*keys);
      }

      vec_hdrs.push_back( fitsfiles::Hdu{hdupos, header_str, key_values} );
   }

   return vec_hdrs;
}

