
#include "removecard.hpp"
#include "mergefiles.hpp"
#include "dropdegen.hpp"
#include "multicutout.hpp"
#include "ast.hpp"
#include "imcopy.hpp" // imcopy() vlkb_cutout()
#include "imcopydav.hpp" // imcopydav()
#include "ast4vl.hpp" // uint_bounds needed
#include "fitsfiles.hpp"
#include "service_string.hpp" // to_cftsio_format() needed

//#include "config.hpp"
#include "io.hpp"

#include <algorithm> // replace needed
#include <iostream>
#include <sstream>

#include <assert.h>
#include <libgen.h> // basename()


//std::ostream& OUT_STREAM = std::cout;
//std::ostream& ERROR_STREAM = std::cerr;


using namespace std;

namespace vlkb
{
   //config conf;
   const string fits_path=".";
   const string fits_cut_path=".";


   //--------------------------------------------
   // util funcs
   //--------------------------------------------

   void usage(const std::string progname)
   {
      std::cerr
         << "Usage: " << progname << " <command> [cmd-options] [cmd-args]" << endl
         << "\n where commands are:\n "
         << "\n\t cutout imcopy imcopydav cutpixels multicutout mergefiles\n"
         << "\n\t listbounds header headermodif headerwcs skyvertices overlap\n"
         << "\n\t nullvals dropdegen checkcard addcard modcard rawdelcard\n"
         << std::endl
         << "Version: " << VERSIONSTR << " " << BUILD << std::endl;
   }



   // program recognizes following commands

   enum cmd_set {
      multicutout, mergefiles, cutout, imcopy, imcopydav, cutpixels,
      listbounds, header, headermodif, headerwcs, overlap, skyvertices,
      nullvals, dropdegen, checkcard, addcard, modcard, rawdelcard};


   // from bash or interpreters usually receive params as strings

   cmd_set to_cmd(const std::string cmdstr)
   {
      cmd_set cmd;

      if(cmdstr.compare("multicutout") == 0)       cmd = multicutout;
      else if(cmdstr.compare("mergefiles") == 0)   cmd = mergefiles;
      else if(cmdstr.compare("cutout") == 0)       cmd = cutout;
      else if(cmdstr.compare("imcopy") == 0)       cmd = imcopy;
      else if(cmdstr.compare("imcopydav") == 0)    cmd = imcopydav;
      else if(cmdstr.compare("cutpixels") == 0)    cmd = cutpixels;
      else if(cmdstr.compare("listbounds") == 0)   cmd = listbounds;
      else if(cmdstr.compare("header") == 0)       cmd = header;
      else if(cmdstr.compare("headermodif") == 0)  cmd = headermodif;
      else if(cmdstr.compare("headerwcs") == 0)    cmd = headerwcs;
      else if(cmdstr.compare("overlap") == 0)      cmd = overlap;
      else if(cmdstr.compare("skyvertices") == 0)  cmd = skyvertices;
      else if(cmdstr.compare("nullvals") == 0)     cmd = nullvals;
      else if(cmdstr.compare("dropdegen") == 0)    cmd = dropdegen;
      else if(cmdstr.compare("checkcard") == 0)    cmd = checkcard;
      else if(cmdstr.compare("addcard") == 0)      cmd = addcard;
      else if(cmdstr.compare("modcard") == 0)      cmd = modcard;
      else if(cmdstr.compare("rawdelcard") == 0)   cmd = rawdelcard;
      else
      {
         stringstream cmd_err;
         cmd_err << "String \'" << cmdstr<< "\' not recognized as valid command." << endl;
         throw(invalid_argument(cmd_err.str()));
      }

      return cmd;
   }

} // namespce



//-----------------------------------------------------------
// all commands handle [argc,argv] and return a code:
// 0 - Ok: command produces result as described in 'usage'
// non-zero: Warning, Error: response has other form that 
// the one described in 'usage'
//-----------------------------------------------------------


int cmd_multicutout(int argc, char * argv[])
{
   int rc;

   switch(argc)
   {
      case 2:
         {
            string json_request_filename(argv[1]);
            std::string tgzfilename = multicutout(json_request_filename, vlkb::fits_path, vlkb::fits_cut_path);//vlkb::conf);
            cout << tgzfilename << endl;
            rc = EXIT_SUCCESS;
         }
         break;

      default:
         cerr << "Usage: multicutout <filename_with_json_request>" << endl;
         rc = EXIT_FAILURE;
   }

   return rc;
}



int cmd_nullvals(int argc, char * argv[])
{
   int rc;

   switch(argc)
   {
      case 2:
         {
            unsigned long long null_cnt;
            unsigned long long total_cnt;
            double fill_ratio = fitsfiles::calc_nullvals(argv[1], /*hdunum*/1, null_cnt, total_cnt);
            //  fill_ratio_[%]   null_cnt   total_cnt 
            cout << fill_ratio << " " <<  null_cnt << " " << total_cnt << endl;
            rc = EXIT_SUCCESS;
         }
         break;

      default:
         std::cerr
            << "Usage: nullvals <filename.fits>\n"
            << std::endl
            <<"Calculates ratio of undefined pixels in image; returns three values:\n"
            <<"<percentage-of-null-values> <count-of-nulls> <count-of-allpixels>\n"
            << std::endl;
         rc = EXIT_FAILURE;
   }

   return rc;
}



int cmd_mergefiles(int argc, char * argv[])
{
   if(argc < 2)
   {
      std::cerr
         << "Usage:  mergefiles [filename.fits ...]\n"
         << "\n"
         << "Merge FITS-files supplied as argument.\n"
         << "Examples: \n"
         << "   vlkb mergefiles /rootvialactea/CHaMP/region6.fits /rootvialactea/CHaMP/region7.fits\n";
      return EXIT_FAILURE;
   }
   else
   {
      const vector<string> files_to_merge(argv + 1, argv + argc);
      for(string file : files_to_merge) cout << file << endl;
      string merged_file = mergefiles(files_to_merge);
      return EXIT_SUCCESS;
   }
}




int cmd_imcopy(int argc, char * argv[])
{
   if(argc == 4)
   {
      std::string infilename{argv[1]};
      int extnum = std::stoi(std::string{argv[2]});
      std::string pixfilter{argv[3]};
      try
      {
         imcopy(infilename, extnum, pixfilter);
      }
      catch(const std::exception & ex)
      {
         std::cerr << ex.what() << std::endl;
         return EXIT_FAILURE;
      }
      return EXIT_SUCCESS;
   }
   else
   {
      std::cerr
         << "Usage:  imcopy filename.fits extnum [a:b c:d ...]\n"
         << "\n"
         << "Send to stdout a subimage of N-dimesional FITS-file HDU with N-element pixel filter.\n"
         << "HDU is given by extension number (0=Primary HDU, 1=Ext1, ...)"
         << "Examples: \n"
         << "   vlkb imcopy /rootvialactea/CHaMP/region7.fits 0 [1:100,1:100,1:100]\n";
      return EXIT_FAILURE;
   }
}


int cmd_imcopydav(int argc, char * argv[])
{
   if(argc == 4)
   {
      std::string url{argv[1]};
      int extnum = std::stoi(std::string{argv[2]});
      std::string pixfilter{argv[3]};
      try
      {
         imcopydav(url, extnum, pixfilter);
      }
      catch(const std::exception & ex)
      {
         std::cerr << ex.what() << std::endl;
         return EXIT_FAILURE;
      }
      return EXIT_SUCCESS;
   }
   else
   {
      std::cerr
         << "Usage:  imcopydav url extnum [a:b c:d ...]\n"
         << "\n"
         << "Send to stdout a subimage of N-dimesional FITS-file HDU with N-element pixel filter.\n"
         << "HDU is given by extension number (0=Primary HDU, 1=Ext1, ...)"
         << "Examples: \n"
         << "   vlkb imcopydav http://localhost:80/tmp/testfile.fits 0 [1:100,1:100,1:100]\n";
      return EXIT_FAILURE;
   }
}




int cmd_cutout(int argc, char * argv[])
{
   if (argc != 4)
   {
      std::cerr
         << "Usage:  cutout <filename.fits> <extnum> <region>\n"
         << "\n"
         << "Calculate overlap between HDU in file and region.\n\nregion in JSON form of VO-SODA params. For example 'POS=CIRCLE 21.4458 -1.373 0.1' :\n"
         " \'{\"pos\":{\"circle\":{\"lat\":-1.373,\"lon\":21.4458,\"radius\":0.1},\"system\":\"ICRS\"},\"service\":\"SUBIMG\"}\'\n";
      return EXIT_FAILURE;
   }
   else
   {
      string pathname{argv[1]};
      int extnum = std::stoi(std::string{argv[2]});
      string region{argv[3]};

      int rc = stream_cutout(pathname, extnum, region);
      if(!rc)
         return EXIT_SUCCESS;
   }
   return EXIT_FAILURE;
}




int cmd_cutpixels(int argc, char * argv[])
{
   if(argc < 2)
   {
      std::cerr
         << "Usage:  cutpixles filename.fits[a:b,c:d,...]\n"
         << "\n"
         << "Cut part of N-dimesional FITS-file on pixel coordinates (using cfitsio extended syntax).\n"
         << "Examples: \n"
         << "   vlkb cutpixels /rootvialactea/CHaMP/region7.fits[1:100,1:100,1:100]\n";
      return EXIT_FAILURE;
   }
   else
   {
      const unsigned long int hdunum = 1;
      std::string infilename{argv[1]};

      size_t last_dot   = infilename.find_last_of(".");
      size_t last_slash = infilename.find_last_of("/");
      size_t last_open_bracket  = infilename.find_last_of("[");
      size_t last_close_bracket = infilename.find_last_of("]");

      std::string pix_ranges = infilename.substr(last_open_bracket+1, last_close_bracket-last_open_bracket-1);
      std::replace(pix_ranges.begin(), pix_ranges.end(), ':', '-');
      std::replace(pix_ranges.begin(), pix_ranges.end(), ',', '_');

      const std::string outfilename{infilename.substr(last_slash+1, last_dot-last_slash-1 ) + "_CUT_" + pix_ranges + ".fits"};

      fitsfiles::fits_hdu_cut(infilename, hdunum, outfilename);
      return EXIT_SUCCESS;
   }
}



int cmd_listbounds(int argc, char * argv[])
{
   int rc;

   if (!((argc == 2) || (argc == 3) || (argc == 4)))
   {
      std::cerr
         << "Usage:  listbounds <filename.fits> <SkySystem> <SpecSystem>\n"
         << "\n"
         << "List the bounds of the region represented by FITS header.\n"
         << "Arguments:\n"
         << "   SkySystem  GALACTIC or ICRS\n"
         << "   SpecSystem System=VELO,StdOfRest=LSRK,Unit=km/s or WAVE, Barycentric, m\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      string pathname(argv[1]);

      string skySystem;
      string specSystem;

      if(argc >= 3)
      {
         skySystem = argv[2];
      }
      if(argc == 4)
      {
         specSystem = argv[3];
      }

      cout << string{argv[0]} << ": " << pathname << " '" << skySystem  << "' " << specSystem << endl;

      rc = vlkb_listbounds(skySystem, specSystem, pathname);
      std::cout << "vlkb_listbounds rc: " << rc << std::endl;
      rc = EXIT_SUCCESS;
   }
   return rc;
}

int cmd_header_wcs(int argc, char * argv[])
{
   int rc;

   if (argc < 2)
   {
      std::cerr
         << "Usage:  header_wcs <pathname.fits>...\n"
         << "\n"
         << "Prints current header WCS axes info.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      unsigned int hdunum = 1;
      char altwcs = ' ';

      for(int i=1; i<argc; i++)
      {
         string pathname(argv[i]);

         std::map<std::string, std::string> card_values{fitsfiles::read_header_wcs_coord_type(pathname, hdunum, altwcs)};

         for(auto it = card_values.cbegin(); it != card_values.cend(); ++it)
         {
            std::cout << it->first << " " << it->second << ", ";
         }
         std::cout << pathname << std::endl;
      }
      rc = EXIT_SUCCESS;
   }
   return rc;
}


int cmd_header(int argc, char * argv[])
{
   int rc;

   if (argc < 2)
   {
      std::cerr
         << "Usage:  header [--backup] <pathname.fits>...\n"
         << "\n"
         << "Prints current header (one card per line) or writes the header into a file with the same pathname but 'fitshdr' extension.\n"
         << "Arguments:\n"
         << "   --backup  write current header from file 'pathname.fits' to 'pathname.fitshdr#n' where n is the HDU number.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      bool backup = false;

      for(int i=1; i<argc; i++)
      {
         if(0 == (string(argv[i]).substr(0,2+6)).compare("--backup"))
         {
            backup = true;
         }
         else
         {
            string pathname(argv[i]);
            cout << to_string(i) << " : " << pathname << endl;

            rc = header_backup(pathname, backup);
            std::cout << "header_backup rc: " << rc << std::endl;
         }
      }
      rc = EXIT_SUCCESS;
   }
   return rc;
}


int cmd_header_modif(int argc, char * argv[])
{
   int rc;

   if (argc < 2)
   {
      std::cerr
         << "Usage:  headermodif --sky=<SkySystem> --spec=<SpecSystem> <filename.fits>... \n"
         << "\n"
         << "Modify header for new coord system.\n"
         << "Arguments:\n"
         << "   SkySystem  GALACTIC or ICRS\n"
         << "   SpecSystem System=VELO,StdOfRest=LSRK,Unit=km/s or WAVE, Barycentric, m\n";
      rc = EXIT_FAILURE;
   }
   else
   {

      string skySystem;
      string specSystem;

      for(int i=1; i<argc; i++)
      {
         if(0 == (string(argv[i]).substr(0,2+3+1)).compare("--sky="))
         {
            skySystem = string(argv[i]).substr(2+3+1);
         }
         else if(0 == (string(argv[i]).substr(0,2+4+1)).compare("--spec="))
         {
            specSystem = string(argv[i]).substr(2+4+1);
         }
         else
         {
            string pathname(argv[i]);
            cout << to_string(i) << " : " << pathname << endl;
            rc = header_modif_coordsys(skySystem, specSystem, pathname);
            std::cout << "header_modif_coordsys rc: " << rc << std::endl;
         }
      }
      rc = EXIT_SUCCESS;
   }
   return rc;
}




int cmd_skyvertices(int argc, char * argv[])
{
   int rc;

   if (argc != 3)
   {
      std::cerr
         << "Usage:  skyvertices <filename.fits[ext]> <SkySystem>\n"
         << "\n"
         << "List vertices in sky of the region represented by FITS header.\n"
         << "Note SkySystem  GALACTIC or ICRS\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      string pathname(argv[1]);
      string skySystem(argv[2]);

      cout << string{argv[0]} << ": " << pathname << " '" << skySystem  << "' " << endl;

      rc = vlkb_skyvertices(pathname, skySystem);
      std::cout << "vlkb_skyvertices rc: " << rc << std::endl;
      rc = EXIT_SUCCESS;
   }
   return rc;
}


char overlapmsg[7][512] = {
   "0 - The check could not be performed because the second Region could not be mapped into the coordinate system of the first Region.",
   "1 - There is no overlap between the two Regions.",
   "2 - The first Region is completely inside the second Region.",
   "3 - The second Region is completely inside the first Region.",
   "4 - There is partial overlap between the two Regions.",
   "5 - The Regions are identical to within their uncertainties.",
   "6 - The second Region is the exact negation of the first Region to within their uncertainties."
};

int cmd_overlap(int argc, char * argv[])
{
   int rc;

   bool is_last_x = (*argv[argc-1] == 'x');
   if(is_last_x) argc--;

   if (argc != 3)
   {
      std::cerr
         << "Usage:  overlap <filename.fits[ext]> <region>\n"
         << "\n"
         << "Calculate overlap between HDU in file and region.\n\nregion in JSON form of VO-SODA params. For example 'POS=CIRCLE 21.4458 -1.373 0.1' :\n"
         " \'{\"pos\":{\"circle\":{\"lat\":-1.373,\"lon\":21.4458,\"radius\":0.1},\"system\":\"ICRS\"},\"service\":\"SUBIMG\"}\'\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      string pathname{argv[1]};
      string region{argv[2]};

      vector<uint_bounds> bnds;
      int ov_code = vlkb_overlap(pathname, region, bnds);
      if((ov_code >= 2) && (ov_code <= 5))
      {
         rc = EXIT_SUCCESS;
         cout << to_cfitsio_format_with_axis_type(bnds) << endl;
      }
      else if((ov_code == 1) || (ov_code == 6))
      {
         rc = EXIT_SUCCESS;
      }
      else
      {
         throw runtime_error("overlap code invalid: " + to_string(ov_code));
      }
   }
   return rc;
}


int cmd_dropdegen(int argc, char * argv[])
{
   int rc;

   if (argc != 2)
   {
      std::cerr
         << "Usage:  dropdegen <filename.fits[ext]>\n"
         << "\n"
         << "Drop degenerate axis (axis with NAXISi=1). Given file will be overwritten! Make a copy before.\n"
         << "Note that it may be necessary to enclose the input file\n"
         << "name in single quote characters on the Unix command line.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      rc = vlkb_dropdegen(argv[1]);
      std::cout << "vlkb_dropdegen rc: " << rc << std::endl;
      rc = EXIT_SUCCESS;
   }
   return rc;
}


int cmd_checkcard(int argc, char * argv[])
{
   int rc;

   if (argc != 3)
   {
      std::cerr
         << "Usage:  checkcard <keyname> <filename.fits>\n"
         << "\n"
         << "Checks if keyname present in fits header.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      const unsigned int hdunum = 1;
      string card{fitsfiles::read_card(argv[2], hdunum, argv[1])};
      cout << card << endl;
      rc = EXIT_SUCCESS;
   }
   return rc;
}

int cmd_addcard(int argc, char * argv[])
{
   int rc;

   if (argc != 3)
   {
      std::cerr
         << "Usage:  addcard key value <filename.fits>\n"
         << "\n"
         << "Adds card by key if it is missing from the header.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      std::string key{argv[1]};
      std::string value{argv[2]};
      std::string filename{argv[3]};
      cout << "NOT IMPLEMENTED: use modcard - it adds card if not exist" << endl;
      rc = EXIT_FAILURE; // FIXME
   }
   return rc;
}



int cmd_modcard(int argc, char * argv[])
{
   int rc;

   if (!((argc == 3) || (argc >= 4)))
   {
      std::cerr
         << "Usage:  modcard token [-v newvalue] <*.fits>\n"
         << "\n"
         << "Overwrites value in key-record which contains string 'token' anywhere in card\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      cout << "argc: " << argc << endl;
      string newvalue;
      string token{argv[1]};
      string arg2{argv[2]};

      cout << "token: " << token << endl;
      cout << "arg2: " << arg2 << endl;

      int ii_start;
      if(0 == arg2.compare("-v"))
      {
         newvalue = string{argv[3]};
         ii_start = 4;
      }
      else
      {
         ii_start = 2;
      }

      cout << "newvalue: " << newvalue << endl;
      cout << "files: " << endl;

      int ii;
      for(ii=ii_start; ii<argc; ii++)
      {
         cout << argv[ii] << endl;
         rc = fitsfiles::mod_value(argv[ii], token, newvalue);
         std::cout << "fitsfiles::mod_value rc: " << rc << std::endl;
         rc = EXIT_SUCCESS;
      }
   }
   return rc;
}


int cmd_rawdelcard(int argc, char * argv[])
{
   int rc;

   if (argc < 3)
   {
      std::cerr
         << "Usage:  rawdelcard keyname <*.fits>\n"
         << "\n"
         << "Extends keyname with spaces to 8 chars and removes all occurences from the header"
         " ('raw': does not use cfitsio and so it can remove cards also from non-standard headers"
         " (like when by mistake wrong card inserted before NAXISn card.)\n"
         << "Note: currently implemented only for Primary HDU.\n";
      rc = EXIT_FAILURE;
   }
   else
   {
      cout << "argc: " << argc << endl;
      string newvalue;
      string keyname{argv[1]};

      cout << "keyname: " << keyname << endl;
      cout << "files: " << endl;
      //const unsigned int hdunum = 1;
      int ii;
      for(ii=2; ii<argc; ii++)
      {
         cout << argv[ii] << endl;
         remove_raw_card(argv[ii], /*hdunum,*/ keyname);
      }
      rc = EXIT_SUCCESS;
   }
   return rc;
}




//-----------------------------------------------------------
// main
//-----------------------------------------------------------
int main (int argc, char * argv[])
{
   const std::string progname = basename(argv[0]);

   if( argc < 2 )
   {
      vlkb::usage(progname);
      return EXIT_FAILURE;
   }

   LOG_open("/tmp", string("vlkb-") + string(argv[1]) + ".log");

   int rc = EXIT_SUCCESS;
   try
   {
      const vlkb::cmd_set cmd(vlkb::to_cmd(argv[1]));

      int cmd_argc = argc - 1;
      char ** cmd_argv = &(argv[1]);

      switch(cmd)
      {
         case vlkb::cutout:      rc = cmd_cutout(cmd_argc, cmd_argv); break;
         case vlkb::imcopy:      rc = cmd_imcopy(cmd_argc, cmd_argv); break;
         case vlkb::imcopydav:   rc = cmd_imcopydav(cmd_argc, cmd_argv); break;
         case vlkb::cutpixels:   rc = cmd_cutpixels(cmd_argc, cmd_argv); break;
         case vlkb::multicutout: rc = cmd_multicutout(cmd_argc, cmd_argv); break;
         case vlkb::mergefiles:  rc = cmd_mergefiles(cmd_argc, cmd_argv); break;

         case vlkb::nullvals:    rc = cmd_nullvals(cmd_argc, cmd_argv); break;
         case vlkb::listbounds:  rc = cmd_listbounds(cmd_argc, cmd_argv); break;
         case vlkb::header:      rc = cmd_header(cmd_argc, cmd_argv); break;
         case vlkb::headermodif: rc = cmd_header_modif(cmd_argc, cmd_argv); break;
         case vlkb::headerwcs:   rc = cmd_header_wcs(cmd_argc, cmd_argv); break;
         case vlkb::skyvertices: rc = cmd_skyvertices(cmd_argc, cmd_argv); break;
         case vlkb::overlap:     rc = cmd_overlap(cmd_argc, cmd_argv); break;

         case vlkb::dropdegen:   rc = cmd_dropdegen(cmd_argc, cmd_argv); break;
         case vlkb::checkcard:   rc = cmd_checkcard(cmd_argc, cmd_argv); break;
         case vlkb::addcard:     rc = cmd_addcard(cmd_argc, cmd_argv); break;
         case vlkb::modcard:     rc = cmd_modcard(cmd_argc, cmd_argv); break;
         case vlkb::rawdelcard:  rc = cmd_rawdelcard(cmd_argc, cmd_argv); break;

         default: assert(false);
      }
   }
   catch(const invalid_argument& ex)
   {
      cerr << "invalid_argument: " << ex.what() << endl;
      rc = EXIT_FAILURE;
   }
   catch(const runtime_error& ex)
   {
      cerr << "runtime_error: " << ex.what() << endl;
      rc = EXIT_FAILURE;
   }
   catch(const exception& ex)
   {
      cerr <<  "exception: " << ex.what() << endl;
      rc = EXIT_FAILURE;
   }

   LOG_close();
   return rc;
}

