#include "paraq.h"

#include <algorithm>
#include <iostream>
#include <math.h>
#include <string.h>
#include <sstream>

using namespace std;

paraq::paraq(int _nprocs, const int _rank)
{
  thisjob = -1;
  nprocs = _nprocs;
  rank = _rank;
  masterdefault = 0;
  jobListInitialized = false;
}

void paraq::addRange(const string& paraid, char *range)
{
  int nargs=0;
  double goodrange[3];

  for( int pos=0; pos<strlen(range); pos++ ) 
    if( range[pos] == ':' ) nargs++;
  if(nargs==0) 
    for(int i=0; i<3; i++) goodrange[i] = strtod(range, NULL);
  else{
    goodrange[0] = strtod(strtok(range, ":"), NULL);
    for(int i=0; i<nargs; i++) goodrange[i+1] = strtod(strtok(NULL, ":"), NULL);
  }

  addRange(paraid, goodrange[0], goodrange[1], goodrange[2]);
}

string paraq::rangeString()
{
  stringstream rangestring;

  for( map< string, vector<double*> >::iterator paraIt = rangeMap.begin(); paraIt != rangeMap.end(); ++paraIt )
    if( paraIt->second.size() > 0 )
      for( vector<double*>::iterator rangeIt = paraIt->second.begin(); rangeIt != paraIt->second.end(); ++rangeIt)
	rangestring << "_" << paraIt->first << (*rangeIt)[0] << "-" << (*rangeIt)[1] << "-" << (*rangeIt)[2];
  
  for (vector<paralink>::iterator linkit = linkedParas.begin(); linkit != linkedParas.end(); ++linkit)
    rangestring << "_" << linkit->first << "--" << linkit->linktype << "--" << linkit->second;

  for( map<string,double>::iterator defIt = defaultPara.begin(); defIt != defaultPara.end(); ++defIt )
    if( rangeMap.find(defIt->first) == rangeMap.end() ) {
      bool linkedpara = false;

      for (vector<paralink>::iterator linkit = linkedParas.begin(); linkit != linkedParas.end(); ++linkit)
	if (linkit->first == defIt->first) {
	  linkedpara = true;
	  break;
	}

      if (!linkedpara)
	rangestring << "_" << defIt->first << defIt->second;
    }

  return rangestring.str();
}

void paraq::addRange(const string& paraid, double min, double max, double step)
{
  double *newrange = new double[3];
  newrange[0] = min;
  newrange[1] = max;
  newrange[2] = step;
  rangeMap[paraid].push_back( newrange );
  
  for( double paraval = min; paraval <= max+0.5*step; paraval += step+(step==0) )
    if( ! inParas(paraMap[paraid], paraval) ) paraMap[paraid].push_back( paraval );

  uniqueAllParaIdsAdd(paraid);
}

bool paraq::inParas( vector<double>& paraVec, double& tofind )
{
  for( vector<double>::iterator it = paraVec.begin(); it < paraVec.end(); ++it) 
    if( fabs(*it - tofind) < 0.0000000001 ) return true;
  return false;
}

void paraq::initJobList()
{
  if( ! jobListInitialized ) {
    map<string,double> jobZero;
    jobList.push_back( jobZero );
    
    for( map< string, vector< double > >::iterator paraIt = paraMap.begin(); paraIt != paraMap.end(); ++paraIt )
      while( jobList.begin()->find(paraIt->first) == jobList.begin()->end() ) {
	for( vector<double>::iterator valIt = paraIt->second.begin(); valIt != paraIt->second.end(); ++valIt ) {
	  jobList.push_back( *jobList.begin() );

	  jobList.back()[paraIt->first] = *valIt;

	  /* also set linked parameters */
	  for (vector<paralink>::iterator linkit = linkedParas.begin(); linkit != linkedParas.end(); ++linkit)
	    if (linkit->second == paraIt->first)
	      jobList.back()[linkit->first] = linkTypeFunction(*valIt, linkit->linktype);
	}	
	jobList.erase( jobList.begin() );
      }
    jobListInitialized = true;
  }
}

double paraq::linkTypeFunction (double linkval, int linktype) {
  switch (linktype) {

  case LINK_1TO1:
    return linkval;

  case LINK_NEGATIVE:
    return -linkval;

  }
}

int paraq::nextParas()
{
  if( thisjob == -1 ) {   
    initJobList();
    thisjob = rank;
  }
  else
    thisjob += nprocs;

  if( jobList.begin()->size() == 0 ) return 0;

  if( thisjob < jobList.size() ) return 1;
  else 
    return 0;
}

double& paraq::operator[] (string paraid) { 
  if( jobList[thisjob].find(paraid) != jobList[thisjob].end() ) {
    return jobList[thisjob][paraid];
  }

  if( defaultPara.find(paraid) != defaultPara.end() ) {
    return defaultPara[paraid];
  }

  cerr << "PARAQ: Parameter " << paraid << " does not exist!" << endl << flush;
  exit(1);
}

int paraq::getTotalJobs() { 
  initJobList();
  return jobList.size(); 
}

void paraq::uniqueAllParaIdsAdd(const string& paraid) {
  for (vector<string>::iterator allparait = allParaIds.begin(); allparait != allParaIds.end(); ++allparait)
    if ( *allparait == paraid )
      return;

  allParaIds.push_back(paraid);
}

string paraq::getParaNames() {
  stringstream paranames;
  
  for (vector<string>::iterator parait = allParaIds.begin(); parait != allParaIds.end(); ++parait)
    paranames << *parait << ":";
  
  return paranames.str();
}

string paraq::getParaVals() {
  stringstream paravals;

  for (vector<string>::iterator parait = allParaIds.begin(); parait != allParaIds.end(); ++parait) {
    if ( parait != allParaIds.begin() )
      paravals << "\t";
    paravals << (*this)[*parait];
  }

  return paravals.str();
}
