
#include "database.hpp"
#include "fitsfiles.hpp"
#include "config.hpp"
#include "io.hpp"

#include <iostream>
#include <sstream>

#include <assert.h>


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


using namespace std;

namespace vlkb
{
   const int EXIT_WITH_USAGE = -1000;
   const int EXIT_WITH_ERROR = -2000;

   config conf;

   void usage(const std::string progname)
   {
      std::cout 
         << "Usage: " << progname << " <conf_filename> <command> [cmd-options] [cmd-args]" << endl
         << "\n where conf_filename is typically /etc/" << progname << "/datasets.conf\n "
         << "\n and where commands are:\n "
         << "\n\t dbfiles dbinit dbcheck dblist dbbounds dbadd dbmodgroups dbremove\n"
         << std::endl
         << "Version: " << VERSIONSTR << " " << BUILD << std::endl;
   }



   // program recognizes following commands

   enum cmd_set {dbinit, dbcheck, dblist, dbfiles, dbbounds, dbaddsurvey, dbmodgroups, dbremovesurvey};


   // from bash receive args as strings

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

      if(cmdstr.compare("dbinit") == 0)       cmd = dbinit;
      else if(cmdstr.compare("dbcheck") == 0)      cmd = dbcheck;
      else if(cmdstr.compare("dblist") == 0)       cmd = dblist;
      else if(cmdstr.compare("dbfiles") == 0)      cmd = dbfiles;
      else if(cmdstr.compare("dbbounds") == 0)     cmd = dbbounds;
      else if(cmdstr.compare("dbadd") == 0)        cmd = dbaddsurvey;
      else if(cmdstr.compare("dbmodgroups") == 0)  cmd = dbmodgroups;
      else if(cmdstr.compare("dbremove") == 0)     cmd = dbremovesurvey;
      else
      {
         stringstream cmd_err;
         cmd_err << "String \'" << cmdstr<< "\' not recognized as valid command." << endl;
         throw(invalid_argument(cmd_err.str()));
      }

      return cmd;
   }

} // namespce


const bool WITH_PASSWORD = true;

std::string base_name(std::string path)
{
   return path.substr(path.find_last_of("//") + 1);
   // FIXME replace with basename
}



//-----------------------------------------------------------
// 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_dbInit(int argc, char * argv[])
{
   int rc;
   try
   {
      if(argc!=2)
      {
         std::cout << "Usage: dbinit <surveys.csv>" << endl;
         return vlkb::EXIT_WITH_USAGE;
      }

      const string csv_filename{argv[1]};

      OUT_STREAM << "DB: " << vlkb::conf.getDbUri() << " schema["<< vlkb::conf.getDbSchema() << "]" << endl;
      OUT_STREAM << "This will delete all data in database, confirm with 'yes':" << endl;
      string answer;
      std::cin >> answer;
      if(answer.compare("yes") == 0) 
      {
         database::dbInit(vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema(), csv_filename);
         rc = 0;
      }
      else
      {
         rc = vlkb::EXIT_WITH_ERROR;
      }
   }
   catch(exception& e)
   {
      rc = vlkb::EXIT_WITH_ERROR;
      ERROR_STREAM << e.what() << endl;
   }
   return rc;
}



int cmd_dbCheck(void)
{
   int rc;
   try
   {
      vector<string> db_rows{database::dbCheck(vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema())};
      for(string one_row : db_rows) cout << one_row << endl;
      rc = 0;
   }
   catch(exception& e)
   {
      rc = vlkb::EXIT_WITH_ERROR;
      ERROR_STREAM << e.what() << endl;
   }
   return rc;
}



int cmd_dbList(void)
{
   int rc;
   try
   {
      vector<string> res_str = database::dbListSurveys(vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema());
      rc = 0;
      for(string res_row : res_str)
         cout << res_row << endl;
   }
   catch(exception& e)
   {
      rc = vlkb::EXIT_WITH_ERROR;
      ERROR_STREAM << e.what() << endl;
   }
   return rc;
}



int cmd_dbBounds(int argc, char * argv[])
{
   int rc;
   switch(argc)
   {
      case 2:
         try
         {
            database::dbSurveyBounds(/*stoi(argv[1]),*/ vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema());
            rc = 0;
         }
         catch(exception& e)
         {
            rc = vlkb::EXIT_WITH_ERROR;
            ERROR_STREAM << e.what() << endl;
         }
         break;

      default:
         cout << "Usage: dbbounds <SID>" << endl
            << "prints min-max bounds" << endl;
         rc = vlkb::EXIT_WITH_USAGE;
   }
   return rc;
}



int cmd_dbFiles(int argc, char * argv[])
{
   int rc;
   switch(argc)
   {
      case 2:
         try
         {
            string row = database::dbListFiles(std::stoi(argv[1]), vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema(), vlkb::conf.getFitsDir());
            rc = 0;
            cout << row << endl;
         }
         catch(exception& e)
         {
            rc = vlkb::EXIT_WITH_ERROR;
            ERROR_STREAM << e.what() << endl;
         }
         break;

      case 3:
         try
         {
            for(int i=std::stoi(argv[1]); i<=std::stoi(argv[2]); i++)
               cout << 
                  database::dbListFiles(i, vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema(), vlkb::conf.getFitsDir())
                  << endl;
            rc = 0;
         }
         catch(exception& e)
         {
            rc = vlkb::EXIT_WITH_ERROR;
            ERROR_STREAM << e.what() << endl;
         }
         break;

      default:
         cout << "Usage: dbfiles <SID-from> [SID-to]" << endl
            << "prints file-count for given survey(s)" << endl;
         rc = vlkb::EXIT_WITH_USAGE;
   }
   return rc;
}



int cmd_dbAdd(int argc, char * argv[])
{
   const int max_hdupos = 1; // FIXME add as param to the command
   const string groups = ""; // NOTE: uses separate cmd 'dbmodgroups' to add groups to enable access to PRIVATE surveys
   int sid_from, sid_to;
   int rc = 0;

   switch(argc)
   {
      case 2:
         sid_from = std::stoi(argv[1]);
         sid_to   = sid_from;
         break;

      case 3:
         sid_from = std::stoi(argv[1]);
         sid_to   = std::stoi(argv[2]);
         break;

      default:
         cout << "Usage: dbadd <SID-from> [SID-to]" << endl
            << "inserts survey(s) metadata to database" << endl;
         rc = vlkb::EXIT_WITH_USAGE;
   }

   OUT_STREAM << "This will add all data into database, (even if already exists!), confirm with 'yes':" << endl;
   string answer;
   std::cin >> answer;
   if(answer.compare("yes") != 0) return vlkb::EXIT_WITH_ERROR; 

   for(int i=sid_from; i<=sid_to; i++)
   {
      try
      {
         database::dbAddSurvey(i, groups,
               vlkb::conf.getObsCorePublisher(), vlkb::conf.getObsCoreAccessFormat(), vlkb::conf.getObscoreAccessUrl(),
               vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema(), vlkb::conf.getFitsDir(), max_hdupos);
      }
      catch(exception& e)
      {
         rc = vlkb::EXIT_WITH_ERROR;
         ERROR_STREAM << "SID["<< i << "]: " << e.what() << endl;
      }
   }

   return rc;
}



int cmd_dbModGroups(int argc, char * argv[])
{
   // FIXME groups should be cmd arg and mandatory of policy PRIVATE and empy null if policu PUBLIC
   // e.g. exit with error if dbAddSurvey gets sid of PRIVATE but no groups arg supplied
   string groups;
   int sid_from, sid_to;
   int rc;
   switch(argc)
   {
      case 3:
         groups   = std::string(argv[1]);
         sid_from = std::stoi(argv[2]);
         sid_to   = sid_from;
         break;

      case 4:
         groups   = std::string(argv[1]);
         sid_from = std::stoi(argv[2]);
         sid_to   = std::stoi(argv[3]);
         break;

      default:
         cout << "Usage: dbmodgroups <groups> <SID-from> [SID-to]" << endl
            << "modifies list of groups which can access PRIVATE surveys" << endl;
         rc = vlkb::EXIT_WITH_USAGE;
   }

   try
   {
      for(int i=sid_from; i<=sid_to; i++)
         database::dbModifyGroups(i, groups,
               vlkb::conf.getObsCorePublisher(), vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema());
      rc = 0;
   }
   catch(exception& e)
   {
      rc = vlkb::EXIT_WITH_ERROR;
      ERROR_STREAM << e.what() << endl;
   }

   return rc;
}



int cmd_dbRemove(int argc, char * argv[])
{
   int rc;
   switch(argc)
   {
      case 2:
         try
         {
            database::dbRemoveSurvey(stoi(argv[1]), vlkb::conf.getDbUri(WITH_PASSWORD), vlkb::conf.getDbSchema());
            rc = 0;
         }
         catch(exception& e)
         {
            rc = vlkb::EXIT_WITH_ERROR;
            ERROR_STREAM << e.what() << endl;
         }
         break;

      default:
         cout << "Usage: dbremove <SID>" << endl
            << "removes survey metadata from database" << endl;
         rc = vlkb::EXIT_WITH_USAGE;
   }
   return rc;
}



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

   if( argc < 3 )
   {
      vlkb::usage(progname);
      return vlkb::EXIT_WITH_USAGE;
   }

   const string conf_filename(argv[1]);

   // recognize command from string
   const vlkb::cmd_set cmd(vlkb::to_cmd(argv[2]));

   // sub-command arguments (skips: vlkb <conf-filename>)
   int cmd_argc = argc - 2;
   char ** cmd_argv = &(argv[2]);

   vlkb::conf.read_config(conf_filename);
/*
   cout << "LOG DIR: " << vlkb::conf.getLogDir() << endl;
   cout << "FITSDIR: " << vlkb::conf.getFitsDir() << endl;
   cout << "DB[" << vlkb::conf.getDbSchema() << "]: " <<vlkb::conf.getDbUri() << endl;
   cout << endl; 
*/
   LOG_open(vlkb::conf.getLogDir(), vlkb::conf.getLogFileName());
   int rc;

   try
   {
      switch(cmd)
      {
         case vlkb::dbinit:         rc = cmd_dbInit(cmd_argc,cmd_argv); break;
         case vlkb::dbcheck:        rc = cmd_dbCheck(); break;
         case vlkb::dblist:         rc = cmd_dbList(); break;
         case vlkb::dbbounds:       rc = cmd_dbBounds(cmd_argc,cmd_argv); break;
         case vlkb::dbfiles:        rc = cmd_dbFiles(cmd_argc,cmd_argv); break;
         case vlkb::dbaddsurvey:    rc = cmd_dbAdd(cmd_argc,cmd_argv); break;
         case vlkb::dbmodgroups:    rc = cmd_dbModGroups(cmd_argc,cmd_argv); break;
         case vlkb::dbremovesurvey: rc = cmd_dbRemove(cmd_argc,cmd_argv); break;

         default: assert(false);
      }
   }
   catch(const invalid_argument& ex)
   {
      cout << ex.what() << endl;
   }
   catch(const exception& ex)
   {
      cerr <<  ex.what() << endl;
   }

   LOG_close();
   return rc;
}

