#include "globals.h"
#include "DefaultParameters.h"
#include "Logger.h"
#include "Parameters.h"

#include <fstream>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

bool global::checkForProgramOption( boost::program_options::variables_map *vm,
                                    const char *option ) {
  if ( vm->count( option ) == 0 ) {
    LOG( Logger::LOGLEVEL_LOW, "Error in fetchProgramOptions\tOption missing:<%s>", option );
    return false;
  } else return true;
}

bool global::fetchProgramOptions( int *argc, char ***argv,
                                  po::variables_map *vm,
                                  const char *filenameBase ) {
  try {
    po::options_description programOptions( "Program options" );
    programOptions.add_options()

    ( "uid",
      po::value<std::string>(),
      "Human-readable text identifier for this program run." )

    ( "epsilon",
      po::value<FloatType>()->default_value( DefaultParameters::epsilon ),
      "[Optional] Tournament reward/penalty" )

    ( "convergenceThreshold",
      po::value<FloatType>()->default_value( DefaultParameters::convergenceThreshold ),
      "[Optional] Probability level at which convergence is determined" )

    ( "delta",
      po::value<int>(),
      "[Optional] Degree of the island model network" )

    ( "mu",
      po::value<FloatType>()->default_value( DefaultParameters::mu ),
      "[Optional] Migrant mixing coefficient" )

    ( "gamma",
      po::value<unsigned int>()->default_value( DefaultParameters::gamma ),
      "[Optional] Epoch length modifier" )

    ( "epochX",
      po::value<unsigned int>(),
      "Initial value of epoch threshold X" )

    ( "epochY",
      po::value<unsigned int>(),
      "Initial value of epoch threshold Y" )

    ( "epochGenerationLimit",
      po::value<unsigned int>()->default_value( DefaultParameters::epochGenerationLimit ),
      "[Optional] Maximum number of generations in one epoch" )

    ( "intervalUpdateParameters",
      po::value<unsigned int>()->default_value( DefaultParameters::intervalUpdateParameters ),
      "[Optional] Number of generations between parameter updating and communication polling" )

    ( "intervalCheckEpoch",
      po::value<unsigned int>()->default_value( DefaultParameters::intervalCheckEpoch ),
      "[Optional] Number of generations between checking for epoch threshold crossing" )

    ( "intervalStoppingLocal",
      po::value<unsigned int>()->default_value( DefaultParameters::intervalStoppingLocal ),
      "[Optional] Number of generations between checking local stopping conditions" )

    ( "intervalStoppingGlobal",
      po::value<unsigned int>()->default_value( DefaultParameters::intervalStoppingGlobal ),
      "[Optional] Number of generations between checking global stopping conditions" )

    ( "stopCPU",
      po::value<unsigned int>()->default_value( 30 ),
      "[Optional] max. number of seconds for the stopping conditions" )

    ( "stopCA",
      po::value<unsigned int>(),
      "[Optional] target number of converged alleles per deme for the stopping conditions" )

    ( "stopFitness",
      po::value<FloatType>(),
      "[Optional] target fitness value for the stopping conditions" )

    ( "stopGen",
      po::value<unsigned int>(),
      "[Optional] target number of generations for the stopping conditions" )
    /* End of the options description */ ;

    // Implicit options
    po::options_description implicitOptions( "Implicit options" );
    implicitOptions.add_options()
    ( "problemFile", po::value<std::string>(), "File containing the problem description" );

    po::options_description combinedOptions;
    combinedOptions.add( programOptions ).add( implicitOptions );
    po::positional_options_description positionalOptions;
    positionalOptions.add( "problemFile", -1 );
    // Parse command line
    store( po::command_line_parser( *argc, *argv ).
           options( combinedOptions ).positional( positionalOptions ).run(), *vm );
    // Parse configuration file
    std::ifstream ifs(( std::string( filenameBase ) + ".cfg" ).c_str() );
    store( parse_config_file( ifs, programOptions ), *vm );
    notify( *vm );

    // Validate options
    bool valid = true;
    valid = valid && checkForProgramOption( vm, "uid" );
    valid = valid && checkForProgramOption( vm, "epsilon" );
    valid = valid && checkForProgramOption( vm, "convergenceThreshold" );
    valid = valid && checkForProgramOption( vm, "delta" );
    valid = valid && checkForProgramOption( vm, "mu" );
    valid = valid && checkForProgramOption( vm, "gamma" );
    valid = valid && checkForProgramOption( vm, "epochX" );
    valid = valid && checkForProgramOption( vm, "epochY" );
    valid = valid && checkForProgramOption( vm, "epochGenerationLimit" );
    valid = valid && checkForProgramOption( vm, "intervalUpdateParameters" );
    valid = valid && checkForProgramOption( vm, "intervalCheckEpoch" );
    valid = valid && checkForProgramOption( vm, "intervalStoppingLocal" );
    valid = valid && checkForProgramOption( vm, "intervalStoppingGlobal" );
    valid = valid && checkForProgramOption( vm, "stopCPU" );
    valid = valid && checkForProgramOption( vm, "problemFile" );

    if (( !valid ) &&
        ( Parameters::getInstance()->getRank() == 0 ) &&
        ( Logger::getInstance()->getLogLevel() > Logger::LOGLEVEL_LOW ) ) {
      std::ofstream ofs(( std::string( filenameBase ) + ".usage" ).c_str(), std::ofstream::out | std::ofstream::trunc );
      if ( ofs.is_open() ) {
        ofs << programOptions << std::endl;
        ofs.close();
      }
    }
    return valid;
  } catch ( std::exception & e ) {
    LOG( Logger::LOGLEVEL_LOW,
         "Error in fetchProgramOptions\tstandard exception:<%s>",
         e.what() );
    return false;
  }
}
