//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef PARMS_HPP
#define PARMS_HPP
/**
 * \file Parms.hpp
 *
 * Defines the Parms class
 */

#include <Config.hpp>

#include <Forall.hpp>
#include <Information.hpp>

#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <QString>
#include <QStringList>
#include <QTextStream>
#include <set>
#include <sstream>
#include <string>
#include <vector>

mgx_EXPORT QTextStream& operator>>(QTextStream& ss, bool b);

namespace mgx 
{
  /**
   * \class Parms Parms.hpp <Parms.hpp>
   * \brief A utility class to parse L-Studio like parameter files
   *
   * <h2>Format of the parameter file</h2>
   *
   * The basic information in the parameter file is a key associated to
   * a value, possibly with a C++-like comment:
   *
   * \code
   * key: value // Comment
   * \endcode
   *
   * Keys can be organized in sections:
   *
   * \code
   * [Section1]
   * key1: value1
   * key2: value2
   * ...
   *
   * [Section2]
   * key1: value3
   * key2: value4
   * ...
   * \endcode
   *
   * Empty line or comment-only line are ignored, any other line will raise an
   * error. The default section is named with an empty string "".
   *
   * <h2> Usage example of Parm </h2>
   *
   * Here is an example:
   *
   * \code
   * int a,b;
   * QString s1, s2;
   * std::vector<double> all_d;
   * Parms parms( "view.v", 2 );
   *
   * // First read simple parameters
   * parms( "", "a", a );
   * parms( "Main", "b", b, 0 );
   * parms( "Main", "string1", s1 );
   * parms( "Main", "string2", s2 );
   *
   * // Then read all the keys "double" in section "Values"
   * parms.all( "Values, "double", all_d);
   * \endcode
   *
   * <h2>Reading typed parameters</h2>
   *
   * To read user-defined typed as parameter, it is enough to overload the
   * function
   * \code QTextStream& operator>>( QTextStream&, const T& ). \endcode
   * \p T being the type of the parameter to read.
   *
   * <h2>Key duplicates</h2>
   *
   * If the same key is used may times in the same section, either all the
   * values can be retrieved using the Parms::all() method, or only the last
   * can be retrieved using the normal Parms::operator()().
   *
   * <h2> \anchor ParmVerbosity Verbosity</h2>
   *
   * The class accepts 5 verbosity levels:
   * - 0: No output
   * - 1: Errors only
   * - 2: Errors and warning
   * - 3: User information
   * - 4: Debug information
   *
   * Debug information output the raw string before evaluation to set up
   * a parameter.
   *
   */
  class mgx_EXPORT Parms 
	{
  public:
    /**
     * Default constructor, with no file
     */
    explicit Parms(int verboseLevel = 1);
  
    /**
     * Constructor of the parameter file.
     */
    Parms(const QString& parmFile, int verboseLevel = 1);
  
    ~Parms();
  
    /**
     * Change the verbosity level.
     *
     * \see \ref ParmVerbosity "Verbosity"
     */
    void verboseLevel(int vl) {
      VerboseLevel = (vl < 0) ? 0 : vl;
    }
  
    /**
     * Returns true if the parameter object has been correctly loaded
     */
    bool isLoaded() const {
      return loaded;
    }
  
    /**
     * Returns true if the parameter file was empty (e.g. no valid values were found)
     */
    bool empty() const {
      return Parameters.empty();
    }
  
    /**
     * This operator retrieve a single parameter.
     * \param section Section in which the parameter is looked for
     * \param key Key to look for
     * \param value Variable to set up if possible
     *
     * If the [section]key exists, \c value is set up to the parameter value.
     * Any key placed before the first section is considered in the first
     * secion. If the [section]key is multiply defined, only the last one is
     * taken and a warning is issued (see \ref ParmVerbosity "Verbosity"). If
     * the [section]key does not exist, or its content cannot be interpreted as
     * the requested type, then ar error is issued.
     *
     * \returns True if there was no error while converting the different
     * parameters from their string representation and the [section]key
     * existes.
     */
    template <typename T> bool operator()(const QString& section, const QString& key, T& value) const;
  
    /**
     * Retrieve a single parameter.
     *
     * Boolean value must read "true" or "false", case being meaningless.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, bool& value) const;
    /**
     * Retrieve a single parameter.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, int& value) const;
    /**
     * Retrieve a single parameter.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, float& value) const;
    /**
     * Retrieve a single parameter.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, double& value) const;
    /**
     * Retrieve a single parameter.
     *
     * For string, the value is the whole line with whitespaces and comment
     * stripped before and after the parameter.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, std::string& value) const;
  
    /**
     * Retrieve a single parameter.
     *
     * For string, the value is the whole line with whitespaces and comment
     * stripped before and after the parameter.
     *
     * \see operator()( const QString& section, const QString& key, T& value )
     */
    bool operator()(const QString& section, const QString& key, QString& value) const;
  
    /**
     * Variation on the previous, but if the [section]key is not found, an
     * information message is issued (instead of an error) and \c value is set
     * up to \c def.
     */
    template <typename T> bool operator()(const QString& section, const QString& key, T& value, const T& def);
  
    /**
     * This operator retrieves all parameters with same [section]key.
     * \param section Section in which the parameter is looked for
     * \param key Key to look for
     * \param value Variable to set up
     *
     * \c value is filled with the different values found having same
     * [section]key. If none, \c value is simply empty. The only error that can
     * arise is a reading error, if one parameter has invalid value. This
     * parameter will simply be ignored, all other parameters being read.
     *
     * \returns True if there was no error while converting the different
     * parameters from their string representation.
     */
    template <typename Container> bool all(const QString& section, const QString& key, Container& value);
  
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<bool>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<int>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<float>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<double>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<std::string>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, std::vector<QString>& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, const QString& key, std::vector<T>& value )
     */
    bool all(const QString& section, const QString& key, QStringList& value);
  
    /**
     * This operator retrieves all parameters with same [section].
     * \param section Section in which the parameter is looked for
     * \param value Variable to set up
     *
     * \c value is filled with the different keys found in [section] and, for
     * each key, the vector of all the values associated to it. If none, \c
     * value is simply empty. The only error that can arise is a reading error,
     * if one parameter has invalid value. This parameter will simply be
     * ignored, all other parameters being read.
     *
     * \returns True if there was no error while converting the different
     * parameters from their string representation.
     */
    template <typename T, typename Container> bool all(const QString& section, std::map<QString, Container>& values);
  
    /**
     * Retrieve a all parameters with same [section].
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<bool> >& value);
    /**
     * Retrieve a all parameters with same [section].
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<int> >& value);
    /**
     * Retrieve a all parameters with same [section].
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<float> >& value);
    /**
     * Retrieve a all parameters with same [section].
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<double> >& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<std::string> >& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, std::vector<QString> >& value);
    /**
     * Retrieve a all parameters with same [section]key.
     *
     * \see all( const QString& section, std::map<QString, std::vector<T> >& value )
     */
    bool all(const QString& section, std::map<QString, QStringList>& value);
  
  private:
    /**
     * Initialize the object
     */
    void init();
  
    /**
     * Extract a value as a string given \c section and \c key.
     *
     * \returns True if [section]key exist.
     */
    bool extractValues(const QString& section, const QString& key, QStringList& values) const;
  
    /**
     * Read a value from a string.
     *
     * If the value to read is a boolean, the input string has to be equal to
     * "true" or "false" (case insensitive).
     *
     * \returns True if the reading succeeded
     */
    bool readValue(const QString& value, bool& variable) const;
    /**
     * Read a value from a string.
     *
     * If the value to read is a string, the input one is simply copied.
     *
     * \returns True if the reading succeeded
     */
    bool readValue(const QString& value, std::string& variable) const;
    /**
     * Read a value from a string.
     *
     * If the value to read is a string, the input one is simply copied.
     *
     * \returns True if the reading succeeded
     */
    bool readValue(const QString& value, QString& variable) const;
    /**
     * Read a vector of values from a string.
     *
     * Default is to use operator>> on a \c istringstream defined on the input
     * string. Print an error is the flux operator fails.
     *
     * \returns True if the reading succeeded
     */
    template <typename T> bool readValue(const QString& value, std::vector<T>& variable) const;
    /**
     * Read a list of values from a string.
     *
     * Default is to use operator>> on a \c istringstream defined on the input
     * string. Print an error is the flux operator fails.
     *
     * \returns True if the reading succeeded
     */
    template <typename T> bool readValue(const QString& value, std::list<T>& variable) const;
    /**
     * Read a set of values from a string.
     *
     * Default is to use operator>> on a \c istringstream defined on the input
     * string. Print an error is the flux operator fails.
     *
     * \returns True if the reading succeeded
     */
    template <typename T> bool readValue(const QString& value, std::set<T>& variable) const;
    /**
     * Read a value from a string.
     *
     * Default is to use operator>> on a \c istringstream defined on the input
     * string. Print an error is the flux operator fails.
     *
     * \returns True if the reading succeeded
     */
    template <typename T> bool readValue(const QString& value, T& variable) const;
  
    template <typename T, typename InsertIterator> bool readContainer(const QString& value, InsertIterator container) const;
  
    /**
     * Name of the parameter file to read
     */
    QString ParmFileName;
  
    /**
     * Check the existence of \c key.
     *
     * \param key [Section]key written as "section:key"
     */
    bool check(QString& key) const;
  
    /**
     * Map of all the parameters in string form as read from the file. The
     * strings are already stripped from whitespaces and comments.
     */
    std::map<QString, QStringList> Parameters;
  
    /**
     * Current section while reading the parameter file
     */
    QString Section;
  
    /**
     * Current verbosity level
     */
    int VerboseLevel;
  
    /**
     * Set to True if the existence of a key output an error. Used with the
     * default value to avoid printing an error in that case.
     */
    bool CheckExist;
  
    /**
     * Make sure the parameter file is correctly loaded
     */
    bool loaded;
  };
  
  template <typename T, typename InsertIterator> bool Parms::readContainer(const QString& value, InsertIterator it) const
  {
    QString val(value);
    QTextStream iss(&val, QIODevice::ReadOnly);
    while(!iss.atEnd() and iss.status() == QTextStream::Ok) {
      T v;
      iss >> v;
      *it++ = v;
    }
    return true;
  }
  
  template <typename T> bool Parms::readValue(const QString& value, std::vector<T>& variable) const
  {
    return readContainer<T>(value, std::back_insert_iterator<std::vector<T> >(variable));
  }
  
  template <typename T> bool Parms::readValue(const QString& value, std::list<T>& variable) const
  {
    return readContainer<T>(value, std::back_insert_iterator<std::list<T> >(variable));
  }
  
  template <typename T> bool Parms::readValue(const QString& value, std::set<T>& variable) const
  {
    return readContainer<T>(value, std::insert_iterator<std::set<T> >(variable, variable.end()));
  }
  
  template <typename T> bool Parms::readValue(const QString& value, T& variable) const
  {
    QString val(value);
    QTextStream iss(&val, QIODevice::ReadOnly);
    iss >> variable;
    return iss.status() == QTextStream::Ok;
  }
  
  template <typename T> bool Parms::operator()(const QString& section, const QString& key, T& value) const
  {
    QStringList values;
    if(!extractValues(section, key, values))
      return false;
  
    if((values.size() > 1) && (VerboseLevel > 1)) {
      Information::err << "Parms::operator():Warning multiple value for key [" << section << "]" << key
                       << ", last one used.\n" << flush;
    }
  
    if(!readValue(values.back(), value)) {
      if(VerboseLevel > 0) {
        Information::err << "Parms::operator():Error getting value for key [" << section << "]" << key << " value "
                         << values.back() << "\n" << flush;
      }
      return false;
    }
    return true;
  }
  
  template <typename T> bool Parms::operator()(const QString& section, const QString& key, T& value, const T& def)
  {
    bool found = true;
    CheckExist = false;
    if(!(*this)(section, key, value)) {
      found = false;
      if(VerboseLevel > 2) {
        Information::err << "Parms::operator()::Info key [" << section << "]" << key
                         << " not found, using default value" << endl;
      }
      value = def;
    }
    CheckExist = true;
    return found;
  }
  
  template <typename Container> bool Parms::all(const QString& section, const QString& key, Container& value)
  {
    bool valid = true;
    typedef typename Container::value_type T;
    CheckExist = false;
    QStringList values;
    if(!extractValues(section, key, values))
      return false;
    value.clear();
    std::insert_iterator<Container> it(value, value.end());
    forall(const QString& val, values) {
      T single_value;
      if(readValue(val, single_value)) {
        *it++ = (const T&)single_value;
      } else {
        if(VerboseLevel > 2) {
          Information::err << "Parms::all:Error reading key [" << section << "]" << key << " with value " << val
                           << "\n" << flush;
        }
        valid = false;
      }
    }
    CheckExist = true;
    return valid;
  }
  
  template <typename T, typename Container> 
	bool Parms::all(const QString& section, std::map<QString, Container>& result)
  {
    bool valid = true;
    CheckExist = false;
    typedef std::map<QString, QStringList>::value_type value_type;
    result.clear();
    int pos = section.size() + 1;
    forall(const value_type& pair, Parameters) {
      QString sec(pair.first.mid(0, pos - 1));
      if(sec != section)
        continue;
      QString key(pair.first.mid(pos));
      Container& value = result[key];
      value.clear();
      forall(const QString& val, pair.second) {
        T single_value;
        if(readValue(val, single_value)) {
          value.push_back(single_value);
        } else {
          if(VerboseLevel > 2) {
            Information::err << "Parms::all:Error reading key [" << section << "]" << key 
              << " with value " << val << endl;
          }
          valid = false;
        }
      }
    }
    CheckExist = true;
    return valid;
  }
}
#endif
