
#include "parse_surveys_csv.hpp"

#include "my_assert.hpp"
#include "io.hpp"

#include <aria-csv-parser/parser.hpp>

#include <limits> // uint max needed
#include <stdexcept>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

using namespace aria::csv;
using namespace std;


string trim(string& str)
{
   // \v(=11) vertical-tab, \f(=12) formfeed
   // NOTE : current terminal drivers do not honour these but simply translate to chars; however some printer drivers could
   const string spaces{" \t\n\r\v\f"};
   size_t first = str.find_first_not_of(spaces);
   if (first == std::string::npos)
      return "";
   size_t last = str.find_last_not_of(spaces);
   return str.substr(first, (last-first+1));
}


string to_string(dataproduct d)
{
   string str;
   switch(d)
   {
      case dataproduct::IMAGE : str = "image"; break;
      case dataproduct::CUBE  : str = "cube";  break;
   }
   my_assert(!str.empty(), __FILE__,__LINE__, "unrecognized value of dataproduct type");
   return str;
}


dataproduct to_dataproduct(string raw_string)
{
   if(trim(raw_string).compare("image") == 0) return dataproduct::IMAGE;
   else if(trim(raw_string).compare("cube")  == 0) return dataproduct::CUBE;
   else throw invalid_argument("'dataproduct' type must be 'image' or 'cube' but was: >" + raw_string + "<"); 
}



string to_string(authorization_policy p)
{
   string str;
   switch(p)
   {
      case authorization_policy::PRIVATE : str = "PRIVATE"; break;
      case authorization_policy::PUBLIC  : str = "PUBLIC";  break;
   }
   my_assert(!str.empty(), __FILE__,__LINE__, "unrecognized value of authorization_policy type");
   return str;
}

authorization_policy to_authorization_policy(string raw_string)
{
   if(trim(raw_string).compare("PRIVATE") == 0) return authorization_policy::PRIVATE;
   else if(trim(raw_string).compare("PUBLIC")  == 0) return authorization_policy::PUBLIC;
   else throw invalid_argument("'authorization_policy' type must be 'PRIVATE' or 'PUBLIC' but was: " + raw_string);
}



std::string to_string(velocity_unit vu)
{
   string str;
   switch(vu)
   {
      case velocity_unit::MPS  : str = "m/s";  break;
      case velocity_unit::KMPS : str = "km/s"; break;
      case velocity_unit::NONE : str = "''";   break;
      case velocity_unit::KPC :  str = "kpc";  break;
   }
   my_assert(!str.empty(), __FILE__,__LINE__, "unrecognized value of velocity_unit type");
   return str;
}

velocity_unit to_velocity_unit(std::string raw_string)
{
   if(trim(raw_string).compare("m.s**-1") == 0) return velocity_unit::MPS;
   else if(trim(raw_string).compare("m/s") == 0) return velocity_unit::MPS;
   else if(trim(raw_string).compare("km.s**-1") == 0) return velocity_unit::KMPS;
   else if(trim(raw_string).compare("km/s") == 0) return velocity_unit::KMPS;
   else if(trim(raw_string).empty()) return velocity_unit::NONE;
   else if(trim(raw_string).compare("kpc") == 0) return velocity_unit::KPC;
   else throw invalid_argument("'velocity_unit' type must be 'm.s**-1' or 'km.s**-1' but was: " + raw_string); 
}


std::string to_string(calibration calib_level)
{
   string str;
   switch(calib_level)
   {
      case calibration::LEVEL0: str = "LEVEL0"; break;
      case calibration::LEVEL1: str = "LEVEL1"; break;
      case calibration::LEVEL2: str = "LEVEL2"; break;
      case calibration::LEVEL3: str = "LEVEL3"; break;
      case calibration::LEVEL4: str = "LEVEL4"; break;
   }
   my_assert(!str.empty(), __FILE__,__LINE__, "unrecognized value of calibration type in " + string{__func__});
   return str;
}

unsigned int to_uint(calibration calib_level)
{
   const unsigned int default_value = std::numeric_limits<unsigned int>::max();
   unsigned int val = default_value;
   switch(calib_level)
   {
      case calibration::LEVEL0: val = 0; break;
      case calibration::LEVEL1: val = 1; break;
      case calibration::LEVEL2: val = 2; break;
      case calibration::LEVEL3: val = 3; break;
      case calibration::LEVEL4: val = 4; break;
   }
   my_assert(val != default_value, __FILE__,__LINE__, "unrecognized value of calibration type in " + string{__func__});
   return val;
}


calibration to_calibration(std::string raw_string)
{
   size_t pos{};
   unsigned int level = stoi(raw_string, &pos);

   switch(level)
   {
      case 0: return calibration::LEVEL0; break;
      case 1: return calibration::LEVEL1; break;
      case 2: return calibration::LEVEL2; break;
      case 3: return calibration::LEVEL3; break;
      case 4: return calibration::LEVEL4; break;
      default:
              throw invalid_argument("'calibration' value is " + to_string(level) + " however valid values are 0,1,2,3 and 4");
   }
}





std::ostream& operator<<( std::ostream &out, struct survey const& p)
{
   out << setw(3) << right << p.survey_id 
      << ": " << setw(5) << to_string(p.dataproduct_type)
      << " " << setw(7) << to_string(p.auth_policy)
      << setw(13) << right << p.rest_frequency_Hz;
   out << " " << setw(50) << left << p.storage_path + "/" + p.file_filter; 
   out << " [" << p.name << ", " << p.species << ", " << p.transition << "]" 
      << "   " << p.description
      << "   " << p.o_ucd;	       

   return out;
}


string to_stringN(string raw_string, string::size_type n)
{
   if(raw_string.length() > n) throw invalid_argument(raw_string + " is too long. Should be at most : " + to_string(n));
   else return raw_string;
}


vector<survey> parse_surveys(string filename, bool skip_first_row)
{
   LOG_trace(__func__);

   std::ifstream f(filename);
   CsvParser parser(f);

   vector<survey> surveys;

   struct survey surv;

   int ixr = 0;
   for (auto& row : parser)
   {
      if(skip_first_row && (ixr++ == 0)) continue; // FIXME do before enterning cycle

      int ixf = 0;
      for (auto& field : row)
      {
         switch(ixf++)
         {
            case 0: surv.survey_id = atoi(field.c_str()); break;
                    // subsurvey id
            case 1: surv.name       = field; break;
            case 2: surv.species    = field; break;
            case 3: surv.transition = field; break;
                    // vlkb-internal extra-data
            case 4: surv.rest_frequency_Hz = stoul(field); break;
            case 5: break; // NOT USED surv.restf_fits_unit     = to_stringN(field, 20); break;
            case 6: surv.velocity_fits_unit  = to_velocity_unit(field); break;
                    // vlkb-internal did-resolution
            case 7: surv.storage_path  = field; break;
            case 8: surv.file_filter   = field; break;
                    //
            case 9: surv.description = field; break;
                    // obscore related
            case 10: surv.dataproduct_type         = to_dataproduct(field); break;
            case 11: surv.calib_level              = to_calibration(field); break;
            case 12: surv.o_ucd                    = field; break;
            case 13: surv.fitskey_facility_name    = to_stringN(field, 8); break;
            case 14: surv.fitskey_instrument_name  = to_stringN(field, 8); break;
                     // security
            case 15: surv.auth_policy = to_authorization_policy(field); break;
            default:
                     my_assert(false, __FILE__,__LINE__, "too many fileds in a row of surveys metadata file: " + filename);
         }
      }
      surveys.push_back(surv);
   }

   return surveys;
}



