#include "Parameters.h"
#include "Logger.h"
#include "Statistics.h"

#include <cstddef>
#include <cassert>
#include <cmath>
#include <algorithm>

//#define VERBOSE

Parameters * Parameters::instance = NULL;

const AlleleType Parameters::maxAlleleTypeValue = std::numeric_limits<AlleleType>::max();
const unsigned int Parameters::maxUnsignedIntValue = std::numeric_limits<unsigned int>::max();

Parameters * Parameters::getInstance() {
  if ( !instance )
    instance = new Parameters();
  return instance;
}

void Parameters::deleteInstance() {
  if ( instance )
    delete instance;
  instance = NULL;
}

Parameters::Parameters() :
    n( maxUnsignedIntValue ),
    a( NULL ),
    sumA( maxUnsignedIntValue ),
    maxA( maxAlleleTypeValue ),
    rho( static_cast<FloatType>( 0.5 ) ),
    epsilon( static_cast<FloatType>( -1.0 ) ),
    convergenceThreshold( static_cast<FloatType>( -1.0 ) ),
    p( -1 ),
    rank( -1 ),
    delta( -1 ),
    mu( static_cast<FloatType>( -1.0 ) ),
    gamma( -1 ),
    epochX( maxUnsignedIntValue ),
    epochY( maxUnsignedIntValue ),
    epochGenerationLimit( maxUnsignedIntValue ),
    intervalUpdateParameters( maxUnsignedIntValue ),
    intervalCheckEpoch( maxUnsignedIntValue ),
    intervalStoppingLocal( maxUnsignedIntValue ),
    intervalStoppingGlobal( maxUnsignedIntValue ) { /* Empty. */ }

Parameters::~Parameters() {
  if ( a )
    delete [] a;
}

void Parameters::setNA( unsigned int numberOfGenes, const AlleleType *numberOfAlleles ) {
  if ( a )
    delete [] a;
  n = numberOfGenes;
  a = new AlleleType[n];
  sumA = 0; maxA = 0;
  for ( unsigned int locus = 0; locus < n; locus++ ) {
    a[locus] = numberOfAlleles[locus];
    sumA += a[locus];
    maxA = std::max( maxA, a[locus] );
  }
}

unsigned int Parameters::getN() const {
  return n;
}

AlleleType Parameters::getA( unsigned int locus ) const {
  if ( a )
    return a[locus];
  else
    return maxAlleleTypeValue;
}

unsigned int Parameters::getSumA() const {
  return sumA;
}

AlleleType Parameters::getMaxA() const {
  return maxA;
}

FloatType Parameters::getRho() const {
  return rho;
}
void Parameters::setRho( const FloatType &value ) {
  rho = value;
}

FloatType Parameters::getEpsilon() const {
  return epsilon;
}
void Parameters::setEpsilon( const FloatType& value ) {
  epsilon = value;
}

FloatType Parameters::getConvergenceThreshold() const {
  return convergenceThreshold;
}
void Parameters::setConvergenceThreshold( const FloatType& value ) {
  convergenceThreshold = value;
}

int Parameters::getP() const {
  return p;
}
void Parameters::setP( int value ) {
  p = value;
}

int Parameters::getRank() const {
  return rank;
}
void Parameters::setRank( int value ) {
  rank = value;
}

int Parameters::getDelta() const {
  return delta;
}
void Parameters::setDelta( int value ) {
  delta = value;
}

FloatType Parameters::getMu() const {
  return mu;
}
void Parameters::setMu( const FloatType& value ) {
  mu = value;
}

unsigned int Parameters::getGamma() const {
  return gamma;
}
void Parameters::setGamma( unsigned int value ) {
  gamma = value;
}

unsigned int Parameters::getEpochX() const {
  return epochX;
}
void Parameters::setEpochX( unsigned int value ) {
  epochX = value;
}

unsigned int Parameters::getEpochY() const {
  return epochY;
}
void Parameters::setEpochY( unsigned int value ) {
  epochY = value;
}

unsigned int Parameters::getEpochGenerationLimit() const {
  return epochGenerationLimit;
}
void Parameters::setEpochGenerationLimit( unsigned int value ) {
  epochGenerationLimit = value;
}

unsigned int Parameters::getIntervalUpdateParameters() const {
  return intervalUpdateParameters;
}
void Parameters::setIntervalUpdateParameters( unsigned int value ) {
  intervalUpdateParameters = value;
}

unsigned int Parameters::getIntervalCheckEpoch() const {
  return intervalCheckEpoch;
}
void Parameters::setIntervalCheckEpoch( unsigned int value ) {
  intervalCheckEpoch = value;
}

unsigned int Parameters::getIntervalStoppingLocal() const {
  return intervalStoppingLocal;
}
void Parameters::setIntervalStoppingLocal( unsigned int value ) {
  intervalStoppingLocal = value;
}

unsigned int Parameters::getIntervalStoppingGlobal() const {
  return intervalStoppingGlobal;
}
void Parameters::setIntervalStoppingGlobal( unsigned int value ) {
  intervalStoppingGlobal = value;
}

unsigned int Parameters::ceilDiv( unsigned int divisor, unsigned int dividend ) {
  assert( dividend > 0u );
  return (( divisor + ( dividend - 1u ) ) / dividend );
}

void Parameters::updateParameters( bool newEpoch ) {
#ifdef VERBOSE
  const unsigned int oldX = epochX, oldY = epochY;
  const FloatType oldRho = rho;
#endif
  {
    const FloatType one = static_cast<FloatType>( 1.0 ), two = static_cast<FloatType>( 2.0 );
    const FloatType n = static_cast<FloatType>( this->n );
    const FloatType c = static_cast<FloatType>( Statistics::getInstance()->getNumConvergedAlleles() );
    const FloatType t = static_cast<FloatType>( Statistics::getInstance()->getNumGenerationsEpoch() );

    rho = one / ( two + (( n - two ) / n ) * c + std::sqrt( t ) / n );
  }
  if ( newEpoch ) {
    // Update epoch thresholds using the epoch length modifier
    epochX += ceilDiv( epochX, gamma );
    epochY += ceilDiv( epochY, gamma );
    epochX = std::min( epochX, n );
    epochY = std::min( epochY, n );
  }

#ifdef VERBOSE
  if ( newEpoch )
    LOG( Logger::LOGLEVEL_HIGH, "updateParameters(T)\trho:%.3f => %.3f\tX:%u => %u\tY:%u => %u",
         oldRho, rho,
         oldX, epochX, oldY, epochY );
  else
    LOG( Logger::LOGLEVEL_DEBUG, "updateParameters(F)\trho:%.3f => %.3f",
         oldRho, rho );
#endif
}
