//
//
// File: src/Defaults_Text.cc
// Purpose: A lightweight implementation of the Defaults interface
//          that uses simple text files.
// Created: Jul, 2000, sss.
//
// CMSSW File      : src/Defaults_Text.cc
// Original Author : Scott Stuart Snyder <snyder@bnl.gov> for D0
// Imported to CMSSW by Haryo Sumowidagdo <Suharyo.Sumowidagdo@cern.ch>
//

/**
    @file Defaults_Text.cc

    @brief A lightweight implementation of the Defaults
    interface that uses simple text files.  See the documentation for
    the header file Defaults_Text.h for details.

    @par Creation date:
    Jul 2000

    @author
    Scott Stuart Snyder <snyder@bnl.gov> for D0.

    Apr 2009: Haryo Sumowidagdo <Suharyo.Sumowidagdo@cern.ch>:
    Imported to CMSSW.<br>
    Oct 2009: Haryo Sumowidagdo <Suharyo.Sumowidagdo@cern.ch>:
    Added Doxygen tags for automatic generation of documentation.

    @par Terms of Usage:
    With consent from the original author (Scott Snyder).

 */

#include "TopQuarkAnalysis/TopHitFit/interface/Defaults_Text.h"
#include <cassert>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <cctype>
#include <cstring>
#include <map>

using std::abort;
using std::atof;
using std::atoi;
using std::cerr;
using std::getline;
using std::ifstream;
using std::isspace;
using std::map;
using std::strchr;
using std::string;
using std::tolower;

namespace {

  /**
    Helper function to remove comments (text starting with `;' or `#') and
    leading and trailing spaces from string <i>s</i>.
    @param s The string to strip.
    @par Return:
    The stripped string.
 */
  string strip(string s)
  //
  // Purpose: Remove comments (text starting with `;' or `#') and leading
  //          and trailing spaces from S.
  //
  // Inputs:
  //   s -           The string to strip.
  //
  // Returns:
  //  The stripped string.
  //
  {
    string::size_type j = s.find_first_of(";#");
    if (j == string::npos)
      j = s.size();

    while (j > 0 && isspace(s[j - 1]))
      --j;

    string::size_type i = 0;
    while (i < j && isspace(s[i]))
      ++i;

    return string(s, i, j - i);
  }

}  // unnamed namespace

namespace hitfit {

  //***************************************************************************

  /**
    @class Defaults_Textrep
    @brief The internal representation for a Defaults_Text object.
 */
  class Defaults_Textrep
  //
  // Purpose: The internal representation for a Defaults_Text object.
  //
  {
  public:
    // Constructor.

    /**
     @brief Constructor, construct a Defaults_Textrep instance from an
     ASCII text-file and command line arguments.
     @param file The name of the input ASCII file to read.  See the
     header file for a description of the format for this and the argument
     list. Pass an empty string to skip reading a file.
   */
    Defaults_Textrep(string file);
    /**
     @brief Constructor, construct a Defaults_Textrep instance from an
     ASCII text-file.
     @param file The name of the input ASCII file to read.  See the
     header file for a description of the format for this and the argument
     list. Pass an empty string to skip reading a file.
     @param argc The length of the argument list.
     @param argv The argument list.
   */
    Defaults_Textrep(string file, int argc, char** argv);

    // The data.  Maps from parameter names to values (which are stored
    // as strings).
    /**
     The data, maps from parameter names to values (which are stored
     as strings)
   */
    std::map<std::string, std::string> _map;

    // Look up parameter NAME and return its value.
    /**
     Look up parameter <i>name</i> and return its value.
     @param name The name of the parameter.
     @par Return:
     The value of the parameter in C++ string format.
   */
    string get_val(string name) const;

  private:
    // Read parameters from FILE and add them to our data.
    /**
     Read parameters from ASCII text file <i>file</i> and add the content
     to data.
     @param file The ASCII text file to read.
   */
    void read_file(string file);

    // Look for additional parameter settings in the argument list
    // ARGC, ARGV and add them to our data.
    /**
     Look for additional parameter settings in the argument list
     <i>argc</i> and <i>argv</i> and add the content to data.
     @param argc The length of the argument list.
     @param argv The argument list.
   */
    void process_args(int argc, char** argv);

    // Helper to process a line defining a single parameter.
    /**
     Helper function to process a line defining a single parameter.
     @param l The line to process.
   */
    void doline(string l);
  };

  Defaults_Textrep::Defaults_Textrep(string file)
  //
  // Purpose: Constructor.
  //
  // Inputs:
  //   file -        The name of the defaults file to read.
  //                 See the comments in the header for a description
  //                 of the format for this and for the argument list.
  //                 Pass an empty string to skip reading a file.
  //
  {
    read_file(file);
  }

  Defaults_Textrep::Defaults_Textrep(string file, int argc, char** argv)
  //
  // Purpose: Constructor.
  //
  // Inputs:
  //   file -        The name of the defaults file to read.
  //                 See the comments in the header for a description
  //                 of the format for this and for the argument list.
  //                 Pass an empty string to skip reading a file.
  //   argc -        The arglist length.
  //   argv -        The arglist.
  //
  {
    read_file(file);
    process_args(argc, argv);
  }

  void Defaults_Textrep::read_file(string file)
  //
  // Purpose: Read parameters from FILE and add them to our data.
  //
  // Inputs:
  //   s -           The name of the file to read.
  //
  {
    // Just return if we weren't given a file.
    if (file.empty())
      return;

    ifstream f(file.c_str());
    if (!f.good()) {
      cerr << "Can't open " << file << "\n";
      abort();
    }

    string l;
    while (getline(f, l)) {
      doline(l);
    }

    f.close();
  }

  void Defaults_Textrep::process_args(int argc, char** argv)
  //
  // Purpose: Process the argument list ARGC, ARGV and add additional
  //          parameters from it to our data (possibly overriding
  //          existing settings).  See the header file for more details.
  //
  // Inputs:
  //   argc -        The arglist length.
  //   argv -        The arglist.
  //
  {
    // Look for arguments starting with `--'.
    for (int i = 1; i < argc; i++) {
      if (argv[i][0] == '-' && argv[i][1] == '-') {
        // Found one.
        string l;
        if (strchr(argv[i], '=') != nullptr)
          // It was of the form `--NAME=VALUE'.  Change to `NAME=VALUE'.
          l = argv[i] + 2;
        else if (argv[i][2] == 'n' && argv[i][3] == 'o') {
          // It was of the form `--noNAME'.  Change to `NAME=0'.
          l = argv[i] + 4;
          l += "=0";
        } else {
          // It was of the form `--NAME'.  Change to `NAME=1'.
          l = argv[i] + 2;
          l += "=1";
        }

        // Process it like a line we read from a file.
        doline(l);
      }
    }
  }

  string Defaults_Textrep::get_val(string name) const
  //
  // Purpose: Look up parameter NAME and return its value.
  //          The parameter must exist.
  //
  // Inputs:
  //   name -        The name of the parameter.
  //
  // Returns:
  //   The value of the parameter.
  //
  {
    std::string val;

    if (_map.find(name) == _map.end()) {
      cerr << "can't find default for " << name << "\n";
      abort();
    } else {
      std::map<string, string>::const_iterator it = _map.find(name);
      val = it->second;
    }

    return val;
  }

  void Defaults_Textrep::doline(string l)
  //
  // Purpose: Helper to process a line defining a single parameter.
  //
  // Inputs:
  //   l -           The line to process.
  //
  {
    // Strip spaces from the line and ignore it if it's blank.
    l = strip(l);
    if (l.empty())
      return;

    // It must contain a `=' character.
    string::size_type pos = l.find('=');
    if (pos == string::npos) {
      cerr << "bad defaults line " << l << "\n";
      abort();
    }

    // Split off name and value parts.
    std::string name = strip(l.substr(0, pos));
    std::string val = strip(l.substr(pos + 1));

    // Add it to the map.
    _map[name] = val;
  }

  //***************************************************************************

  Defaults_Text::Defaults_Text(std::string def_file)
      //
      // Purpose: Constructor.
      //
      // Inputs:
      //   def_file -    The name of the defaults file to read.
      //                 See the comments in the header for a description
      //                 of the format for this and for the argument list.
      //                 Pass an empty string to skip reading a file.
      //
      : _rep(new Defaults_Textrep(def_file)) {}

  Defaults_Text::Defaults_Text(std::string def_file, int argc, char** argv)
      //
      // Purpose: Constructor.
      //
      // Inputs:
      //   def_file -    The name of the defaults file to read.
      //                 See the comments in the header for a description
      //                 of the format for this and for the argument list.
      //                 Pass an empty string to skip reading a file.
      //   argc -        The arglist length.
      //   argv -        The arglist.
      //
      : _rep(new Defaults_Textrep(def_file, argc, argv)) {}

  Defaults_Text::~Defaults_Text()
  //
  // Purpose: Destructor.
  //
  {
    delete _rep;
  }

  bool Defaults_Text::exists(std::string name) const
  //
  // Purpose: Test to see if parameter NAME exists.
  //
  // Inputs:
  //   name -        The name of the parameter to look up.
  //
  // Returns:
  //   True if NAME exists.
  //
  {
    std::string val;
    return (_rep->_map.find(name) != _rep->_map.end());
  }

  int Defaults_Text::get_int(std::string name) const
  //
  // Purpose: Get the value of NAME as an integer.
  //
  // Inputs:
  //   name -        The name of the parameter to look up.
  //
  // Returns:
  //   The parameter's value as an integer.
  //
  {
    return atoi(_rep->get_val(name).c_str());
  }

  double Defaults_Text::get_float(std::string name) const
  //
  // Purpose: Get the value of NAME as a float.
  //
  // Inputs:
  //   name -        The name of the parameter to look up.
  //
  // Returns:
  //   The parameter's value as a float.
  //
  {
    return atof(_rep->get_val(name).c_str());
  }

  bool Defaults_Text::get_bool(std::string name) const
  //
  // Purpose: Get the value of NAME as a bool.
  //
  // Inputs:
  //   name -        The name of the parameter to look up.
  //
  // Returns:
  //   The parameter's value as a bool.
  //
  {
    string val = _rep->get_val(name);
    if (tolower(val[0]) == 't' || tolower(val[0]) == 'y')
      return true;
    else if (tolower(val[0]) == 'f' || tolower(val[0]) == 'n')
      return false;
    return !!get_int(name);
  }

  string Defaults_Text::get_string(std::string name) const
  //
  // Purpose: Get the value of NAME as a string.
  //
  // Inputs:
  //   name -        The name of the parameter to look up.
  //
  // Returns:
  //   The parameter's value as a string.
  //
  {
    return _rep->get_val(name);
  }

  std::ostream& operator<<(std::ostream& s, const Defaults_Text& def)
  //
  // Purpose: Dump out all parameter settings.
  //
  // Inputs:
  //   s -           The stream to which we're writing.
  //   def -         The instance to dump.
  //
  // Returns:
  //   The stream S.
  //
  {
    for (std::map<std::string, std::string>::const_iterator it = def._rep->_map.begin(); it != def._rep->_map.end();
         it++) {
      s << "[" << it->first << "] = [" << it->second << "]\n";
    }

    return s;
  }

}  // namespace hitfit
