
#include <davix/davix.hpp>
#include <fitsio.h>
#include <cstring>
#include <iomanip>

#include "fitsfiles.hpp"
#include "io.hpp"

using namespace Davix;
using namespace std;

class dav
{
   public:

      dav(string uri_str):file(this->c,Uri(uri_str))
   {
      const char END_CARD[]
         ="END                                                                             ";

      this->file.readPartial(NULL, this->buffer, dav::NCARDS*80, 0, &err);
      // FIXME what if all file is shorter then 1400 cards -> read by 2880 until END-card?
      for(int i=0; i<NCARDS; i++)
      {
         char card[80+1];
         card[80]=0;
         strncpy(card, &(buffer[i*80]), 80);
         if(0 == string{card}.compare(string{END_CARD}))
         {
            endcard_cix = i; 
            break;
         }
      }

      long n_blocks    = (endcard_cix+1) / 36;
      long n_cardslots = (endcard_cix+1) % 36;

      long n_blocks_with_padding = (n_cardslots==0) ? n_blocks : (n_blocks+1);

      this->du_firstbyte_cix = (2880)*n_blocks_with_padding;
   }


      int fits_get_img_type(int *bitpix, int *status)
      {
         string value{get_card_value(1, status)};
         if(! (*status))
         {
            *bitpix = stoi(value);;
         }
         return *status;
      }


      int fits_get_img_dim(int *naxis, int *status)
      {
         string value{get_card_value(2, status)};
         if(! (*status))
         {
            *naxis = stoi(value);;
         }
         return *status;
      }


      // there is also LONLONG *naxes version: fits_et_img_sizell
      int fits_get_img_size(int maxdim, long *naxes, int *status)
      {
         for(int i=0; i<maxdim; i++)
         {
            string value{get_card_value(3+i, status)};
            if(! (*status))
            {
               naxes[i] = stol(value);;
            }
         }

         int naxis = maxdim;
         // prepare volume[] for coord -> offset calcs
         for(int i=1; i<naxis; i++) this->volume[i] = this->volume[i-1] * naxes[i-1];
         return *status;
      }


// Manual 5.4.1 Keyword Reading Routines
// 'Return the number of existing keywords (not counting the END keyword) and the amount of
// space currently available for more keywords. It returns morekeys = -1 if the header has not
// yet been closed.'
      int fits_get_hdrspace(int *numkeys, int *morekeys, int *status)
      {
         *numkeys = endcard_cix;

         long rem = (endcard_cix+1) % 36;
         *morekeys = (rem==0) ? 0 : (36-rem);
         return *status;
      }


      // keynum=1..numkeys
      int fits_read_record(int keynum, char *card, int *status)
      {
         strncpy(card, &(buffer[(keynum-1)*80]), 80);
         card[80]=0;

         return *status;
      }


      int fits_read_key(int datatype, char *keyname, void *value, char *comment,int *status)
      {
         char lkeyname[8+1];
         char lvalue[16+1];
         int keylength;

         for(int i=0; i<(endcard_cix+1); i++)
         {
            char card[80+1]; card[80]=0;
            fits_read_record(i+1, card, status);
            fits_get_keyname(card, lkeyname, &keylength, status);
            lkeyname[keylength] = 0;

            if(0 == string(lkeyname).compare(string{keyname}))
            {
               fits_parse_value(card, lvalue, comment, status);
               if((lvalue!=NULL) && (*status==0))
               {
                  switch(datatype)
                  {
                     case TDOUBLE: *((double*)value) = stod(lvalue); break;
                     default:
                        break;
                  }
               }
            }
         }

         return *status;
      }

// cfitsio manual[9.5 Specialized FITS Primary Array or IMAGE Extension I/O Routines]:
// 'The fpixel and lpixel parameters are integer arrays which specify the starting
// and ending pixel coordinate in each dimension (starting with 1, not 0) of the FITS image'
      int ffgsv(int bitpix,
            int naxis, long *naxes,
            long *fpixel, /*long *lpixel, long *inc,*/ long arraylen,
            char nulval, char *array, int *anynul, int *status )
      {
         //assert( fpixel[0] <= lpixel[0] );

         dav_off_t pixels_off = 0;
         for(int i=(naxis-1); i>=0; i--)
         {
            pixels_off += (fpixel[i]-1) * volume[i];
         }

         dav_off_t doff = du_firstbyte_cix + (abs(bitpix)/8) * pixels_off;

         this->file.readPartial(NULL, array, arraylen, doff, &err);

         return *status;
      }
      /*
         dav_ssize_t readPartial(const RequestParams* params,
         void* buff,
         dav_size_t count,
         dav_off_t offset,
         DavixError** err) throw();
       */

   private:

      string get_card_value(int card_ix, int *status)
      {
         char card[80+1]; card[80]=0;
         char value[16+1];

         fits_read_record(card_ix+1, card, status);
         fits_parse_value(card, value, NULL, status);
         if((value!=NULL) && (*status==0))
         {
            return string{value};
         }
         else
         {
            return string();
         }
      }
/*
      string get_card_key(int card_ix, int *status)
      {
         char card[80+1]; card[80]=0;
         char keyname[8+1];
         int keylength;

         fits_read_record(card_ix+1, card, status);
         fits_get_keyname(card, keyname, &keylength, status);
         keyname[keylength] = 0;
         if((keyname!=NULL) && (*status==0))
         {
            return string{keyname};
         }
         else
         {
            return string();
         }
      }
*/


      DavixError* err=NULL;
      Context c;
      DavFile file;

      static const int NCARDS=1400;
      char buffer[NCARDS*80];
      long endcard_cix;
      long du_firstbyte_cix;

      dav_off_t volume[9] = {1,1,1,1,1,1,1,1,1};
};

/////////////////////////////////////////////////////////////////////////////
#define HEADER_TOO_LONG (10000)
#define MAX_CARD_COUNT (1400)

/*
namespace fitsfiles {
string 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;
}
}

#define LOG_STREAM cerr
#define LOG_trace(a) {LOG_STREAM << a << endl;}
*/
void endianess(int bitpix, long buffsize, char* buffer)
{
   const int bytepix = abs(bitpix)/8;
   char pixbuffer[bytepix];

   for(int ii=0; ii<buffsize; ii+=bytepix)
   {
      for(int kk=0; kk<bytepix; kk++)
      {
        pixbuffer[kk] = buffer[ii+kk]; 
      }
      for(int kk=0; kk<bytepix; kk++)
      {
        buffer[ii+bytepix-1-kk] = pixbuffer[kk]; 
      }
   }
}

int fits_copy_image_section3(
      dav fdav,  /* I - dav handle to FITS-file */
      fitsfile *newptr,  /* I - pointer to output image */
      char *expr,       /* I - Image section expression    */
      int *status)
{
   int bitpix, naxis, numkeys, morekeys, nkey;
   long naxes[] = {1,1,1,1,1,1,1,1,1}, smin, smax, sinc;
   long fpixels[] = {1,1,1,1,1,1,1,1,1};
   long lpixels[] = {1,1,1,1,1,1,1,1,1};
   long incs[] = {1,1,1,1,1,1,1,1,1};
   char *cptr, keyname[FLEN_KEYWORD], card[FLEN_CARD];
   int ii, tstatus, anynull;
   long minrow, maxrow, minslice, maxslice, mincube, maxcube;
   long firstpix;
   long ncubeiter, nsliceiter, nrowiter, kiter, jiter, iiter;
   int klen, kk, jj;
   long outnaxes[9], outsize, buffsize;
   double *buffer, crpix, cdelt;

   if (*status > 0)
      return(*status);

   /* get the size of the input image */
   fdav.fits_get_img_type(&bitpix, status);
   fdav.fits_get_img_dim(&naxis, status);
   if (fdav.fits_get_img_size(naxis, naxes, status) > 0)
      return(*status);

   if (naxis < 1 || naxis > 4)
   {
      ffpmsg(
            "Input image either had NAXIS = 0 (NULL image) or has > 4 dimensions");
      return(*status = BAD_NAXIS);
   }

   /* create output image with same size and type as the input image */
   /*  Will update the size later */
   fits_create_img(newptr, bitpix, naxis, naxes, status);

   /* copy all other non-structural keywords from the input to output file */
   fdav.fits_get_hdrspace(&numkeys, &morekeys, status);

   int cards_written = 4;
   for (nkey = 4; nkey <= numkeys; nkey++) /* skip the first few keywords */
   {
      fdav.fits_read_record(nkey, card, status);

      /* cfitsio(v4.2.0) manual: 10.2.3 Notes about the stream filetype driver
       * stream:// driver has internal buffer NIOBUF=40 blocks e.g. 1400 cards
       * TYP_COMM_KEY: skip HISTORY COMMENT and empty keys */
      if(numkeys > MAX_CARD_COUNT)
         if (fits_get_keyclass(card) == TYP_COMM_KEY) continue;

      if (fits_get_keyclass(card) > TYP_CMPRS_KEY)
      {
         if(++cards_written > MAX_CARD_COUNT) return(HEADER_TOO_LONG);

         /* write the record to the output file */
         fits_write_record(newptr, card, status);
      }
   }

   if (*status > 0)
   {
      ffpmsg("error copying header from input image to output image");
      return(*status);
   }

   /* parse the section specifier to get min, max, and inc for each axis */
   /* and the size of each output image axis */

   LOG_STREAM << "expr: " << string{expr} << endl;
   LOG_STREAM << "naxis: " << to_string(naxis) << endl;

   cptr = expr;
   for (ii=0; ii < naxis; ii++)
   {
      if (fits_get_section_range(&cptr, &smin, &smax, &sinc, status) > 0)
      {
         ffpmsg("error parsing the following image section specifier:");
         ffpmsg(expr);
         return(*status);
      }

      if (smax == 0)
         smax = naxes[ii];   /* use whole axis  by default */
      else if (smin == 0)
         smin = naxes[ii];   /* use inverted whole axis */

      if (smin > naxes[ii] || smax > naxes[ii])
      {
         ffpmsg("image section exceeds dimensions of input image:");
         ffpmsg(expr);
         return(*status = BAD_NAXIS);
      }

      fpixels[ii] = smin;
      lpixels[ii] = smax;
      incs[ii] = sinc;

      if (smin <= smax)
         outnaxes[ii] = (smax - smin + sinc) / sinc;
      else
         outnaxes[ii] = (smin - smax + sinc) / sinc;

      /* modify the NAXISn keyword */
      fits_make_keyn("NAXIS", ii + 1, keyname, status);
      LOG_STREAM << "modifkey: " << keyname << " " << outnaxes[ii] << " / " << *status <<endl;
      fits_modify_key_lng(newptr, keyname, outnaxes[ii], NULL, status);

      /* modify the WCS keywords if necessary */

      if (fpixels[ii] != 1 || incs[ii] != 1)
      {
         for (kk=-1;kk<26; kk++)  /* modify any alternate WCS keywords */
         {
            /* read the CRPIXn keyword if it exists in the input file */
            fits_make_keyn("CRPIX", ii + 1, keyname, status);

            if (kk != -1) {
               klen = (int)strlen(keyname);
               keyname[klen]= (char)((int)'A' + kk);
               keyname[klen + 1] = '\0';
            }

            LOG_STREAM << "read key: " << string{keyname} << " / " << *status << endl;

            tstatus = 0;
            if (fdav.fits_read_key(TDOUBLE, keyname,
                     &crpix, NULL, &tstatus) == 0)
            {

               /* calculate the new CRPIXn value */
               if (fpixels[ii] <= lpixels[ii]) {
                  crpix = (crpix - double(fpixels[ii])) / double(incs[ii]) + 1.0;
                  /*  crpix = (crpix - (fpixels[ii] - 1.0) - .5) / incs[ii] + 0.5; */
               } else {
                  crpix = (double(fpixels[ii]) - crpix)  / double(incs[ii]) + 1.0;
                  /* crpix = (fpixels[ii] - (crpix - 1.0) - .5) / incs[ii] + 0.5; */
               }

               /* modify the value in the output file */
               fits_modify_key_dbl(newptr, keyname, crpix, 15, NULL, status);

               LOG_STREAM << "modifkey: " << string{keyname} << " / " << *status << endl;

               if (incs[ii] != 1 || fpixels[ii] > lpixels[ii])
               {
                  /* read the CDELTn keyword if it exists in the input file */
                  fits_make_keyn("CDELT", ii + 1, keyname, status);

                  if (kk != -1) {
                     klen = (int)strlen(keyname);
                     keyname[klen]=(char)((int)'A' + kk);
                     keyname[klen + 1] = '\0';
                  }

                  LOG_STREAM << "modifkey: " << string{keyname} << " / " << *status << endl;

                  tstatus = 0;
                  if (fdav.fits_read_key(TDOUBLE, keyname,
                           &cdelt, NULL, &tstatus) == 0)
                  {
                     /* calculate the new CDELTn value */
                     if (fpixels[ii] <= lpixels[ii])
                        cdelt = cdelt * double(incs[ii]);
                     else
                        cdelt = cdelt * double(-incs[ii]);

                     /* modify the value in the output file */
                     fits_modify_key_dbl(newptr, keyname, cdelt, 15, NULL, status);
                  }

                  /* modify the CDi_j keywords if they exist in the input file */

                  fits_make_keyn("CD1_", ii + 1, keyname, status);

                  LOG_STREAM << "modifkey: " << string{keyname} << endl;

                  if (kk != -1) {
                     klen = (int)strlen(keyname);
                     keyname[klen]=(char)((int)'A' + kk);
                     keyname[klen + 1] = '\0';
                  }

                  for (jj=0; jj < 9; jj++)   /* look for up to 9 dimensions */
                  {
                     keyname[2] = (char)((int)'1' + jj);

                     LOG_STREAM << "modifkey: " << string{keyname} <<" / " << *status << endl;
                     tstatus = 0;
                     if (fdav.fits_read_key(TDOUBLE, keyname,
                              &cdelt, NULL, &tstatus) == 0)
                     {
                        /* calculate the new CDi_j value */
                        if (fpixels[ii] <= lpixels[ii])
                           cdelt = cdelt * double(incs[ii]);
                        else
                           cdelt = cdelt * double(-incs[ii]);

                        /* modify the value in the output file */
                        fits_modify_key_dbl(newptr, keyname, cdelt, 15, NULL, status);
                     }
                  }

               } /* end of if (incs[ii]... loop */
            }   /* end of fits_read_key loop */
         }    /* end of for (kk  loop */
      }
      *status=0; // FIXME I had to add this to force continue because it exited for CRPIXiA 
   }  /* end of main NAXIS loop */

   if (ffrdef(newptr, status) > 0)  /* force the header to be scanned */
   {
      return(*status);
   }

   /* turn off any scaling of the pixel values */
   // NOTE not needed: we just copy data unchanged fits_set_bscale(fptr,  1.0, 0.0, status);
   fits_set_bscale(newptr, 1.0, 0.0, status);

   /* to reduce memory foot print, just read/write image 1 row at a time */

   outsize = outnaxes[0];
   buffsize = (abs(bitpix) / 8) * outsize;

   LOG_STREAM << "outsize / buffsize : " << outsize << " / " << buffsize << endl;

   buffer = (double *) malloc(buffsize); /* allocate memory for the image row */
   if (!buffer)
   {
      ffpmsg("fits_copy_image_section: no memory for image section");
      return(*status = MEMORY_ALLOCATION);
   }
   /* read the image section then write it to the output file */

   minrow = fpixels[1];
   maxrow = lpixels[1];
   if (minrow > maxrow) {
      nrowiter = (minrow - maxrow + incs[1]) / incs[1];
   } else {
      nrowiter = (maxrow - minrow + incs[1]) / incs[1];
   }

   minslice = fpixels[2];
   maxslice = lpixels[2];
   if (minslice > maxslice) {
      nsliceiter = (minslice - maxslice + incs[2]) / incs[2];
   } else {
      nsliceiter = (maxslice - minslice + incs[2]) / incs[2];
   }

   mincube = fpixels[3];
   maxcube = lpixels[3];
   if (mincube > maxcube) {
      ncubeiter = (mincube - maxcube + incs[3]) / incs[3];
   } else {
      ncubeiter = (maxcube - mincube + incs[3]) / incs[3];
   }

   firstpix = 1;
   for (kiter = 0; kiter < ncubeiter; kiter++)
   {
      if (mincube > maxcube) {
         fpixels[3] = mincube - (kiter * incs[3]);
      } else {
         fpixels[3] = mincube + (kiter * incs[3]);
      }

      lpixels[3] = fpixels[3];

      for (jiter = 0; jiter < nsliceiter; jiter++)
      {
         if (minslice > maxslice) {
            fpixels[2] = minslice - (jiter * incs[2]);
         } else {
            fpixels[2] = minslice + (jiter * incs[2]);
         }

         lpixels[2] = fpixels[2];

         for (iiter = 0; iiter < nrowiter; iiter++)
         {
            if (minrow > maxrow) {
               fpixels[1] = minrow - (iiter * incs[1]);
            } else {
               fpixels[1] = minrow + (iiter * incs[1]);
            }

            lpixels[1] = fpixels[1];

            //
            fdav.ffgsv(bitpix, naxis, naxes,
                  fpixels, buffsize, 0, (char*)buffer, NULL, status);

            endianess(bitpix, buffsize, (char*)buffer);

            if (bitpix == 8)
            {
               ffpprb(newptr, 1, firstpix, outsize, (unsigned char *) buffer, status);
            }
            else if (bitpix == 16)
            {
               ffppri(newptr, 1, firstpix, outsize, (short *) buffer, status);
            }
            else if (bitpix == 32)
            {
               ffpprk(newptr, 1, firstpix, outsize, (int *) buffer, status);
            }
            else if (bitpix == -32)
            {
               ffppne(newptr, 1, firstpix, outsize, (float *) buffer, FLOATNULLVALUE, status);
            }
            else if (bitpix == -64)
            {
               ffppnd(newptr, 1, firstpix, outsize, (double *) buffer, DOUBLENULLVALUE,
                     status);
            }
            else if (bitpix == 64)
            {
               ffpprjj(newptr, 1, firstpix, outsize, (LONGLONG *) buffer, status);
            }

            firstpix += outsize;
         }
      }
   }

   free(buffer);  /* finished with the memory */

   if (*status > 0)
   {
      ffpmsg("fits_copy_image_section: error copying image section");
      return(*status);
   }

   return *status;
}

void imcopydav(std::string url, int extnum, std::string pixfilter)
{
   LOG_trace(__func__);
   LOG_STREAM << url << " EXT: " << extnum << endl;
   LOG_STREAM << "pixfilter: " << pixfilter << endl;

   int status = 0;

   pixfilter.erase( remove(pixfilter.begin(), pixfilter.end(), '['), pixfilter.end() );
   pixfilter.erase( remove(pixfilter.begin(), pixfilter.end(), ']'), pixfilter.end() );

   LOG_STREAM << "filter expr: " << pixfilter << endl;

   char *expr = (char*)pixfilter.c_str(); /* I - Image section expression */

   dav fdav(url);     /* I - input image */
   fitsfile *newfptr; /* I - pointer to output image */

   //fits_create_file(&newfptr, "cut.fits", &status);
   fits_create_file(&newfptr, "stream://", &status);
   if (status)
   {
      string errmsg{ fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, status) };

      int tstatus = 0;
      //fits_close_file(fptr, &tstatus);
      if(tstatus) LOG_STREAM << fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, tstatus) << tstatus<< endl;

      throw runtime_error("fits_open_file to stream failed: " + errmsg);
   }


   fits_copy_image_section3(fdav, newfptr, expr, &status);
   if (status)
   {
      int tstatus = 0;
      //fits_close_file(fptr, &tstatus);
      //if(tstatus) LOG_STREAM << fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, tstatus) << tstatus<< endl;
      tstatus = 0;
      fits_close_file(newfptr, &tstatus);
      if(tstatus) LOG_STREAM << fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, tstatus) << tstatus<< endl;

      if(status == HEADER_TOO_LONG)
      {
         throw runtime_error("Cutout from " + url
               + " failed: header is too long. Direct streaming in current build support max "
               + to_string(MAX_CARD_COUNT) + " cards in header.");
      }
      else
      {
         string errmsg{fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, status)};
         throw runtime_error("fits_copy_image_section " + url
               + " to cut-file with " + string{expr} + " failed: " + errmsg);
      }
   }


   fits_close_file(newfptr, &status);
   if (status)
   {
      string errmsg{fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, status)};
      throw runtime_error("fits_close_file cut failed: " + errmsg);
   }
}

