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

#ifndef GBC_OPTIONS_H
#define GBC_OPTIONS_H "$Id: Options.h 1367 2024-08-30 21:00:18Z ge $"

#include <map>
#include <deque>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <iterator>
#include "Range.h"

namespace gbc
{
template<class _Tp>
std::ostream& operator<<(std::ostream& out,const std::deque<_Tp>& dq)
  {
  std::copy(dq.begin(),dq.end(),std::ostream_iterator<typename std::deque<_Tp>::value_type>(out,","));
  return out;
  }

template<class _Tp1,class _Tp2>
std::ostream& operator<<(std::ostream& out,const std::map<_Tp1,_Tp2>& mp)
  {
  for (auto it=mp.begin(); it != mp.end(); ++it)
    out << it->first << ":" << it->second << ",";
  return out;
  }


class BaseOption;
template<class _Tp> class Option;

template<class _Tp>
inline void show(std::ostream& out, void* op)
  {static_cast<Option<_Tp>* >(op)->show(std::cout);}


/// \brief Contains the maps of options and arguments.
///
/// This class has several maps of type Option and with the arguments which
/// are read-in from command line. Most functions are static such that they can
/// be called without defining an object.
/// Typically you would have a 
/// \verbatim
///  OptionMap::arguments(argc,argv);
/// \endverbatim
/// somewhere at the beginning of your main(). This reads in the arguments from
/// the command line.
/// Then you define all objects of type Option. When done, you should have a
/// \verbatim
/// OptionMap::finished();
/// \endverbatim
/// which then assigns all arguments to their respective Options.
/// 
/// If you run your program without any options, you should get a brief desription
/// of all options available.
class OptionMap
  {
  public:
  typedef std::map<std::string,BaseOption*>   map_type;
  typedef std::deque<std::string>           arg_deque_type;
  typedef std::map<std::string,std::string> arg_map_type;

  static const int necessary=1;  ///< to be used to set an option to become necessary.
  static const int optional=0;  ///< to be used to set an option to be optional.
  static const int forbidden=-1;  ///< to be used to set an option to be optional.
  
  static std::string env_id; ///< identification for environment variables

  static size_t Max_arg_length;       ///< maximal length of arguments, dynamically updated
  static size_t Max_desc_length;      ///< maximal length of descriptions, dynamically updated
  static size_t Max_type_desc_length; ///< maximal length of type descriptions, dynamically updated

  static map_type       all_options;           ///< Holds all options
  static arg_deque_type all_arguments;         ///< All arguments which were provided
  static arg_map_type   interpreted_arguments; ///< All arguments after interpretation.
  static arg_map_type   unused_arguments;      ///< Arguments not found in all_options
  static arg_map_type   used_arguments;        ///< Arguments found in all_options
  static arg_map_type   synonymns;             ///< Equivalent names of options
  
  static bool Debug;

  static void add_option(std::string arg, BaseOption* bo);
  
  static void add_synonymn(std::string syn, std::string arg);

  /// \brief Adds arguments, typically from command-line, to the deque OptionMap::all_arguments
  static void arguments(int argc, char *argv[]);

  /// \brief Takes the argument and brakes it down into its components.
  ///
  /// From "-opt=arg" we brake down into "opt" and "arg". If "arg" has further
  /// structures, e.g. "0:1:2" we leave that for later.
  static void interpretation(std::string arg);

  /// \brief Correlated the arguments with the defined options and assigns the results.
  static void scan_arguments(void);

  static void show_options(int action=0);

  static bool provided(std::string arg);

  /// \brief Finishes reading all arguments and assigns to options.
  static void finished(bool stop_if_empty=true);

  static void check_if_all_given(bool stop_if_empty=true);

  template<class _Tp> static int type_string(void);

  template<class _Tp> static std::string type_description(void);

  template<class _Tp>
  static void add(std::string arg, Option<_Tp>* so)
    {
    add_option(arg,static_cast<BaseOption*>(so));
    if (OptionMap::type_description<_Tp>().length() > OptionMap::Max_type_desc_length)
      OptionMap::Max_type_desc_length=OptionMap::type_description<_Tp>().length();
    }
    
  //Clears the readed options, this allows for a reintepretation of arguments
  static void clear_readin(void)
    {
    all_arguments.clear();
    interpreted_arguments.clear();
    unused_arguments.clear();
    used_arguments.clear();
    }

  static void clear_all(void)
    {
    all_options.clear();
    synonymns.clear();
    clear_readin();
    }
    
  static void environment_id(std::string id)
    {
    env_id=id;
    }

  };

/// \brief Base class for all Options
/// \attention Do not use this directly, use class Option.
class BaseOption
  {
  public:
    std::string Description;  ///< Describes the purpose of this option
    std::string Command;      ///< 
    int Strictly_needed;     ///< 1: yes, 0: no, -1: forbidden
    bool Provided;            ///< true if it was provided by user.
    int Option_type;          ///< records the type of option
    bool RestrictOptions;
    
  BaseOption(void): Description(), Command(), Strictly_needed(OptionMap::optional), Provided(false), Option_type(1), RestrictOptions(false) {}

  BaseOption(std::string arg, std::string desc=std::string(), int nd=OptionMap::optional, int optp=1)
    : Description(desc), Command(arg), Strictly_needed(nd), Provided(0), Option_type(optp), RestrictOptions(false)
    {
    }
    
  inline void define(std::string arg, std::string desc=std::string(), int nd=OptionMap::optional, int optp=1)
    {
    Command=arg;
    Description=desc;
    Strictly_needed=nd;
    Option_type=optp;
    }

  inline std::string description(void) const {return Description;}

  inline bool provided(void) const {return Provided;}
  
  //Turns this optional
  //A construct line x.turn_optional(y.provided()) will turn x optional if y was provided
  inline void turn_optional(bool optional = true)
    {
    if (optional) Strictly_needed=0;
    }

  //Turns this necessary
  //A construct line x.turn_necessary(!y.provided()) will turn x optional if y was not provided
  inline void turn_necessary(bool was_necessary = true)
    {
    if (was_necessary) Strictly_needed=1;
    }

  //Turns this forbidden
  //A construct line x.turn_forbidden(!y.provided()) will turn x forbidden if y was not provided
  inline void turn_forbidden(bool was_forbidden)
    {
    if (was_forbidden) Strictly_needed=-1; //it now becomes an error if provided
    }

  //Allows restriction of options
  inline void restrict_options(void) { RestrictOptions=true; }
  
    
  };

template<class _Tp>
/// \brief Class for defining command-line options.
class Option: public BaseOption
  {
  public:
  typedef Option<_Tp>               option_type;
  typedef _Tp internal_type;
  typedef std::map<internal_type,std::string> internal_deque_type;
  
  internal_type Value, DefaultValue;
  internal_deque_type AcceptableOptions;
  bool DefaultDefined;

  Option(void): BaseOption(), Value(),  DefaultDefined(false) {}

  
 Option(std::string arg, std::string desc=std::string(), bool nd=OptionMap::optional,internal_type val=internal_type())
    : BaseOption(arg,desc,nd,OptionMap::type_string<internal_type>()), Value(val),  DefaultDefined(false)
  
    {
    OptionMap::add(arg,this);
    if (val != internal_type()) 
      {
      AcceptableOptions[val]="default";
      DefaultValue=val;
      DefaultDefined=true;
      }
    }
    
  inline void define(std::string arg, std::string desc=std::string(), bool nd=OptionMap::optional,internal_type val=internal_type())
    {
    BaseOption::define(arg,desc,nd,OptionMap::type_string<internal_type>());
    Value=val;
    OptionMap::add(arg,this);
    if (val != internal_type()) 
      {
      AcceptableOptions[val]="default";
      DefaultValue=val;
      DefaultDefined=true;
      }    
    }
    
  inline void add_restricted_option(internal_type val, std::string desc)
    {
    RestrictOptions=true;
    AcceptableOptions[val]=desc;
    }
    
  inline operator internal_type() const {return Value;}
  
  inline bool is_not_empty(void) const {return  Value != internal_type(); }
  
  inline bool check(internal_type res) {return Value == res;}

  inline bool check(internal_type res1, internal_type res2) {return (Value == res1) || (Value == res2);}

  inline bool check(internal_type res1, internal_type res2, internal_type res3) {return (Value == res1) || (Value == res2) || (Value == res3);}

  inline bool check(internal_type res1, internal_type res2, internal_type res3, internal_type res4) {return (Value == res1) || (Value == res2) || (Value == res3) || (Value == res4);}

  inline void show_internal(std::ostringstream& st) const
    {
    if (this->is_not_empty()) st << (internal_type)(*this);
    else 
      {
      switch (this->Option_type)
        {
        case 1:
        case 2:
        case 4:
              st << (internal_type)(*this);
              break;
        case 3: st << "empty string";
              break;
        case 11:
        case 12:
        case 21:
        case 22:
        case 23:
        case 331:
        case 333:
          st << "empty list";
              break;
        }
      }
    }

  inline std::ostream& show(std::ostream &out) const
    {
    out << std::right << std::setw(OptionMap::Max_arg_length)  << Command << " = ";
    
    std::ostringstream st;
    if (Strictly_needed and !Provided) st << std::string("**MISSING** "); 
    else 
      {
      show_internal(st);
      }
    if (Strictly_needed) st << std::string(" (necessary ");
    else          st << std::string(" (optional ");
    if (Provided) st << std::string("and provided)");
    else          
      {
      st << std::string("and not provided");
      if (!Strictly_needed) st << std::string(", using default values");
      st << std::string(")");
      }
    

    out << std::left << std::setw(25) << st.str() << std::endl;
    out << std::right << std::setw(OptionMap::Max_arg_length+3) << " " << std::left << std::setw(OptionMap::Max_desc_length) << Description;
    out << std::left << std::setw(OptionMap::Max_type_desc_length) << 
        std::string(" [") + OptionMap::type_description<typename Option<_Tp>::internal_type>() + std::string("]") << " ";
    
    if (AcceptableOptions.size() > 0) 
      {
      for(auto desc : AcceptableOptions)
        {
        std::string fmt=" *)";
        if (desc.first == Value)
          {
          if (Provided) 
            {
            fmt="+*)"; 
            if (desc.first == DefaultValue and DefaultDefined) fmt="+&)";
            }
          else
            {
            fmt="i*)";
            if (desc.first == DefaultValue and DefaultDefined) fmt="i&)";
            }
          }
        else
          {
          if (desc.first == DefaultValue and DefaultDefined) fmt="&)";
          }
        out << std::endl << std::right << std::setw(OptionMap::Max_arg_length+3) << fmt << Command << "=" << desc.first << " (" << std::right << desc.second << ")";
        }
      }
    
    return out;
    }

//   inline friend std::ostream& show_option(std::ostream &out, const option_type& op)
//     {
//     return op->show(out);
//     }

    
  };
  
  template<>
   inline bool  Option<Range<double> >::is_not_empty(void) const {return  Value.begin() != internal_type() || Value.end() != internal_type(); }

  template<>
   inline bool  Option<Range<int> >::is_not_empty(void)    const {return  Value.begin() != internal_type() || Value.end() != internal_type(); }

  
};
#endif
