
#include "json_region.hpp"
#include "cutout.hpp"
#include "fitsfiles.hpp"
#include "fits_header.hpp"
#include "ast4vl.hpp"
#include "cutout_nljson.hpp"
#include "cutout_ostream.hpp"
#include "json.hpp"
#include "io.hpp"
#include "my_assert.hpp"

#include <string.h>
#include <string>
#include <fstream> // ofstream for tgz-file
#include <sstream>
#include <stdexcept>
// for timestamp
#include <iomanip>
#include <chrono>
#include <ctime>

/* create_timestamp */
#include <time.h>
#include <sys/time.h>



using namespace std;
using json = nlohmann::json;

coordinates::coordinates()
{
   skysys  = skysystem::NONE;
   shape   = area::CIRCLE;
   specsys = specsystem::NONE;
   timesys = timesystem::NONE;
}


int convert_pol(std::string pol)
{
   // "I", "Q", "U", "V", "RR", "LL", "RL", "LR", "XX", "YY", "XY", "YX"
        if(pol.compare("I") == 0) return 1;
   else if(pol.compare("Q") == 0) return 2;
   else if(pol.compare("U") == 0) return 3;
   else if(pol.compare("V") == 0) return 4;

   else if(pol.compare("RR") == 0) return -1;
   else if(pol.compare("LL") == 0) return -2;
   else if(pol.compare("RL") == 0) return -3;
   else if(pol.compare("LR") == 0) return -4;
   else if(pol.compare("XX") == 0) return -5;
   else if(pol.compare("YY") == 0) return -6;
   else if(pol.compare("XY") == 0) return -7;
   else if(pol.compare("YX") == 0) return -8;
   else
      throw invalid_argument(pol + " is not a valid polarization state");
}

int min_pol_state(std::vector<std::string> pol)
{
   int min_p = 1000;
   for(string p : pol)
   {
      int np = convert_pol(p);
      if(np < min_p) min_p = np;
   }
   return min_p;
}

int max_pol_state(std::vector<std::string> pol)
{
   int max_p = -1000;
   for(string p : pol)
   {
      int np = convert_pol(p);
      if(np > max_p) max_p = np;
   }
   return max_p;
}




char * usec_timestamp(char * ts, size_t ts_len)
{
	struct timeval tv; 
	time_t nowtime;
	struct tm *nowtm;
	char tmbuf[64];//, buf[64];

	gettimeofday(&tv, NULL);
	nowtime = tv.tv_sec;
	nowtm = localtime(&nowtime);
	strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d_%H-%M-%S", nowtm);
	snprintf(ts, ts_len, "%s_%06ld", tmbuf, (long)tv.tv_usec);

	return ts; 
}   

string create_timestamp()
{
	const int TS_LEN = 256;
	char ts[TS_LEN];
	return string(usec_timestamp(ts,TS_LEN));
}

string generate_cut_fitsname(string pubdid, unsigned int hdunum)
{

   //pubdid = pubdid.substr(0,pubdid.find_last_of('_'));

   string cutfitsname{"vlkb-cutout"};

   string timestamp{create_timestamp()};
   replace(pubdid.begin(), pubdid.end(), '/', '-');
   replace(pubdid.begin(), pubdid.end(), ' ', '_');

   // FIXME works only for one digit hdunum

   if(hdunum == 1)
   {
      return cutfitsname + '_' + timestamp + '_' + pubdid;
   }
   else
   {
      string extn{"EXT" + to_string(hdunum-1)};
      return cutfitsname + '_' + timestamp + '_' +  extn + '_' + pubdid;
   }
}




int to_v_type(enum specsystem specsys)
{
	switch(specsys)
	{
		case specsystem::NONE: return 0;
		case specsystem::VELO_LSRK: return 1;
		case specsystem::WAVE_Barycentric: return 2;
	}
	return 0;
}



int to_v_valid(enum specsystem specsys)
{
   return specsys == specsystem::NONE ? 0 : 1;
}



string to_cfitsio_format(vector<uint_bounds> bounds)
{
   my_assert(!bounds.empty(),__FILE__,__LINE__,"bounds vector is empty" );

   stringstream ss;
   ss <<"[";
   ss << bounds[0].pix1 << ":" << bounds[0].pix2;
   for(unsigned int i = 1; i < bounds.size(); i++)
   {
      ss << " " << bounds[i].pix1 << ":" << bounds[i].pix2;
   }
   ss << "]";
   return ss.str();
}

string axistype2string(unsigned char cc)
{
   switch(cc)
   {
      case 'o': return "LON"; break;
      case 'a': return "LAT"; break;
      case 'b': return "BAND"; break;
      case 't': return "TIME"; break;
      case 'p': return "POL"; break;
      default:
         throw invalid_argument(cc + " is not a valid axis type");
   }
}



string to_cfitsio_format_with_axis_type(vector<uint_bounds> bounds)
{
   my_assert(!bounds.empty(),__FILE__,__LINE__,"bounds vector is empty" );

   stringstream ss;
   ss << to_cfitsio_format(bounds);
   ss << " AXES ";

   for(unsigned int i = 0; i < bounds.size(); i++)
   {
      ss << " " << axistype2string(bounds[i].type);
   }

   return ss.str();
}


coordinates parse_coordinates(const string region_string)
{
   LOG_trace(string(__func__) + " : " + region_string);

   json_region reg(region_string);
   coordinates coord = to_coordinates(reg.get_pos(), reg.get_band(), reg.get_time(), reg.get_pol());

   LOG_STREAM << "coord parsed: " << coord << endl;

   return coord;
}


coordinates to_coordinates(const position pos, const band bnd, const time_axis time, const std::vector<std::string> pol)
{
   coordinates coord;
   coord.skysys = pos.sys;

   if(pos.shape == area::RANGE)
      coord.shape = area::RECT;
   else
      coord.shape = pos.shape;

   switch(coord.shape)
   {
      case area::CIRCLE:
         {
            coord.lon_deg  = pos.circ.lon;
            coord.lat_deg  = pos.circ.lat;
            coord.dlon_deg = 2.0 * pos.circ.radius;
            coord.dlat_deg = coord.dlon_deg;
         }
         break;

      case area::RECT:
         {
            coord.lon_deg  = (pos.rng.lon1 + pos.rng.lon2)/2.0;
            coord.lat_deg  = (pos.rng.lat1 + pos.rng.lat2)/2.0;
            coord.dlon_deg = pos.rng.lon2 - pos.rng.lon1;
            coord.dlat_deg = pos.rng.lat2 - pos.rng.lat1;
         }
         break;

      case area::POLYGON:
         {
            coord.p_lon_deg  = pos.poly.lon;
            coord.p_lat_deg  = pos.poly.lat;
         }
         break;

      default:
         {
            my_assert( false, __FILE__,__LINE__, " unknown shape in JSON: " + to_string(int(coord.shape)) );
         }
   }

   coord.specsys = bnd.sys;
   if(coord.specsys != specsystem::NONE)
   {
      coord.vl_kmps = bnd.band_value[0];
      coord.vu_kmps = bnd.band_value[1];
   }

   coord.timesys = time.sys;
   if(coord.timesys != timesystem::NONE)
   {
      coord.time_value[0] = time.time_value[0];
      coord.time_value[1] = time.time_value[1];
   }

   coord.pol = pol;

   return coord;
}






std::uintmax_t cutout_file(
      const string abs_fits_pathname, unsigned int hdunum,
      const coordinates coord,
      const string vlkbcutout_pathname,
      const vector<fits_card> extra_cards)
{
   LOG_trace(__func__);

   string header{ fitsfiles::read_header(abs_fits_pathname, hdunum) };

   my_assert(!header.empty(),__FILE__,__LINE__,"header is empty" );

   if(!extra_cards.empty())
   {
      header = fitsfiles::append_card_if_not_in_header(header, extra_cards);
   }

   int ov_code;
   vector<uint_bounds> bounds = calc_overlap(header, coord, ov_code);
   //vector<uint_bounds> bounds = legacy::call_AST4VL_overlap(coord, header, ov_code);

   if((ov_code==1)||(ov_code==6))
      throw invalid_argument("given coordinates do not overlap with given fits-hdu area (ov_code = " + to_string(ov_code) +")");

   string bounds_str = to_cfitsio_format(bounds);

   LOG_STREAM << "bounds " << bounds_str << endl;

   fitsfiles::fits_hdu_cut(abs_fits_pathname + bounds_str, hdunum, vlkbcutout_pathname);

   if(!extra_cards.empty())
   {
      fits::header hdr(vlkbcutout_pathname, 1, READWRITE);
      hdr.update(extra_cards);
   }

   LOG_STREAM << "cutout file: " + vlkbcutout_pathname << endl;

   return fitsfiles::fileSize(vlkbcutout_pathname);
}



cutout_res_s do_cutout_file(
      const std::string fits_pathname, unsigned int hdunum,
      const position pos, const band bnd, const time_axis time, const std::vector<std::string> pol,
      const bool count_null_values,
      const std::vector<fits_card> extra_cards,
      const std::string conf_fits_path,
      const std::string conf_fits_cutpath)
{
   const string abs_subimg_pathname = conf_fits_cutpath + "/" + generate_cut_fitsname(fits_pathname, hdunum);
   const string abs_fits_pathname{ conf_fits_path + "/" + fits_pathname };

   coordinates coord = to_coordinates(pos, bnd, time, pol);

   uintmax_t filesize = cutout_file(abs_fits_pathname, hdunum, coord, abs_subimg_pathname, extra_cards);

   unsigned long long null_cnt  = 0;
   unsigned long long total_cnt = 0;
   double fill_ratio = -1.0;
   if(count_null_values)
   {
      fill_ratio = fitsfiles::calc_nullvals(abs_subimg_pathname, /*hdunum*/1, null_cnt, total_cnt);
   }

   cutout_res_s cutres{ filesize, abs_subimg_pathname, {fill_ratio, null_cnt, total_cnt} };
   return cutres;
}






