#include "paramParser.h"
#include <fstream>
#include <stdlib.h>
#include <cstring>

using namespace std;

// #define __deb

paramParser::paramParser() {
  init();	
}

paramParser::~paramParser()
{
  if (paramList != NULL) delete paramList;
  if (parseList != NULL) delete parseList;
}

void paramParser::init() {
  pre = "-";
  paramList = new tParamObjList();
  parseList = new tParseObjList();
  searchIt = parseList->begin();  
  header = "";
  ctag = "#";
  fileError = "";
}

// -----------------------------------------------

void paramParser::clearParseList() {
  if (parseList != NULL) parseList->clear();
}

void paramParser::setParamChar(string c) {
  // this functions redefines the character parameters have to start wit (default '-')
  string tmp = c;
  if ((tmp != "") && (tmp.length() == 1)) {
    pre = tmp;
  }
}

void paramParser::setFileHeaderLine(string h) {
  /* If you want the configuration/ parameter files to start with a specific string (=header),
   * you can set this string via this function.
   */
  header = h;
}

void paramParser::setCommentTag(string t) {
  // t="" means that there will be no comment tag!
  ctag = t;
}

void paramParser::addParam(string s1) {
  addParam(s1, "", 0, 0);
}

void paramParser::addParam(string s1, int m) {
  addParam(s1, "", m, m);
}

void paramParser::addParam(string s1, int min, int max) {
  addParam(s1, "", min, max);
}

void paramParser::addParam(string s1, string s2) {
  addParam(s1, s2, 0, 0);
}

void paramParser::addParam(string s1, string s2, int m) {
  addParam(s1, s2, m, m);
}

void paramParser::addParam(string s1, string s2, int min, int max) {
  // add a new paramater. We first check, whether one of the two keys already exists!
  if (s1.substr(0,1) != pre) s1 = pre + s1;
  tParamObjListIt iter = paramList->find(s1);
  if( iter != paramList->end() ) {
    throw("First paramater already assigned!");
    return;
  }
  if (s2 != "") {
    if (s2.substr(0,1) != pre) s2 = pre + s2;
    iter = paramList->find(s2);
    if( iter != paramList->end() ) {
      throw("Second paramater already assigned!");
      return;
    }
  }
  
  paramObj* tmp = new paramObj(s1, s2, min, max);
  paramList->insert(paramList->begin(), pair<string, paramObj*>(s1, tmp));
  //if (s2 != "") (*paramList)[s2] = tmp;
  if (s2 != "") paramList->insert(paramList->begin(), pair<string, paramObj*>(s2, tmp));
}

void paramParser::restartParsing() {
  // resets the traversing iterator to the start of the parseObj list
  travIt = parseList->begin();
}

void paramParser::resetParser() {
  travIt = parseList->begin();
  while (travIt != parseList->end()) {
    (*travIt)->setProcessable(true);
    travIt++;
  }
  travIt = parseList->begin();
}

parseObj* paramParser::getOption() {
  if (parseList->size() > 0)
    return (*travIt);
  return NULL;
}

parseObj* paramParser::getNextOption() {
  if (parseList->size() <= 0) return NULL;
  travIt++;
  if (travIt == parseList->end()) return NULL; 
  return (*travIt);
}

/*
void paramParser::addParseObject(paramObj* p) {
  if (p != NULL) {
    parseObj *tmp = new parseObj(p);
    tmp->setValid();
    p->inst++;   // increase number of instances
    parseList->push_back(tmp);
  }
}
*/

void paramParser::addCreatedParseObject(parseObj *p) {
  if ( (p != NULL) && (p->data != NULL)) {
    p->data->inst++;
    p->setValid();
    parseList->push_back(p);
  }
}

paramObj* paramParser::findParameter(string s1, string s2) {
  // search parameter list for entry. If none of s1, s2 is defined,
  // NULL is returned. Otherwise, the option(s) s1, s2 have been defined.
  if (s1 == "") return NULL;
  tParamObjListIt localIt;
	
  localIt = paramList->find(s1);
  if ( localIt != paramList->end() )
    return (*localIt).second;
  if (s2 == "") return NULL;
  localIt = paramList->find(s2);
  if ( localIt != paramList->end() ) 
    return (*localIt).second;
  return NULL;
}

parseObj* paramParser::findOption(string s1, string s2) {
  if (s1 == "") return NULL;
  paramObj *po;
  tParseObjListIt localIt;
  
  if (searchIt == parseList->end()) {
    // start new search if we have reached the end of the options list
    searchIt = parseList->begin();
  } else {
    // continue a previous search
    searchIt++;
  }
  while (searchIt != parseList->end()) {
    po = (*searchIt)->data;
    if (po != NULL) {
      if ((po->getParamName1() == s1) || (po->getParamName2() == s1))
        return (*searchIt);
      if (s2 == "") return NULL;
      if ((po->getParamName1() == s2) || (po->getParamName2() == s2))
	return (*searchIt);
    } else {
      return NULL;
    }
    searchIt++;
  }
  return NULL;
}

void paramParser::parseCommandLine(int argc, char* argv[])
{
  string tmp;
  paramObj* po;
  parseObj* pa;
  bool testvalid;
	
  // new parameters have to begin with a '-' character!
  int i=1;
  while (i < argc) {
    tmp = argv[i];
    // check for parameter headder character
    if (tmp.substr(0, 1) == pre) {
      if ((po = findParameter(tmp, "")) != NULL) {
	// mark paramObject as used (not parseObject!!!)
	po->setUsed();
        // end of argv reached?
        if (i >= argc) return;
        i++;
        if (i < argc) {
          tmp = argv[i];
          /* if, e.g., the leading character for parameters is "-", we might nevertheless have a value
           * which is a negative number!
           */
          pa = new parseObj(po);
	  pa->setInvalid();
          while ( (( i < argc ) && ( (tmp.substr(0, 1) != pre))) ||
		  ((tmp.substr(0, 1) == pre) && (findParameter(tmp, "") == NULL) && (i < argc)) ) {
            pa->addValue(tmp);
            i++;
            if (i < argc) {
              tmp = argv[i];
            }
          }
          // is the number of values valid, is it within the given min and max numbers?
          testvalid = (pa->size() >= pa->getMinNumberOfValues()) && (pa->size() <= pa->getMaxNumberOfValues());
          pa->setValidValues(testvalid);
          if ((!testvalid) && (!verbose)) {
	    printf("Invalid number of values for parameter %s. Must be between %d and %d, not %d\n", pa->getParamName1().c_str(),
		   po->getMinNumberOfValues(), po->getMaxNumberOfValues(), pa->size());
	  } // else {
	  addCreatedParseObject(pa);
#ifdef __deb
	  printf("deb ");
	  pa->show();
#endif
	  //}
        } else {
          pa = new parseObj(po);
          testvalid = (pa->size() >= pa->getMinNumberOfValues()) && (pa->size() <= pa->getMaxNumberOfValues());
          pa->setValidValues(testvalid);
	  if ((!testvalid) && (!verbose)) {
	    printf("Invalid number of values for parameter %s. Must be between %d and %d, not %d!\n", pa->getParamName1().c_str(),
		   po->getMinNumberOfValues(), po->getMaxNumberOfValues(), pa->size());
	  }// else {
	  addCreatedParseObject(pa);
#ifdef __deb
	  printf("deb ");
	  pa->show();
#endif
	  //}
        }
      } else {
        if (!verbose) printf("Unknown parameter %s. Parameter and value(s) ignored!\n", tmp.c_str());
	i++;
      }
    } else {
      i++;
    }
  }
  restartParsing();
}

void paramParser::parseFile(string file, parseObj *pa) {
  string line;
  int l=-1;
  vector<string> tokens;
  
  fstream fle(file.c_str());
  if (fle) {
    fileError = "Error in parameter file " + file + ": ";
    if (!fle.eof()) getline(fle, line);
    line=line.substr(0, line.find("\n")-1);  // chomp final \n
    if ((line == header) && (!fle.eof())) getline(fle, line);
    while (!fle.eof()) {
      line=line.substr(0, line.find("\n")-1);  // chomp final \n
      l=line.find("\t");
      while (l != string::npos ) {
	line.replace(l, 1, " ");
	l=line.find("\t");
      }
      l = line.find("  ");
      while (l != string::npos) {
	line.replace(l, 2, " ");
	l = line.find("  ");
      }
      // cut leading spaces
      l = line.find(" ");
      while (l == 0) {
	line.replace(l, 1, "");
	l = line.find(" ");
      }		
      if (ctag != "") {
	l = line.find(ctag);
	if (l != string::npos) line = line.substr(0, l);    // cut comments
      }
      
      // finally: split spring by spaces
      int oldpos=0;
      string token;
      l = line.find(" ");
      while (l != string::npos) {
	token = line.substr(oldpos, l-oldpos);
	tokens.push_back(token);
	oldpos = l+1;
	l = line.find(" ", oldpos);
      }
      token = line.substr(oldpos, line.length()-oldpos);
      if (token.length() > 0) tokens.push_back(token);
      getline(fle, line);
    }
    const int ns = tokens.size()+1;
    char* largv[ns];
    largv[0] = new char[2];
    for (int i=0; i < tokens.size(); i++) {
      largv[i+1] = new char[tokens[i].length()];
      largv[i+1] = (char*)tokens[i].c_str();
      //      printf("%s<-\n", largv[i+1]);
    }
    //resetParser();
    parseCommandLine(tokens.size()+1, largv);
    fileError = "";
  }
  restartParsing();
  fle.close();
}
