#include "globals.h"
#include "BistProblem.h"
#include "Communication.h"
#include "Parameters.h"

#include <string>
#include <boost/program_options.hpp>

int main( int argc, char *argv[] ) {
  // Initialize MPI
  int rank, p;
  Communication::programStart( &argc, &argv, &rank, &p );

  // Set MPI parameters
  Parameters *params = Parameters::getInstance();
  params->setP( p );
  params->setRank( rank );

  // Load the program options
  boost::program_options::variables_map vm;
  if ( !global::fetchProgramOptions( &argc, &argv, &vm ) ) {
    LOG( Logger::LOGLEVEL_LOW, "Fatal error\tRequired program options missing!" );
    // Deinit MPI and exit
    Communication::programTerminate();
    return 1;
  }

  // Start the logger
  Logger::getInstance()->generateFilenames( vm["uid"].as<std::string>().c_str() );
  LOG( Logger::LOGLEVEL_MEDIUM, "Program start" );

  // Set configuration parameters
  params->setEpsilon(
    vm["epsilon"].as<FloatType>() );
  params->setConvergenceThreshold(
    vm["convergenceThreshold"].as<FloatType>() );
  params->setDelta(
    vm["delta"].as<int>() );
  params->setMu(
    vm["mu"].as<FloatType>() );
  params->setGamma(
    vm["gamma"].as<unsigned int>() );
  params->setEpochX(
    vm["epochX"].as<unsigned int>() );
  params->setEpochY(
    vm["epochY"].as<unsigned int>() );
  params->setEpochGenerationLimit(
    vm["epochGenerationLimit"].as<unsigned int>() );
  params->setIntervalUpdateParameters(
    vm["intervalUpdateParameters"].as<unsigned int>() );
  params->setIntervalCheckEpoch(
    vm["intervalCheckEpoch"].as<unsigned int>() );
  params->setIntervalStoppingLocal(
    vm["intervalStoppingLocal"].as<unsigned int>() );
  params->setIntervalStoppingGlobal(
    vm["intervalStoppingGlobal"].as<unsigned int>() );

  // Load problem
  BistProblem problem( vm["problemFile"].as<std::string>() );
  if ( rank == 0 )
    problem.print();
  if ( !problem.isGood() ) {
    LOG( Logger::LOGLEVEL_LOW, "Fatal error\tInvalid problem file <%s>!", vm["problemFile"].as<std::string>().c_str() );
    // Deinit MPI and exit
    Communication::programTerminate();
    return 2;
  }

  // Set problem domain parameters
  params->setNA( problem.getNumberOfGenes(), problem.getNumberOfAlleles() );

  // Load the Selfish Gene engine
  SgEngine engine( &problem );

  // Load stopping conditions
  StoppingConditions stopCon; // Default constructor will set disjunctive polling for both contexts
  stopCon.addGlobal( new StoppingConditions::GlobalConditionCpuTime( vm["stopCPU"].as<unsigned int>() ) );

  if ( vm.count( "stopCA" ) ) {
    unsigned int ct = vm["stopCA"].as<unsigned int>();
    unsigned int pct = static_cast<unsigned int>( p ) * ct;
    stopCon.addLocal( new StoppingConditions::LocalConditionConvergedAlleles( ct ) );
    stopCon.addGlobal( new StoppingConditions::GlobalConditionConvergedAlleles( pct ) );
  }
  if ( vm.count( "stopFitness" ) ) {
    FloatType ft = vm["stopFitness"].as<FloatType>();
    stopCon.addLocal( new StoppingConditions::LocalConditionFitness( ft, &engine ) );
    stopCon.addGlobal( new StoppingConditions::GlobalConditionStoppedDemes( 1u ) );
  }
  if ( vm.count( "stopGen" ) ) {
    unsigned int gt = vm["stopGen"].as<unsigned int>();
    unsigned int pgt = static_cast<unsigned int>( p ) * gt;
    stopCon.addLocal( new StoppingConditions::LocalConditionGenerations( gt ) );
    stopCon.addGlobal( new StoppingConditions::GlobalConditionGenerations( pgt ) );
  }

  // Solve the problem!
  global::solve( &engine, stopCon );

  //Write a report
  if ( rank == 0 )
    engine.printBestIndividual( true, false, Logger::LOGLEVEL_LOW, "Program finished\t" );
  Logger::getInstance()->writeReports( engine.getBestFitness(), true );

  // Deinit MPI and exit
  Communication::programTerminate();
  return 0;
}
