#include "StringUtils.h"
#include "OnelabClients.h"
#include "metamodel.h"
#include <algorithm>

onelab::server *onelab::server::_server = 0;
std::string clientName;

void initializeLoop(const std::string &level)
{
  bool changed = false;
  std::vector<onelab::number> numbers;
  onelab::server::instance()->get(numbers);
  for(unsigned int i = 0; i < numbers.size(); i++){

    if(numbers[i].getAttribute("Loop") == level){
      if(numbers[i].getChoices().size() > 1){
	numbers[i].setIndex(0);
	numbers[i].setValue(numbers[i].getChoices()[0]);
	onelab::server::instance()->set(numbers[i]);
	changed = true;
      }
      else if(numbers[i].getStep() > 0){
	if(numbers[i].getMin() != -onelab::parameter::maxNumber()){
	  numbers[i].setValue(numbers[i].getMin());
	  onelab::server::instance()->set(numbers[i]);
	  changed = true;
	}
      }
      else if(numbers[i].getStep() < 0){
	if(numbers[i].getMax() != onelab::parameter::maxNumber()){
	  numbers[i].setValue(numbers[i].getMax());
	  onelab::server::instance()->set(numbers[i]);
	  changed = true;
	}
      }
    }
  }
  // force this to make sure that we remesh, even if a mesh exists and
  // we did not actually change a Gmsh parameter
  if(changed)
   onelab::server::instance()->setChanged(true, "Gmsh");
}
void initializeLoops()
{
  initializeLoop("1");
  initializeLoop("2");
  initializeLoop("3");
}

bool incrementLoop(const std::string &level)
{
  bool recompute = false, loop = false;
  std::vector<onelab::number> numbers;
  onelab::server::instance()->get(numbers);
  for(unsigned int i = 0; i < numbers.size(); i++){
    if(numbers[i].getAttribute("Loop") == level){
      loop = true;

      if(numbers[i].getChoices().size() > 1){
	int j = numbers[i].getIndex() + 1;
	if((j >= 0) && (j < (int)numbers[i].getChoices().size())){
	  numbers[i].setValue(numbers[i].getChoices()[j]);
	  numbers[i].setIndex(j);
	  onelab::server::instance()->set(numbers[i]);
	  OLMsg::Info("Recomputing with %dth choice %s=%g", j,
		    numbers[i].getName().c_str(), numbers[i].getValue());
	  recompute = true;
	}
      }
      else if(numbers[i].getStep() > 0){
	if(numbers[i].getMax() != onelab::parameter::maxNumber() &&
	   numbers[i].getValue() < numbers[i].getMax()){
	  numbers[i].setValue(numbers[i].getValue() + numbers[i].getStep());
	  onelab::server::instance()->set(numbers[i]);
	  OLMsg::Info("Recomputing with new step %s=%g",
		    numbers[i].getName().c_str(), numbers[i].getValue());
	  recompute = true;
	}
      }
      else if(numbers[i].getStep() < 0){
	if(numbers[i].getMin() != -onelab::parameter::maxNumber() &&
	   numbers[i].getValue() > numbers[i].getMin()){
	  numbers[i].setValue(numbers[i].getValue() + numbers[i].getStep());
	  onelab::server::instance()->set(numbers[i]);
	  OLMsg::Info("Recomputing with new step %s=%g",
		    numbers[i].getName().c_str(), numbers[i].getValue());
	  recompute = true;
	}
      }
    }
  }

  if(loop && !recompute) // end of this loop level
    initializeLoop(level);

  return recompute;
}

bool incrementLoops()
{
  bool ret = false;
  if(incrementLoop("3"))      ret = true;
  else if(incrementLoop("2")) ret = true;
  else if(incrementLoop("1")) ret = true;
  return ret;
}

std::string stateToChar(){
  std::vector<onelab::number> numbers;
  std::ostringstream sstream;
  onelab::server::instance()->get(numbers);
  for(std::vector<onelab::number>::iterator it = numbers.begin();
      it != numbers.end(); it++)
    sstream << (*it).getValue() << '\t';
  return sstream.str();
}

std::string showParamSpace(){
  std::vector<std::string> parameters=onelab::server::instance()->toChar();
  std::string db = "ONELAB parameter space: size="
    + itoa(onelab::server::instance()->getNumParameters()) + "\n";
  for(unsigned int i = 0; i < parameters.size(); i++)
    db.append(parameters[i] + '\n');
  for(unsigned int i = 0; i < db.size(); i++)
    if(db[i] == onelab::parameter::charSep()) db[i] = ' ';
  return db.c_str();
}

std::string showClientStatus(){
  std::ostringstream sstream;
  std::string name;
  std::cout << "\nONELAB: Present state of the onelab clients" << std::endl;
  for(onelab::server::citer it = onelab::server::instance()->firstClient();
      it != onelab::server::instance()->lastClient(); it++){
    name.assign(it->first);
    sstream << "<" << onelab::server::instance()->getChanged(name) << "> "
	    << name << std::endl;
  }
  return sstream.str();
}


bool menu() {
  int choice, counter1=0, counter2=0;
  std::vector<onelab::number> numbers;
  std::vector<onelab::string> strings;

  do {
    std::cout << "\nONELAB: menu" << std::endl ;
    std::cout << " 1- View parameter space\n 2- Client status\n 3- Set value\n 4- Analyze\n 5- Compute\n 6- Loop\n 7- Quit metamodel" << std::endl;
    choice=0;
    std::string mystr;
    while( (choice<1 || choice>7) && ++counter1<10 ) {
      std::cout << "\nONELAB: your choice? ";
      std::getline (std::cin, mystr);
      std::stringstream myStream(mystr);
      if (myStream >> choice) break;
      std::cout << "Invalid choice" << std::endl;
    }
    std::cout << "Your choice is <" << choice << ">" << std::endl;

    if (choice==1){
      std::cout << showParamSpace();
      choice=0;
    }
    if (choice==2){
      std::cout << showClientStatus();
      choice=0;
    }
    else if (choice==3){
      std::string name;
      std::cout << "ONELAB: Variable name? "; std::cin >> name;
      onelab::server::instance()->get(numbers,name);
      if (numbers.size()) {
	float fval;
	std::cout << "ONELAB: Value? "; std::cin >> fval;
	numbers[0].setValue(fval);
	bool allowed = onelab::server::instance()->set(numbers[0]);
      }
      else{
	onelab::server::instance()->get(strings,name);
	if (strings.size()) {
	  std::string sval;
	  std::cout << "ONELAB: Value? "; std::cin >> sval;
	  strings[0].setValue(sval);
	  onelab::server::instance()->set(strings[0]);
	}
	else
	  std::cout << "ONELAB: The variable " << name
		    << " is not defined" << std::endl;
      }
      choice=0;
    }
    else if (choice==4){
      metamodel("check");
      choice=0;
    }
    else if (choice==5){
      metamodel("compute");
      choice=0;
    }
    else if (choice==6){
      initializeLoops();
      do{
	metamodel("compute");
      }while(incrementLoops());
      choice=0;
    }
    else if (choice==7)
      exit(1);
    else
      choice=0;
  } while(!choice && ++counter2<20);
}

void PrintUsage(const char *name){
  printf("\nUsage:       %s [-a -i -h -v int] modelName\n", name);
  exit(1);
}

int main(int argc, char *argv[]){
  bool launchMenu=false;
  int i = 1;
  std::string caseName="", todo="compute", modelName="", workingDir="";
  onelab::remoteNetworkClient *client = 0;

  while(i < argc) {
    if(argv[i][0] == '-') {
      if(!strcmp(argv[i] + 1, "a")) {
        i++;
	todo="check";
      }
      else if(!strcmp(argv[i] + 1, "i")) {
	i++;
	launchMenu=true;
      }
      else if(!strcmp(argv[i] + 1, "lol")) {
	//loader used to call non-native clients (type=encapsulated) 
	std::string clientName=argv[i+1];
	client = new onelab::remoteNetworkClient(clientName,argv[i+2]);
	if(client){
	  std::string action, cmd;

	  std::vector<onelab::string> ps;
	  client->get(ps,clientName+"/Action");
	  if(ps.size() && ps[0].getValue().size())
	    action.assign(ps[0].getValue());

	  if(!action.compare("compute")){
	    std::vector<onelab::string> ps;
	    client->get(ps,clientName+"/FullCmdLine");
	    if(ps.size() && ps[0].getValue().size())
	      cmd.assign(ps[0].getValue());

	    if(cmd.size()){
	      OLMsg::Info("Loader calls <%s>",cmd.c_str());
	      SystemCall(cmd.c_str(),true); //true->blocking
	    }
	    else
	      OLMsg::Info("No full command line found for <%s>",
			  clientName.c_str());
	    //OLMsg::Info("Stopping client <%s>", clientName.c_str());
	  }
	  delete client;
	}
	exit(1);
      }
      else if(!strcmp(argv[i] + 1, "onelab")) {
	//loader used as a test native client
	client = new onelab::remoteNetworkClient(argv[i+1],argv[i+2]);
	if(!client){
	  std::cout << "I have no client\n";
	  exit(1);
	}
	std::vector<onelab::string> strings;
	client->get(strings,client->getName()+"/9CheckCommand"); 
	if(strings.empty()){ // initialize
	  onelab::string s(client->getName()+"/9CheckCommand","-a");
	  client->set(s);
	  onelab::number o(client->getName()+"/Initialized",1);
	  client->set(o);
	}
	else{ 
	  std::cout << "I am initialized: CheckCommand=<" 
		    << strings[0].getValue() << ">" << std::endl;	
	  onelab::number o("alpha",123456);
	  client->set(o);
	  client->get(strings,"MESSAGE"); 
	  if(strings.size()){
	    std::cout << "I have to tell you: " << strings[0].getValue() << std::endl;
	  }
	  std::cout << "Now sleeping for 5s\n";
	  SleepInSeconds(5);
	  std::cout << "Awake again\n";
	}
	delete client;
	return 0;
      }
      else if(!strcmp(argv[i] + 1, "v")) {
        i++;
        if(argv[i])
          OLMsg::SetVerbosity(atoi(argv[i++]));
        else
          OLMsg::Fatal("Missing number");
      }
      else {
	i++;
	PrintUsage(argv[0]);
      }
    }
    else {
      caseName=argv[i];
      modelName.assign(SplitFileName(caseName)[1]);
      workingDir.assign(SplitFileName(caseName)[0]);
      if(workingDir.empty())
	workingDir.assign(getCurrentWorkdir());
      i++;
    }
  }

  if(caseName.empty())
    OLMsg::Fatal("No valid input model name <%s>.", caseName.c_str());

  OLMsg::InitializeOnelab("onelab");
  if(argc) OLMsg::SetOnelabString("LoaderPathName",argv[0]);
  OLMsg::SetOnelabString("Arguments/FileName",modelName);
  OLMsg::SetOnelabString("Arguments/WorkingDir",workingDir);

  if(launchMenu)
    menu();
  else
    metamodel(todo);

  OLMsg::FinalizeOnelab();
}

