#include <iostream>
#include <resultset.h>
#include "table.h"
#include "combo.h"
#include <string>
#include <vector>
#include <stdlib.h> // For random and srandom

using namespace std;

void getConstraints(int,MYSQL,vector<vector<int> >&,vector<vector<int> >&);
void getStatus(int,MYSQL,vector<int>&,vector<int>&,vector<int>&,vector<int>&,vector<int>&);
bool getSAMS(MYSQL,vector<int>&,int&);
int truerand(int);
void constrain(Combo &combo,const vector<vector<int> >& triggers,const vector<vector<int> >& constraints);
void makeTable(string,ResultSet&);
Combo makeFacilitiesCombo(int size,vector<vector<int> > triggers,vector<vector<int> > constraints,vector<int> a,vector<int> selectable);
int needsSAMS(Combo combo,vector<int> needssams,int hassams);

int main(int argc,char** argv){
  string host,user,pass,data,output;
  // Parse argument list
  if(argc<2){cout<<"Usage: "<<argv[0]<<" <idx> [<host> <user> <pass> <db> <outputdb>]\n";return 0;}
  int idx=atoi(argv[1]);
  if(argc==7)
    {
      host=argv[2];
      user=argv[3];
      pass=argv[4];
      data=argv[5];
      output=argv[6];
    }
  else
    {
      host="127.0.0.1";
      user="root";
      pass="blah";
      data="plum_data";
      output="plum_output";      
    }

  // Connect to the databases
  MYSQL conn=ResultSet::makeConn(host,user,pass,data);
  MYSQL outputconn=ResultSet::makeConn(host,user,pass,output);

  // Get constraint data for this run from database
  vector<vector<int> > triggers,constraints;
  getConstraints(idx,conn,triggers,constraints);

  // Get rack selection status data
  vector<int> r,s,a,o,k,selectable;
  getStatus(idx,conn,r,s,a,o,k);
  for(int n=0;n!=r.size();n++)
    selectable.push_back(r[n]);
  for(int n=0;n!=s.size();n++)
    selectable.push_back(s[n]);

  Combo::init(conn);

  // Get a few run parameters out of the database
  int min,max,numcombos;
  string tblname;
  bool sams;
  ResultSet runparams(&conn);
  runparams.query<<"select min,max,tblname,numcombos,newsams from runs where idx="<<idx;
  runparams.store();
  if(runparams.next())
    {
      min=(int)runparams["min"];
      max=(int)runparams["max"];
      numcombos=(int)runparams["numcombos"];
      tblname=(string)runparams["tblname"];
      sams=(runparams["newsams"]!=0);
    }

  // Get SAMS data
  vector<int> needssams;int hassams;
  if(sams)
    if(!getSAMS(conn,needssams,hassams))
      {
	cout<<"Problem in the SAMS table, which is needed for this run.\n";
	exit(1);
      }

  // Error trapping
  if(min>max)
    {cout<<"Minimum number of racks is greater than maximum.\n";exit(1);}
  if(min<a.size())
    {cout<<"Minimum number of racks is set too low for number of always-selected.\n";exit(1);}
  if(max>selectable.size()+a.size())
    {
      cout<<"Maximum number of racks is set too high for number of selectable: "
	  <<max<<" vs. "
	  <<(selectable.size()+a.size())<<'\n';
      exit(1);
    }

  // Create table to put combos in.
  ResultSet outputdb(&outputconn); // Make a way to talk to the output db
  makeTable(tblname,outputdb);  

  // Add sum fields
  ResultSet sums(&conn);
  sums.query<<"select outputfield from sums";
  sums.store();
  if(sums.size()!=0)
    while(sums.next())
      {
	outputdb.query<<"alter table "<<tblname<<" add "<<sums["outputfield"]<<"_ttl double";
	outputdb.store();
	outputdb.query<<"alter table "<<tblname<<" add "<<sums["outputfield"]<<"_xcd int";
	outputdb.store();
      }

  // Create combinations
  srandom(time(0));

  for(int n=min;n<=max;n++)
    {
      for(int j=0;j!=numcombos;j++)
	{
	  Combo combo=makeFacilitiesCombo(n,triggers,constraints,a,selectable);
	  int combosams;
	  if(sams)
	    combosams=needsSAMS(combo,needssams,hassams);
	  // Ideally this should be in a separate thread, but I'm not sure that MySQL's C API is threadsafe, and Cygwin only partially supports pthreads.
	  outputdb.query<<"insert into "<<tblname<<" (combonum,selectednum,selects,needssams) values ("<<j<<","<<n<<",\""<<combo<<"\","<<combosams<<")";
	  outputdb.store();
	}
    }
  return 0;
}

int needsSAMS(Combo combo,vector<int> needssams,int hassams){
  for(int n=0;n!=needssams.size();n++)
    if(combo[needssams[n]])
      {
	combo[hassams]=true;
	return 1;
      }
  return 0;
}


Combo makeFacilitiesCombo(int size,vector<vector<int> > triggers,vector<vector<int> > constraints,vector<int> a,vector<int> selectable){
  Combo combo(a);
  if(size>a.size()+selectable.size())size=a.size()+selectable.size();
  for(int j=a.size();j<size;j++)
    {
      int idx;
      do{idx=selectable[truerand(selectable.size())];}while(combo[idx]);
      combo[idx]=true;
    }
  constrain(combo,triggers,constraints);
  return combo;
}

void constrain(Combo &combo,const vector<vector<int> >& triggers,const vector<vector<int> >& constraints){
  for(int n=0;n!=triggers.size();n++)
    {
      vector<int> trigger=triggers[n];
      for(int j=0;j!=trigger.size();j++)
	if(combo[trigger[j]]) // If there's a trigger rack selected
	  {
	    vector<int> constraint=constraints[n];
	    for(int n=0;n!=constraint.size();n++)
	      combo[constraint[n]]=true;
	  }
    }
}


int truerand(int limit){
  int r;
  do{r=random();}while(r>(RAND_MAX-RAND_MAX%limit));
  return r%limit;
}



void getStatus(int idx,MYSQL conn,vector<int>& r,vector<int>& s,vector<int>& a,vector<int>& o,vector<int>& k){
  r.clear();s.clear();a.clear();o.clear();k.clear(); // Make sure these are empty
  ResultSet rackstatus(&conn);
  rackstatus.query<<"select selection.* from selection,racks where run="<<idx<<" and rack=racks.idx order by rack asc"; // Get the selection status data from this run
  rackstatus.store();
  int rack=rackstatus.fieldNumber("rack");  // Remember the indices of a couple fields so we don't have to look them up every time
  int status=rackstatus.fieldNumber("status");
  while(rackstatus.next()) // For each row in results (each rack)
    {
      int current=(int)rackstatus[status]; // Grab its status
      int racknum=(int)rackstatus[rack]; // And its number
      switch(current) // Put its number in the right vector
	{
	case 0:r.push_back(racknum);break;
	case 1:s.push_back(racknum);break;
	case 2:a.push_back(racknum);break;
	case 3:o.push_back(racknum);break;
	case 4:k.push_back(racknum);break;
	}
    }
}




bool getSAMS(MYSQL conn,vector<int>& needssams,int& hassams){
  ResultSet sams(&conn);
  sams.query<<"select idx from racks where sams=2";
  sams.store();
  if(sams.next())
    hassams=sams["idx"];
  else return false;
  
  sams.query<<"select idx from racks where sams=1";
  sams.store();
  while(sams.next())
    needssams.push_back(sams["idx"]);

  return true;
}




void getConstraints(int idx,MYSQL conn,vector<vector<int> > &triggers,vector<vector<int> > &constraints){
  ResultSet runcons(&conn); // The constraints selected in this run
  runcons.query<<"select cons from runcons where run="<<idx;
  runcons.store();

  triggers.clear();constraints.clear();

  if(runcons.size()==0)return;

  while(runcons.next())
    {
      ResultSet consquery(&conn); // Make resultset on this database conn
      // Query DB for the triggers for this constraint.
      consquery.query<<"select rack,trigger from constraints where name=\""<<runcons.get("cons")<<'\"';
      consquery.store(); // Run the query
      vector<int> trigger,racks; // These are the triggers/racks lists for this constraint
      if(consquery.size()==0)continue;
      while(consquery.next()) // Add them all in from the resultset
	{
	  int rack=(int)consquery["rack"];
	  if((int)consquery["trigger"]==1)
	    trigger.push_back(rack);
	  racks.push_back(rack);
	}
      triggers.push_back(trigger); // Add this to the overall triggers list.
      constraints.push_back(racks); // Add this to the overall triggers list.
    }
}
