#include "SgEngine.h"
#include "Parameters.h"

#include <cassert>
#include <cstring>
#include <boost/lexical_cast.hpp>

SgEngine::SgEngine( ProblemDomain *p ) :
    problem( p ),
    population(),
    bestFitness( ProblemDomain::NEGATIVE_INFINITY ),
    bestIndividual( new AlleleType[p->getNumberOfGenes()] ),
    individualOne( new AlleleType[p->getNumberOfGenes()] ),
    individualTwo( new AlleleType[p->getNumberOfGenes()] ),
    incomingIndividual( new AlleleType[p->getNumberOfGenes()] ),
    sizeOfIndividual( p->getNumberOfGenes() * sizeof( AlleleType ) ) {
  // Make sure that the Parameters singleton has been correctly initialized
  assert( p->getNumberOfGenes() == Parameters::getInstance()->getN() );
  // Set initial allele bias in the population. This is up to the problem domain.
  problem->setInitialBias( &population );
}

SgEngine::~SgEngine() {
  delete [] bestIndividual;
  delete [] individualOne;
  delete [] individualTwo;
  delete [] incomingIndividual;
}

std::string SgEngine::getChromosomeString( const AlleleType *chromosome ) {
  const unsigned int n = Parameters::getInstance()->getN();
  std::string buffer;
  for ( unsigned int locus = 0; locus < n; locus++ ) {
    buffer += '\t';
    try {
      buffer += boost::lexical_cast<std::string>( static_cast<unsigned int>( chromosome[locus] ) );
    } catch ( boost::bad_lexical_cast &e ) {
      buffer += "Exception:";
      buffer += e.what();
    }
  }
  return buffer;
}

void SgEngine::printBestIndividual( bool withChromosome, bool isNew, Logger::LogLevel logLevel, const char *prefix ) const {
  LOG( logLevel, "%s%sest individual\tfitness:\t%.3f", prefix, isNew ? "New b" : "B", bestFitness );
  if ( withChromosome && bestFitness != ProblemDomain::NEGATIVE_INFINITY ) {
    std::string buffer( prefix );
    buffer += isNew ? "New b" : "B";
    buffer += "est individual\tchromosome:";
    buffer += getChromosomeString( bestIndividual );
    LOG( logLevel, buffer.c_str() );
  }
}

const AlleleType * SgEngine::getBestIndividual() const {
  return bestIndividual;
}

FloatType SgEngine::getBestFitness() const {
  return bestFitness;
}

bool SgEngine::hasBestIndividual() const {
  return ( bestFitness != ProblemDomain::NEGATIVE_INFINITY );
}

AlleleType * SgEngine::getSpaceForSolutionCandidate() {
  return incomingIndividual;
}

bool SgEngine::testSolutionCandidate() {
  FloatType fitness = problem->getFitness( incomingIndividual );
  std::string str( "testSolutionCandidate\tincoming chromosome:" );
  str += getChromosomeString( incomingIndividual );
  LOG( Logger::LOGLEVEL_DEBUG, str.c_str() );
  if ( fitness > bestFitness ) {
    //TODO:swap pointers instead of copying?
    std::memcpy( bestIndividual, incomingIndividual, sizeOfIndividual );
    bestFitness = fitness;
    printBestIndividual( false, true, Logger::LOGLEVEL_LOW, "testSolutionCandidate\t" );
    return true;
  } else {
    LOG( Logger::LOGLEVEL_HIGH, "testSolutionCandidate\tInferior solution\tfitness:%.3f,bestFitness:%.3f",
         fitness, bestFitness );
    return false;
  }
}

Population * SgEngine::getPopulation() {
  return &population;
}

void SgEngine::doOneGeneration( ) {
  population.selectIndividual( individualOne );
  population.selectIndividual( individualTwo );
  AlleleType *winner = NULL, *loser = NULL;
  ProblemDomain::TournamentReturn tournamentResult = problem->tournament( individualOne, individualTwo );
  switch ( tournamentResult.winner ) {
    case ProblemDomain::TournamentReturn::LEFT:
      winner = individualOne;
      loser = individualTwo;
      break;
    case ProblemDomain::TournamentReturn::RIGHT:
      winner = individualTwo;
      loser = individualOne;
      break;
    case ProblemDomain::TournamentReturn::TIE:
      // Register the winner only. It is needed for checking for "bestness"
      winner = individualOne;
      break;
  }
  if ( tournamentResult.winner != ProblemDomain::TournamentReturn::TIE ) {
    assert( winner );
    assert( loser );
    population.rewardGenes( winner );
    population.penalizeGenes( loser );
  }
  if ( tournamentResult.winningFitness > bestFitness ) {
    assert( winner );
    //TODO:swap pointers instead of copying? Not easy: we have to adjust class member individual___
    std::memcpy( bestIndividual, winner, sizeOfIndividual );
    bestFitness = tournamentResult.winningFitness;
    printBestIndividual( false, true, Logger::LOGLEVEL_LOW, "doOneGeneration\t" );
  }
}

void SgEngine::doMoreGenerations( unsigned int howMany ) {
  for ( unsigned int i = 0; i < howMany; i++ )
    doOneGeneration();
}

void SgEngine::updatePopulation() {
  population.updateConvergedAlleles();
}
