#include <algorithm> // remove_if
#include <string.h> 
#include <stdlib.h>
#include <math.h> 
#include <ctype.h>
#include <errno.h>
#include <stddef.h>  /* apparently needed to define size_t */
#include <stdexcept>
#include "fitsio2.h"
//#include "group.h"

#include "service_string.hpp" // to_cftsio_format() needed$
#include "ast.hpp"
#include "fitsfiles.hpp"
#include "imcopy.hpp"
#include "io.hpp"

#include "fitsio.h"

using namespace std;



int stream_cutout(string pathname, int extnum, string region)
{
   vector<uint_bounds> bnds;

   int ov_code = vlkb_overlap(pathname, region, bnds);

   if((ov_code >= 2) && (ov_code <= 5))
   {
      string pixfilter{ to_cfitsio_format(bnds) };

      imcopy(pathname, extnum, pixfilter, "dummy");
      return EXIT_SUCCESS;

   }
   else if((ov_code == 1) || (ov_code == 6))
   {
      // no overlap
      return EXIT_SUCCESS;
   }
   else
   {
      throw runtime_error("overlap code invalid: " + to_string(ov_code));
   }
}



// ----------------------------------------------------------------------------------

int fits_copy_image_section2(
      fitsfile *fptr,  /* I - pointer to input image */
      fitsfile *newptr,  /* I - pointer to output image */
      char *expr,       /* I - Image section expression    */
      int *status);




void imcopy(std::string filename, int extnum, std::string pixfilter, std::string temp_root)
{
   LOG_trace(__func__);

   LOG_STREAM << filename << " EXT: " << extnum << endl;
   LOG_STREAM << "pixfilter: " << pixfilter << endl;
   LOG_STREAM << "temp_root: " << temp_root << 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 */

   fitsfile *fptr;    /* I - pointer to input image */
   fitsfile *newfptr; /* I - pointer to output image */

   const char * cfilename = filename.c_str();

   fits_open_diskfile(&fptr, cfilename, READONLY, &status);
   if (status)
   {
      string errmsg{ fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, status) };
      throw runtime_error("fits_open_file " + filename + " failed: " + errmsg);
   }

   /* cfitsio(v4.2.0) manual: 10.2.3 Notes about the stream filetype driver
    *  "-" writes to stdout: but first loads all file into mem
    *  "stream://" keeps NIOBUF=40 buffers, and header must fit into it */
   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);
   }

   /* err codes 1..1000 reserved by cfitsio */
   #define HEADER_TOO_LONG (10000)
   #define MAX_CARD_COUNT (1400)
   /* see notes about stream driver in cfitsio manual */
   fits_copy_image_section2(fptr, 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 " + filename
               + " 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 " + filename
               + " to cut-file with " + string{expr} + " failed: " + errmsg);
      }
   }

   fits_close_file(fptr, &status);
   if (status)
   {
      string errmsg{fitsfiles::cfitsio_errmsg(__FILE__, __LINE__, status)};

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

      throw runtime_error("fits_close_file " + filename + " 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);
   }
}


/* ---- version 3.49 ------- */
/* Currently this is a direct copy from cfitsio-lib.
 * Ensures read-by-rows on systems (CentOS7) with old cfitsio lib.
 * Also consider to duplicate the buffer and run reader
 * on parallel-thread to avoid reader-write wait for each other. */
int fits_copy_image_section2(
      fitsfile *fptr,  /* I - pointer to input image */
      fitsfile *newptr,  /* I - pointer to output image */
      char *expr,       /* I - Image section expression    */
      int *status)
{
   /* copies an image section from the input file to a new output HDU */

   int bitpix, naxis, numkeys, 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 */
   fits_get_img_type(fptr, &bitpix, status);
   fits_get_img_dim(fptr, &naxis, status);
   if (fits_get_img_size(fptr, 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 */
   fits_get_hdrspace(fptr, &numkeys, NULL, status);

   int cards_written = 4;
   for (nkey = 4; nkey <= numkeys; nkey++) /* skip the first few keywords */
   {
      fits_read_record(fptr, 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 */

   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);
      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';
            }

            tstatus = 0;
            if (fits_read_key(fptr, 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);

               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';
                  }

                  tstatus = 0;
                  if (fits_read_key(fptr, 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);

                  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);

                     tstatus = 0;
                     if (fits_read_key(fptr, 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 */
      }
   }  /* 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 */
   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;

   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];

            if (bitpix == 8)
            {
               ffgsvb(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
                     (unsigned char *) buffer, &anynull, status);

               ffpprb(newptr, 1, firstpix, outsize, (unsigned char *) buffer, status);
            }
            else if (bitpix == 16)
            {
               ffgsvi(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
                     (short *) buffer, &anynull, status);

               ffppri(newptr, 1, firstpix, outsize, (short *) buffer, status);
            }
            else if (bitpix == 32)
            {
               ffgsvk(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
                     (int *) buffer, &anynull, status);

               ffpprk(newptr, 1, firstpix, outsize, (int *) buffer, status);
            }
            else if (bitpix == -32)
            {
               ffgsve(fptr, 1, naxis, naxes, fpixels, lpixels, incs, FLOATNULLVALUE,
                     (float *) buffer, &anynull, status);

               ffppne(newptr, 1, firstpix, outsize, (float *) buffer, FLOATNULLVALUE, status);
            }
            else if (bitpix == -64)
            {
               ffgsvd(fptr, 1, naxis, naxes, fpixels, lpixels, incs, DOUBLENULLVALUE,
                     buffer, &anynull, status);

               ffppnd(newptr, 1, firstpix, outsize, buffer, DOUBLENULLVALUE,
                     status);
            }
            else if (bitpix == 64)
            {
               ffgsvjj(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
                     (LONGLONG *) buffer, &anynull, status);

               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);
}

