//
// 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.
// 
// Parameter reader class
#include "Parms.hpp"

#include <algorithm>
#include <cctype>
#include <fstream>
#include "Information.hpp"
#include <iostream>
#include <iterator>
#include <map>
#include <qdir.h>
#include <sstream>

#ifdef __unix__
  #include <fcntl.h>
  #include <sys/file.h>
  #include <sys/stat.h>
#endif

namespace mgx 
{
  using Information::err;
  using std::map;
  using std::ostream_iterator;
  using std::copy;
  
  Parms::Parms(int vl) : ParmFileName()
  {
    verboseLevel(vl);
    init();
  }
  
  Parms::Parms(const QString& parmFile, int vl) : ParmFileName(parmFile)
  {
    verboseLevel(vl);
    init();
  }
  
  void Parms::init()
  {
    CheckExist = true;
    loaded = false;
    if(ParmFileName.isEmpty()) {
      loaded = true;
      return;
    }
  #ifdef __unix__
    // First, obtain the lock
    QByteArray ba = ParmFileName.toLocal8Bit();
    int fd = open(ba.data(), O_RDONLY);
    flock(fd, LOCK_SH);
  #endif
    QFile fIn(ParmFileName);
    if(!fIn.open(QIODevice::ReadOnly)) {
      if(VerboseLevel > 0)
        err << "Parms::Parms:Error opening " << ParmFileName << endl;
      return;
    }
    QTextStream ss(&fIn);
    ss.setCodec("UTF-8");
    unsigned int line = 0;
    int pos;
    QString buff;
    while(!ss.atEnd() and ss.status() == QTextStream::Ok) {
      line++;
      // read in line
      buff = ss.readLine();
      // find C++ style comments
      pos = buff.indexOf("//");
      // and remove to end of line
      if(pos != -1)
        buff = buff.mid(0, pos);
      // remove leading and trailing whitespace
      buff = buff.trimmed();
      // skip line if blank
      if(buff.length() == 0)
        continue;
      // Look for section
      if(buff[0] == '[' && buff.right(1)[0] == ']') {
        Section = buff.mid(1, buff.length() - 2);
        if((Section.length() == 0) && (VerboseLevel > 0))
          err << "Parms::Parms:Error on line " << line << ", []" << endl;
        continue;
      }
      // split key and value
      pos = buff.indexOf(":");
      // error if no : delimiter
      if(pos == -1) {
        if(VerboseLevel > 0)
          err << "Parms::Parms:Error on line " << line << ", missing :" << endl;
        continue;
      }
      // get key and value and remove leading/trailing blanks
      QString key = buff.mid(0, pos).trimmed();
      QString value = buff.mid(pos + 1).trimmed();
      // error if no key
      if(key.length() == 0) {
        if(VerboseLevel > 0)
          err << "Parms::Parms:Error on line " << line << ", missing key" << endl;
        continue;
      }
      // error if no value
      if(value.length() == 0) {
        if(VerboseLevel > 2)
          err << "Parms::Parms:Warning on line " << line << ", missing value" << endl;
      }
  
      // Now we have key and value, add to map
      Parameters[Section + ":" + key] << value;
    }
  #ifdef __unix__
    // At last, release the lock
    flock(fd, LOCK_UN);
  #endif
    loaded = true;
  }
  
  Parms::~Parms() {}
  
  // Check if parm exists
  bool Parms::check(QString& key) const
  {
    return (Parameters.find(key) != Parameters.end());
  }
  
  bool Parms::operator()(const QString& section, const QString& key, bool& value) const
  {
    return operator()<bool>(section, key, value);
  }
  
  bool Parms::operator()(const QString& section, const QString& key, int& value) const
  {
    return operator()<int>(section, key, value);
  }
  
  bool Parms::operator()(const QString& section, const QString& key, float& value) const
  {
    return operator()<float>(section, key, value);
  }
  
  bool Parms::operator()(const QString& section, const QString& key, double& value) const
  {
    return operator()<double>(section, key, value);
  }
  
  bool Parms::operator()(const QString& section, const QString& key, std::string& value) const
  {
    return operator()<std::string>(section, key, value);
  }
  
  bool Parms::operator()(const QString& section, const QString& key, QString& value) const
  {
    return operator()<QString>(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, std::vector<bool>& value)
  {
    return all<std::vector<bool> >(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, std::vector<int>& value)
  {
    return all<std::vector<int> >(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, std::vector<float>& value)
  {
    return all<std::vector<float> >(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, std::vector<double>& value)
  {
    return all<std::vector<double> >(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, std::vector<QString>& value)
  {
    return all<std::vector<QString> >(section, key, value);
  }
  
  bool Parms::all(const QString& section, const QString& key, QStringList& value)
  {
    return all<QStringList>(section, key, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, std::vector<bool> >& value)
  {
    return all<bool>(section, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, std::vector<int> >& value)
  {
    return all<int>(section, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, std::vector<float> >& value)
  {
    return all<float>(section, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, std::vector<double> >& value)
  {
    return all<double>(section, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, std::vector<QString> >& value)
  {
    return all<QString>(section, value);
  }
  
  bool Parms::all(const QString& section, std::map<QString, QStringList>& value) {
    return all<QString>(section, value);
  }
  
  bool Parms::readValue(const QString& raw_value, bool& variable) const
  {
    QString value = raw_value.toLower();
    if(value == "true") {
      variable = true;
      return true;
    } else if(value == "false") {
      variable = false;
      return true;
    }
    return false;
  }
  
  bool Parms::readValue(const QString& value, QString& variable) const
  {
    variable = value;
    return true;
  }
  
  bool Parms::readValue(const QString& value, std::string& variable) const
  {
    variable = value.toStdString();
    return true;
  }
  
  bool Parms::extractValues(const QString& section, const QString& key, QStringList& values) const
  {
    QString real_key = section + ":" + key;
    std::map<QString, QStringList>::const_iterator value = Parameters.find(real_key);

    if (value == Parameters.end()) {
      if(CheckExist && (VerboseLevel > 0))
        err << "Parms::operator():Error key not found [" << section << "]" << key << endl;
      return false;
    }

    values = value->second;
    if(VerboseLevel > 3) {
      err << "Parms::extractValues:Debug strings for key [" << section << "]" << key << ": -" << values.join("-")
          << endl;
    }
    return true;
  }
  
  QTextStream& operator>>(QTextStream& ss, bool& b)
  {
    QString val;
    ss >> val;
    val = val.toLower();
    b = (val == "true");
    return ss;
  }
}
