
#include "database.hpp"

#include "DbConn.hpp"
#include "SqlSchema.hpp"
#include "SqlSchema_CREATE.hpp"
#include "SqlSchema_INSERT.hpp"
#include "SqlSchema_DELETE.hpp"
#include "Sql_SELECT_SurveyBounds.hpp"
#include "fitsfiles.hpp"
#include "io.hpp"

#include <iostream>
#include <iomanip>
#include <libgen.h> // basename() needed
#include <iterator>


using namespace std;


struct db_bounds
{
   unsigned int header_id;
   double lon_from;
   double lon_to;
   double lat_from;
   double lat_to;
};



//----------------------------------------------------------------------//
// init db:
// all tables will be deleted if existed
// create surveys table and load its values from config::SurveysFile
// create other tables empty
//----------------------------------------------------------------------//
void database::dbInit(const string db_uri, const string db_schema, const string surveys_csv_pathname)
{
   LOG_trace(__func__);

   DbConn db(db_uri, db_schema);

   Survey tabSurveys("surveys");

   SqlSchema_CREATE cmdCreate;

   vector<string> createAndLoadSurveys = { 
      tabSurveys.getCREATE(),
      tabSurveys.getINSERT(surveys_csv_pathname)
   };

   vector<string> createHeadersDb(cmdCreate.getCREATE());

   db.dbExecCmds(createAndLoadSurveys);
   db.dbExecCmds(createHeadersDb);
};



//----------------------------------------------------------------------//
// check - verify consistency of database 
// RULE1: each table must contain the same number of rows
//----------------------------------------------------------------------//
vector<string> database::dbCheck(const string db_uri, const string db_schema)
{
   DbConn db(db_uri, db_schema);

   vector<string> result_strings;

   // check 1: rows count equal in all tables

   vector<string> tables = db.queryTableNames();

   string qRowsCount = "SELECT ";
   for(const string &table : tables)
      qRowsCount += "(SELECT count(*) FROM " + table + ") as " + table + ",";
   qRowsCount.erase(qRowsCount.size()-1);// remove last coma

   vector<string> table_sizes{db.dbExecQuery2(qRowsCount)};

   result_strings.insert(
         result_strings.end(),
         std::make_move_iterator(table_sizes.begin()),
         std::make_move_iterator(table_sizes.end())
         );

   return result_strings;
};









//----------------------------------------------------------------------//
// query Surveys Table for defined surveys
// and list how many entries (FITS-filse) each Survey has in the system
// (Headers, ObsCore, VerticesGalactic,... tables)
// Side-effect: check that number of entries in different tables is equal, otherwise
// tables are inconsistent; DB is in error state
//----------------------------------------------------------------------//
vector<string> database::dbListSurveys(const string db_uri, const string db_schema)
{
   LOG_trace(__func__);

   DbConn db(db_uri, db_schema);

   string qSidNameCount(
         "SELECT survey_id,count(headers.survey_id), name,species,transition " 
         "FROM headers "
         // only common rows         "INNER JOIN surveys USING (survey_id) "
         "RIGHT OUTER JOIN surveys USING (survey_id) " // all rows of surveys table
         "GROUP BY survey_id,surveys.name,surveys.species,surveys.transition "
         "ORDER BY surveys.survey_id");

   vector<string> res_str;// = db.dbExecQuery2(qSidNameCount);
   //pqxx::result = res = db.dbExecQuery3(qSidNameCount);
   return res_str;
};




void database::dbSurveyBounds(/*int sid,*/ const string db_uri, const string db_schema)
{
   LOG_trace(__func__);

   DbConn db(db_uri, db_schema);

   // FIXME re-implement with obscore::s_region or polygon_region(_galactic)
   //
   //db.dbExecQuery(Sql_SELECT_SurveyBounds::qLonMinMax(sid));
   //db.dbExecQuery(Sql_SELECT_SurveyBounds::qLatMinMax(sid));
}




string database::dbListFiles(int sid, const string db_uri, const string db_schema, const string fitsdir)
{
   LOG_trace(__func__);

   DbConn db(db_uri, db_schema);

   Survey surv = db.querySurveyAttributes(sid);
   string storageFilter = surv.getStorageFilter();

   std::vector<string> pathnames =
      fitsfiles::globVector(fitsdir + "/" + storageFilter);

//   LOG_STREAM << "SID[" << setw(3) <<sid << "]: " << setw(5) <<pathnames.size() << "    "
//      << surv.survName << " " << surv.survSpecies << " " << surv.survTransition << endl;

   return "SID[" + to_string(sid) + "]: " + to_string(pathnames.size()) + "    "
      + surv.survName + " | " + surv.survSpecies + " | " + surv.survTransition;
}



/* FIXME implement add-survey optionally without need of csv-metadata with params:
 * const string storage_filter, // identify files
 * const int    calib_level,    // must provide calib-level for them
 * const string obs_collection, // give it a name
 */
void database::dbAddSurvey(int sid, const string groups,
      const string obscore_publisher,
      const string obscore_access_format,
      const string obscore_access_url,
      const string db_uri, const string db_schema, const string fitsdir,
		int max_hdupos, int min_hdupos)
{
   LOG_trace(__func__);

   // 1, load optional metadata (from Db or csv-file) if exist
   DbConn db(db_uri, db_schema); 
   Survey surv = db.querySurveyAttributes(sid);


   // 2, storage-filter identifies the files: collect them
   string storageFilter = surv.getStorageFilter();

	LOG_STREAM << "StorageFilter: " << storageFilter << endl;

   std::vector<string> pathnames = fitsfiles::globVector(fitsdir + "/" + storageFilter);
   if(pathnames.size() == 0)
   {
      LOG_STREAM << "Found " << pathnames.size() << " files in " << fitsdir + "/" + storageFilter << endl;
      return;
   }


   // 3,a, set obscore mandatory fields: collection-level (the same for all files in the collection)
   const int calib_level       = surv.calibLevel;
   const string obs_collection = surv.getObsCollection();

   // generate 'INSERT INTO obscore ...' SQL-statement
   const fitsfiles::keys_by_type in_keys
   {
      ObsCoreKeys::add_str_keys(
            ObsCoreKeys::strKeys, std::set<std::string>{surv.fitskeyFacilityName, surv.fitskeyInstrumentName}),
         ObsCoreKeys::uintKeys,
         ObsCoreKeys::doubleKeys
   };
   SqlSchema_INSERT cmdInsert;
   const string access_format{obscore_access_format};

	LOG_STREAM << "FITS files found: " << to_string(pathnames.size()) << endl;

   for(unsigned int i=0; i < pathnames.size(); i++)
   {
      // do optional obscore::access_* fields
      string pathname = pathnames.at(i);
      const uintmax_t access_estsize{fitsfiles::fileSize(pathname)/1024};
      // FIXME fitsfiles::globVector() should return rel-pathnames already
      string c_pathname = pathname;
      string filename = basename((char*)c_pathname.c_str());
      string rel_pathname{surv.storagePath + "/" + filename};
      const string access_url{obscore_access_url.empty()
         ?  nullptr : obscore_access_url + "/" + rel_pathname};


      // 3,b, set obscore mandatory fields: file-dependent
      const string obs_id            = rel_pathname.substr(0, rel_pathname.find_last_of("."));
      const string obs_publisher_did = obscore_publisher + "?" + rel_pathname;

      // optional
      const string obs_title{surv.getObsTitle()};

      // 4. set optional values which are available (in header or in metadata)
      try
		{

			// FIXME add check that any of cards RESTFREQ RESTFRQ RESTWAV exist:Add only if not! 
			// current append in fname2hdrstr checks only for _exact_ match on card-key: 'xxxxx____=' ? 'yyyyy____='

			ostringstream restfrq;
			restfrq.setf(ios::fixed, ios::floatfield);
			restfrq.setf(ios::showpoint);
			restfrq << "RESTFRQ = " << setw(20) << setprecision(1) << surv.restFrequency << left << setw(50) << " / VLKB added";
			vector<string> extra_cards{restfrq.str()};

			for(size_t i=0; i<extra_cards.size(); i++) LOG_STREAM << "extra_cards["<< i<<"]: "<< extra_cards[i] << "<" << endl;

			const std::vector<fitsfiles::Hdu> all_hdu{fitsfiles::fname2hdrstr(pathname, extra_cards, max_hdupos, min_hdupos, &in_keys)};

			//LOG_STREAM << "HDU-count: " << to_string(all_hdu.size()) << endl;

			for(fitsfiles::Hdu hdu : all_hdu)
			{
				//LOG_STREAM << "HDU: " << hdu.m_header << "<" << endl;
				cmdInsert.appendRow(
						calib_level,
						obs_collection,
						obs_id,
						obs_publisher_did,
						obs_title,
						surv.survDescription,
						surv.dataproductType,
						surv.oUcd,
						access_url, access_format, access_estsize,
						hdu,
						surv.restFrequency,
						surv.authPolicy,
						groups);
			}
		}
		catch (std::exception const &e)
		{
			LOG_STREAM << "ERR " + string(__func__) + ": " << pathname << " : "<< e.what() << endl;
		}
	}

	db.dbExecCmds(cmdInsert.getINSERT());

}


void database::dbModifyGroups(int sid, const string groups,
		const string obscore_publisher,
		const string db_uri, const string db_schema)
{
	LOG_trace(__func__);

	DbConn db(db_uri, db_schema);
	Survey surv = db.querySurveyAttributes(sid);

	vector<string> cmdModGroups
	{
		"UPDATE obscore SET groups = ARRAY["+ groups +"]::TEXT[] WHERE (policy = 'PRIV') AND (obs_collection = '"+ surv.getObsCollection() +"') AND (obs_title = '"+ surv.getObsTitle()  +"')"
	};

	db.dbExecCmds(cmdModGroups);
}



// FIXME hid identifies rows to remove in tables -> no good, should be pubdid as identificator
void database::dbRemoveSurvey(int sid, const string db_uri, const string db_schema)
{
	LOG_trace(__func__);

	DbConn db(db_uri, db_schema);

	Survey surv = db.querySurveyAttributes(sid);

	SqlSchema_DELETE cmdDelete;

	vector<string> deleteRowsAllTables = {cmdDelete.getCommand(sid, surv)};

	db.dbExecCmds(deleteRowsAllTables);
}




