/*** COPYRIGHT AND LICENSE STATEMENT *** 
 *
 *  This file is part of the MLMCLangevin code.
 *  
 *  (c) Copyright Eike Mueller, Grigoris Katsiolides and Tony Shardlow, University of Bath
 *  
 *  Main Developer: Eike Mueller
 *  
 *  Contributors:
 *    Grigoris Katsiolides
 *    Tony Shardlow
 *  
 *  MLMCLangevin is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  MLMCLangevin is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with MLMCLangevin (see files COPYING and COPYING.LESSER). If not,
 *  see <http://www.gnu.org/licenses/>.
 *
 *** COPYRIGHT AND LICENSE STATEMENT ***/

#include "parameters.hh"

/* ************************************************************** *
 *
 * Base class for parameters which can be read from file.
 *
 * ************************************************************** */

// Constructor
Parameters::Parameters(const std::string section) :
  section_(section), messagehandler() {
  regcomp(&regexKeyword_,
          "^ *([a-zA-Z_]+): *(#.*)?$",REG_EXTENDED);
  regcomp(&regexComment_,
          "^ *(#.*)?$",REG_EXTENDED);
  regcomp(&regexKeyValueAny_,
          "^ *([a-zA-Z0-9_]+) *= *([0-9]+|([+-]?[0-9]*\\.?[0-9]*([Ee][+-]?[0-9]+)?|(true|True|TRUE|false|False|FALSE)|[\'\"].*[\'\"])) *(#.*)?$", REG_EXTENDED);
  regcomp(&regexKeyValueInt_,
          "^ *([a-zA-Z0-9_]+) *= *([+-]?[0-9]+) *(#.*)?$", REG_EXTENDED);
  regcomp(&regexKeyValueDouble_,"^ *([a-zA-Z0-9]+) *= *([+-]?[0-9]*\\.?[0-9]*([Ee][+-]?[0-9]+)?) *(#.*)?$", REG_EXTENDED);
  regcomp(&regexKeyValueBool_,
          "^ *([a-zA-Z0-9_]+) *= *(true|True|TRUE|false|False|FALSE) *(#.*)?$", REG_EXTENDED);
  regcomp(&regexKeyValueString_,
          "^ *([a-zA-Z0-9_]+) *= *([\'\"].*[\'\"]) *(#.*)?$", REG_EXTENDED);
}

/* ************************************************************** *
 * Read from stream
 * ************************************************************** */
int Parameters::readFile(const std::string filename) {
  // reset stream
  std::ifstream is;
  is.open(filename.c_str());
  // read from stream until keyword is found
  bool lineValid;
  char line[256];
  bool parseSection = false;
  bool foundSection = false;
  // initialise found list to zero
  std::map<std::string,bool> found;
  typedef std::map<std::string,Datatype>::iterator Iterator;
  for (Iterator it = keywords.begin(); it!= keywords.end();++it) {
    found.insert(std::pair<std::string,bool>((*it).first,false));
  }
  while (is) {
    lineValid=false;
    is.getline(line,256);
    std::string sline(line);
    // Comment
    if (!regexec(&regexComment_,line,0,0,0)) {
      if (verbose > 0)
        std::cout << " >>> COMMENT: " << line << std::endl;
      lineValid = true;
    }
    // Section Keyword
    regmatch_t matchKeyword[3];
    if (!regexec(&regexKeyword_,line,3,matchKeyword,0)) {
      std::string section = sline.substr(matchKeyword[1].rm_so,
                                         matchKeyword[1].rm_eo);
      if (verbose > 0)
        std::cout << " >>> SECTION: \'" << section << "\'"<< std::endl;
      if (section == section_) {
        // Start parsing section
        parseSection = true;
        foundSection = true;
      } else {
        // Have reached a different section, stop parsing
        parseSection = false;
      }
      lineValid = true;
    }
    // Found a key-value pair
    regmatch_t matchKeyValueAny[4];
    if (!regexec(&regexKeyValueAny_,line,4,matchKeyValueAny,0) ) {
      // Check if this parameter is in the list of keywords
      std::string key = sline.substr(matchKeyValueAny[1].rm_so,
                                     matchKeyValueAny[1].rm_eo-
                                     matchKeyValueAny[1].rm_so);
      std::string value = sline.substr(matchKeyValueAny[2].rm_so,
                                       matchKeyValueAny[2].rm_eo-
                                       matchKeyValueAny[2].rm_so);
      if (verbose > 0)
        std::cout << " >>> KEY: \'" << key << "\' VALUE: \'"
                  << value << "\'" << std::endl;
      if (parseSection) {
        if (keywords.count(key) > 0) {
          if (!found[key]) {
            // Integer
            if ( (!regexec(&regexKeyValueInt_,line,0,0,0)) &&
                 (keywords[key] == Integer) ) {
              contents[key]->setValue(value);
              // Double
            } else if ( (!regexec(&regexKeyValueDouble_,line,0,0,0)) &&
                        (keywords[key] == Double) ) {
              contents[key]->setValue(value);
              // Bool
            } else if ( (!regexec(&regexKeyValueBool_,line,0,0,0)) &&
                        (keywords[key] == Bool) ) {
              contents[key]->setValue(value);
              // String
            } else if ( (!regexec(&regexKeyValueString_,line,0,0,0)) &&
                        (keywords[key] == String) ) {
              contents[key]->setValue(value);
            } else {
              // Parameter has wrong type
              std::cerr << " ERROR reading parameters: Parameter: \'"
                        << key << " = " << value
                        << "\' in section \'" << section_ << "\' "
                        << " has wrong type." << std::endl;
              return 1;
            }
            found[key] = true;
          } else {
            std::cerr << " ERROR reading parameters: Duplicate parameter: \'"
                      << key << " = " << value
                      << "\' in section \'" << section_ << "\' " << std::endl;
            return 1;
          }
          // Unexpected key-value pair in section
        } else {
          std::cerr << " ERROR reading parameters: Invalid parameter: \'"
                    << key << " = " << value
                    << "\' in section \'" << section_ << "\'" << std::endl;
          return 1;
        }
      }
      lineValid = true;
    }
    if (!lineValid) {
      std::cerr << " ERROR reading parameters: Can not parse expression: \'"
                << line
                << "\' in section \'" << section_ << "\'" << std::endl;
      return 1;
    }
  }
  // ERROR handling
  // Check whether we found the section
  if (!foundSection) {
    std::cerr << " ERROR reading parameters: Can not find section: \'"
              << section_ << "\'" << std::endl;
    return 1;
  }
  // Check whether we have found all key-value pair of this section
  typedef std::map<std::string,bool>::iterator FIterator;
  for (FIterator it = found.begin(); it!= found.end();++it) {
    if (!(*it).second) {
      std::cerr << " ERROR reading parameters: parameter \'"
                << (*it).first << "\' in section \'" << section_
                << "\' has not been specified." << std::endl;
      return 1;
    }
  }
  is.close();
  return 0;
}

void Parameters::addKey(const std::string key, const Datatype datatype) {
  keywords.insert(std::pair<std::string,Datatype>(key,datatype));
  if (datatype == Integer) {
    contents[key] = new intParameter("0");
  } else if (datatype == Double) {
    contents[key] = new doubleParameter("0.0");
  } else if (datatype == String) {
    contents[key] = new stringParameter("blank");
  } else if (datatype == Bool) {
    contents[key] = new boolParameter("false");
  }
}

/* ************************************************************** *
 * Write to stream
 * ************************************************************** */

std::ostream& operator<<(std::ostream& output, const Parameters& p) {
  output << "  " << p.section_ << " parameters :" << std::endl;
  std::map<std::string,Parameter*>::const_iterator it;
  std::map<std::string,Parameters::Datatype>::const_iterator pit;
  for (it=p.contents.begin(), pit=p.keywords.begin();
       it != p.contents.end(); ++it,++pit) {
    output << "    " << it->first << " = ";
    if (pit->second == Parameters::Integer) {
      output << (it->second)->getInt();
    } else if (pit->second == Parameters::Double) {
      output << std::scientific << (it->second)->getDouble();
    } else if (pit->second == Parameters::Bool) {
      if ((it->second)->getBool()) {
        output << "true";
      } else {
        output << "false";
      }
    } else if (pit->second == Parameters::String) {
      output << (it->second)->getString();
    }
    output << std::endl;
  }
  return output;
}
