/* ========================================================================== *
 *  Base class for all evolutionary algorithms.
 *
 *  Author: Firas Safadi (fsafadi@ulg.ac.be)
 * ========================================================================== */

#pragma once

#include "Matrix.h"

class EvolutionaryAlgorithm;

class Simulator
{
public:
  Simulator() {}
  virtual ~Simulator() {}
  virtual void sim(EvolutionaryAlgorithm* ea) = 0;
};

class EvolutionaryAlgorithm
{
public:
  //Base constructor
  EvolutionaryAlgorithm(nat popCount, nat popSize, nat genSize, nat maxGenSize);
  //Base destructor
  virtual ~EvolutionaryAlgorithm();

  //Spawns all populations
  virtual void hatchSpecies() = 0;
  //Evolves all populations
  virtual void evolveSpecies() = 0;
  //Wipes all populations
  virtual void wipeSpecies() = 0;
  //(Re)Spawns all populations after setting new genome size
  virtual void hatchSpecies(nat genSize) = 0;

  //Adds an additional gene to the species genome
  virtual void incGenSize();

  //Sets the simulator used to evaluate individual fitness
  virtual void setSimulator(Simulator* sim);
  //Sets the default number of threads per block used for all kernels
  virtual void setThreadsPerBlock(nat n);
  //Sets the number of generations in an evolution cycle
  virtual void setEvolutionEpochs(nat n);

  //Returns the total number of individuals in the species
  virtual nat totalInd() const { return m_iTotalIndividuals; }
  //Returns the number of populations in the species
  virtual nat popCount() const { return m_iPopulationCount; }
  //Returns the number of individuals in a population
  virtual nat popSize() const { return m_iPopulationSize; }
  //Returns the current genome size
  virtual nat genSize() const { return m_iGenomeSize; }
  //Returns the maximum genome size
  virtual nat maxGenSize() const { return m_iMaxGenomeSize; }

  //Returns the number of threads spawned per population block
  virtual nat threadsPerBlock() const { return m_iThreadsPerBlock; }
  //Returns the total number of population blocks
  virtual nat blockCount() const { return m_iBlocks; }

  //Returns the previous (parent) populations
  virtual Matrix& getPrevPop();
  //Returns the current (child) populations
  virtual Matrix& getCurrPop();

  //Returns the previous (parent) population fitness vector
  virtual Matrix& getPrevFit();
  //Returns the current (child) population fitness vector
  virtual Matrix& getCurrFit();

  //Returns the previous (parent) population leaderboard vector
  virtual Natrix& getPrevBoard();
  //Returns the current (child) population leaderboard vector
  virtual Natrix& getCurrBoard();

  //Disables entire genome pregeneration
  virtual void disablePregen() { m_bPreGenerate = false; }
  //Enables entire genome pregeneration
  virtual void enablePregen() { m_bPreGenerate = true; }

  //Identifies the fittest individual in the entire species
  virtual void topPerf(nat& pop, nat& ind, real& fit);

protected:
  //Evaluates the current (child) populations by calling the fitness function
  void evalPop();
  //Sorts the current (child) populations after evaluation
  void sortPop();

  Matrix m_pPopulation;
  Matrix m_pPopulationAlt;

  Matrix m_pFitness;
  Matrix m_pFitnessAlt;

  Natrix m_pLeaderboard;
  Natrix m_pLeaderboardAlt;

  nat m_iPopulationCount;
  nat m_iPopulationSize;

  nat m_iTotalIndividuals;

  nat m_iGenomeSize;
  nat m_iMaxGenomeSize;

  nat m_iEvolutionEpochs;
  nat m_iCurrentGeneration;

  nat m_iThreadsPerBlock;
  nat m_iBlocks;

  bool m_bAlive;
  bool m_bPreGenerate;

  curandState* m_pRandSpring;

  cublasHandle_t m_pHandle;

  //External fitness function
  Simulator* m_pSimulator;
  Simulator* m_pDefaultSimulator;

private:
  //Validates currently set species parameters
  void validateSpeciesParameters() const;
  //Initializes random spring (one generator per individiual)
  void initRandomGenerator();
};
