// $Id: Options.cpp 1367 2024-08-30 21:00:18Z ge $
/// \file Options.h
/// \brief contains the Options class
///
/// $Revision: 1367 $
/// \author Gerald Weber <gweberbh@gmail.com>

#include "Options.h"
#include <string>
#include <deque>
#include <iomanip>
#include <sstream>
#include <iterator>
#include <cstdlib>
#include "ErrorCodes.h"
#include<boost/algorithm/string/split.hpp>                                      
#include<boost/algorithm/string.hpp>                                            

namespace gbc
{

size_t OptionMap::Max_arg_length=0;
size_t OptionMap::Max_desc_length=0;
size_t OptionMap::Max_type_desc_length=0;
bool   OptionMap::Debug=false;
std::string OptionMap::env_id="GBC";

OptionMap::map_type OptionMap::all_options=OptionMap::map_type(); 

OptionMap::arg_deque_type OptionMap::all_arguments=OptionMap::arg_deque_type();

OptionMap::arg_map_type OptionMap::interpreted_arguments=OptionMap::arg_map_type();

OptionMap::arg_map_type OptionMap::unused_arguments=OptionMap::arg_map_type();

OptionMap::arg_map_type OptionMap::used_arguments=OptionMap::arg_map_type();

OptionMap::arg_map_type OptionMap::synonymns=OptionMap::arg_map_type();

void OptionMap::add_option(std::string arg, BaseOption* bo)
  {
  if (all_options.find(arg) == all_options.end())
    {
    all_options.insert(map_type::value_type(arg,bo));
    if (arg.length() > OptionMap::Max_arg_length) OptionMap::Max_arg_length=arg.length();
    if (bo->Description.length() > OptionMap::Max_desc_length) OptionMap::Max_desc_length=bo->Description.length();
    }
  else 
    {
    CERR_IERROR(IERRPWNAD) << "Error: Option with name " << arg <<  " already defined, ending program" << std::endl;
    CERR_TERM
    }
  }
  
//whenever syn is given it is replaced by arg, syn is never again used
void OptionMap::add_synonymn(std::string syn, std::string arg)
  {
  synonymns[syn]=arg;
  }

void OptionMap::arguments(int argc, char *argv[])
    {
    int i;
    std::string arg;
    for (i=1; i < argc; i++)
      {
      arg=argv[i]; 
      all_arguments.push_back(arg);
      interpretation(arg);
      CERR_DDEBUG(DOPT_SHEQ)  << "ARGV i=" << i << " arg=[" << arg << "]" << std::endl;
      }
      
    char* env;
    env = getenv(env_id.c_str());
    if (env != NULL)
      {
      std::string dup=std::string(env);
      COUT_INFO(INFOREDENV) << " reading options from environment variable " << env_id << "=\"" << dup << "\"" << std::endl;
      std::deque<std::string> extracted;
      boost::algorithm::split(extracted, dup, boost::is_any_of(" "),boost::token_compress_on);
      for(auto ex:extracted) 
        {
        all_arguments.push_back(ex);
        interpretation(ex);
        CERR_DDEBUG(DOPT_SHEQ)  << "ENV " << " arg=[" << ex << "]" << std::endl;
        }
      }
    } 


void OptionMap::interpretation(std::string arg)
  {
  bool interpreted=false;
  std::string opt, argument;
  std::string::size_type equal=arg.find("=");
  std::string::size_type dash =arg.find("-");
  std::string::size_type dash2=arg.find("--");
  if (dash != std::string::npos) // if we find a dash
    {
    if (equal != std::string::npos) // if we also find an equal sign
      {
      opt=arg.substr(dash+1,equal-1); // everything between the dash and the equal sign must be the name of the command
      argument=arg.substr(equal+1);   // what comes after the equal sign must the its argument
      if (argument.empty())
        {
        CERR_ERROR(ERRNAPAE) << "no arguments provided after equal sign (=)  for option \"" << opt  << "\"" << std::endl; //empty arguments after = sign is likely an error
        CERR_TERM
        }
      }
    else // if we don't find an equal sign
      {
      opt=arg.substr(dash+1); // everything after the dash is the command name
      argument=std::string(); // leave argument empty
      }
    interpreted=true;
    }
  if (interpreted)
    {
    auto syn_it = synonymns.find(opt); // check if we are not using a synonymn
    if (syn_it != synonymns.end()) 
      {
      CERR_WARN(WROPTASSYN) << "reading \"-" << opt << "\" as \"-" << syn_it->second << "\"" << std::endl; 
      opt = syn_it->second;
      }

    // insert interpreted argumento into map. ATTENTION the option/argument is not replaced, therefore only the first one is valid
    interpreted_arguments.insert(OptionMap::arg_map_type::value_type(opt,argument));
    }
  }

Option<bool>   Verbose("v","verbose (v=1 shows all options, v=0 shows only provided options)",OptionMap::optional,false);

void OptionMap::finished(bool stop_if_empty)
  {
  scan_arguments();
  check_if_all_given(stop_if_empty);
  show_options();
  }

void OptionMap::check_if_all_given(bool stop_if_empty)
  {
  if (interpreted_arguments.empty() and stop_if_empty)
    {
    show_options(1);
    CERR_ERROR(ERRNAPSLA) << "No arguments provided, showing list of available arguments and exiting" << std::endl;
    CERR_TERM
    }
    
  bool all_required_present=true;
  std::stringstream error_msg, normal_msg;
  
  for (auto opt_it : all_options)
    {
    if (!opt_it.second->Provided && opt_it.second->Strictly_needed)
      {
      error_msg << "Required option \"-" << opt_it.first << "\" missing" << std::endl;
      all_required_present=false;
      }

    if (!opt_it.second->Provided && opt_it.second->Strictly_needed==-1)
      {
      error_msg << "Option \"-" <<  opt_it.first << "\" can not be used with this combination of parameters, please check documentation" << std::endl;
      all_required_present=false;
      }
    }
      
  normal_msg << "Used: ";
  for(auto usd : used_arguments) normal_msg << "-" << usd.first << "=" << usd.second << " ";
  normal_msg << std::endl;
   
  if (not unused_arguments.empty())
    {
    normal_msg << "Unknown: ";
    for(auto usd : unused_arguments) normal_msg << "-" << usd.first << "=" << usd.second << " ";
    normal_msg << std::endl;
    }

  if (not all_required_present) 
    {
    show_options(1);
    CERR_ERROR(ERRMOCYP) << normal_msg.str();
    CERR_ERROR(ERRMOCYP) << error_msg.str();
    CERR_ERROR(ERRMOCYP) << "missing options, check your parameters or see documentation" << std::endl; 
    CERR_TERM;
    }
  else std::cout << normal_msg.str();
  }


// Set all type identification numbers
template<> int OptionMap::type_string<double>(void)           {return 1;}
template<> int OptionMap::type_string<int>(void)              {return 2;}
template<> int OptionMap::type_string<std::string>(void)      {return 3;}
template<> int OptionMap::type_string<bool>(void)             {return 4;}
template<> int OptionMap::type_string<Range<double> >(void)   {return 11;}
template<> int OptionMap::type_string<Range<int> >(void)      {return 12;}
template<> int OptionMap::type_string<std::deque<double> >(void) {return 21;}
template<> int OptionMap::type_string<std::deque<int> >(void) {return 22;}
template<> int OptionMap::type_string<std::deque<std::string> >(void) 
                                                              {return 23;}
template<> int OptionMap::type_string<std::map<std::string,double> >(void) 
                                                              {return 331;}
template<> int OptionMap::type_string<std::map<std::string,int> >(void) 
                                                              {return 332;}
template<> int OptionMap::type_string<std::map<std::string,std::string> >(void) 
                                                              {return 333;}

// Brief description about the option types
template<> std::string OptionMap::type_description<double>(void)           {return "double";}
template<> std::string OptionMap::type_description<int>(void)              {return "integer";}
template<> std::string OptionMap::type_description<std::string>(void)      {return "string";}
template<> std::string OptionMap::type_description<bool>(void)             {return "boolean (0 or 1)";}
template<> std::string OptionMap::type_description<Range<double> >(void)   {return "Range (e.g. 0.1:5.2/10)";}
template<> std::string OptionMap::type_description<Range<int> >(void)      {return "Range (e.g. 0:10/2 integers only)";}
template<> std::string OptionMap::type_description<std::deque<double> >(void) {return "List of floating point numbers";}
template<> std::string OptionMap::type_description<std::deque<int> >(void) {return "List of integers";}
template<> std::string OptionMap::type_description<std::deque<std::string> >(void) {return "List of strings";}
template<> std::string OptionMap::type_description<std::map<std::string,double> >(void) 
                                                                           {return "Map of strings->double";}
template<> std::string OptionMap::type_description<std::map<std::string,int> >(void) 
                                                                           {return "Map of strings->int";}
template<> std::string OptionMap::type_description<std::map<std::string,std::string> >(void) 
                                                                           {return "Map of strings->strings";}

void OptionMap::show_options(int action)
  {
  OptionMap::map_type::iterator            opt_it;
  for (opt_it=all_options.begin(); opt_it != all_options.end(); opt_it++)
    {
    if ((opt_it->second->Provided || Verbose) || action)
    {
    switch (opt_it->second->Option_type)
      {
      case 1: show<double>(std::cout,opt_it->second);
              break;
      case 2: show<int>(std::cout,opt_it->second);
              break;
      case 3: show<std::string>(std::cout,opt_it->second);
              break;
      case 4: show<bool>(std::cout,opt_it->second);
              break;
      case 11:show<Range<double> >(std::cout,opt_it->second);
              break;
      case 12:show<Range<int> >(std::cout,opt_it->second);
              break;
      case 21:show<std::deque<double> >(std::cout,opt_it->second);
              break;
      case 22:show<std::deque<int> >(std::cout,opt_it->second);
              break;
      case 23:show<std::deque<std::string> >(std::cout,opt_it->second);
              break;
      case 331:show<std::map<std::string,double> >(std::cout,opt_it->second);
              break;
      case 332:show<std::map<std::string,int> >(std::cout,opt_it->second);
              break;
      case 333:show<std::map<std::string,std::string> >(std::cout,opt_it->second);
              break;
      }
      std::cout << std::endl;
    }}
  }

bool OptionMap::provided(std::string arg)
  {
  OptionMap::map_type::iterator            opt_it;
  opt_it=all_options.find(arg);
  if (opt_it != all_options.end()) return opt_it->second->Provided;
  else return false;
  }

template<class _Tp>
inline void interp(std::stringstream& st,  void* op)
  {interp(st,static_cast<Option<_Tp>* >(op));}

template<class _Tp>
inline void interp(std::stringstream& st, Option<_Tp>* op)
  {
  st >> op->Value;
  if (op->RestrictOptions)
    {
    if (op->AcceptableOptions.find(op->Value) == op->AcceptableOptions.end())
      {
      CERR_ERROR(ERRAGNAFO) << "argument given \"" << op->Value << "\" not acceptable for option \"" << op->Command << "\"" << std::endl;
      CERR << "Acceptable argument for \"" << op->Command << "\" are:" << std::endl;
      for(auto desc : op->AcceptableOptions) CERR << desc.first << " : " << desc.second << std::endl;
      CERR_TERM
      }
    } 
  }
  
template<>
inline void interp(std::stringstream& st, Option<std::string>* op)
  {
  std::string readin;  
  st >> readin;
    
  if (op->RestrictOptions)
    {
    if (op->AcceptableOptions.find(readin) != op->AcceptableOptions.end()) op->Value=readin;
    else
      {
      CERR_ERROR(ERRAGNAFO) << "argument given \"" << readin << "\" not acceptable for option \"" << op->Command << "\"" << std::endl
                            << "Acceptable argument for \"" << op->Command << "\" are:" << std::endl;
      for(auto desc : op->AcceptableOptions) CERR << desc.first << " : " << desc.second << std::endl;
      CERR_TERM
      }
    }
  else op->Value=readin;  
  }


template<class _Tp>
inline void interp(std::stringstream& st,  Option<Range<_Tp> >* rg)
  {
  rg->Value.interpret_string(st.str());
  }

template<class _Tp>
inline void interp(std::stringstream& st, Option<std::deque<_Tp> >* dq)
  {
  std::string arg;
  st >> arg;
  std::string::size_type comma_i=0,comma_f=arg.find(',');
  if (comma_f == std::string::npos) comma_f=arg.size();
  _Tp value;
  while (comma_i < comma_f)
    {
    std::string field=arg.substr(comma_i,comma_f-comma_i);
    std::stringstream value_field;
    value_field << field;

    value_field >> value;
    dq->Value.push_back(value);

    comma_i=comma_f+1;
    comma_f=arg.find(',',comma_i);
    if (comma_f == std::string::npos) comma_f=arg.size();
    }
  }

template<class _Tp1,class _Tp2>
inline void interp(std::stringstream& st, Option<std::map<_Tp1,_Tp2> >* mp)
  {
  std::string arg;
  st >> arg;
  std::string::size_type comma_i=0,comma_f=arg.find(',');
  if (comma_f == std::string::npos) comma_f=arg.size();
  _Tp1 key; _Tp2 value;
  while (comma_i < comma_f)
    {
    std::string field=arg.substr(comma_i,comma_f-comma_i);
    std::string::size_type pos=field.find(':');
    std::stringstream key_field,value_field;
    key_field   << field.substr(0,pos);
    value_field << field.substr(pos+1);

    key_field >> key;
    value_field >> value;
    mp->Value[key]=value;

    comma_i=comma_f+1;
    comma_f=arg.find(',',comma_i);
    if (comma_f == std::string::npos) comma_f=arg.size();
    }
  }


void OptionMap::scan_arguments(void)
  {
  for (auto arg_it : interpreted_arguments)
    {
    auto opt_it = all_options.find(arg_it.first);
    if (opt_it != all_options.end())
      {
      std::stringstream st(arg_it.second); // assign the argument, usually what came after the equal sign
      Option<bool>* so_bool;

      switch (opt_it->second->Option_type)
        {
        case 1: interp<double>(st,opt_it->second);
                break;

        case 2: interp<int>(st,opt_it->second);
                break;

        case 3: interp<std::string>(st,opt_it->second);
                break;

        case 4: so_bool=static_cast<Option<bool>* >(opt_it->second);
                if (arg_it.second != std::string()) st >> so_bool->Value;
                else so_bool->Value=true;
                break;

        case 11: interp<Range<double> >(st,opt_it->second);
                 break;

        case 12: interp<Range<int> >(st,opt_it->second);
                 break;

        case 21: interp<std::deque<double> >(st,opt_it->second);
                 break;

        case 22: interp<std::deque<int> >(st,opt_it->second);
                 break;

        case 23: interp<std::deque<std::string> >(st,opt_it->second);
                 break;

        case 331: interp<std::map<std::string,double> >(st,opt_it->second);
                  break;

        case 332: interp<std::map<std::string,int> >(st,opt_it->second);
                  break;

        case 333: interp<std::map<std::string,std::string> >(st,opt_it->second);
                  break;
        }
      opt_it->second->Provided=true;
      used_arguments.insert(arg_it);
      }
    else {unused_arguments.insert(arg_it);}
    }
  }

};
