/*

g++ -o ga-example-005 -Wall -ansi -pedantic -std=c++11 -O3 ga-example-005.cc

./ga-example-005

*/


#include <iostream>
#include <algorithm>
#include <iterator>
#include <utility>
#include <vector>
#include <cstdlib>
#include <ctime>

#include "ga.hpp"

// The use of int as a chromosome type is fake here.
typedef int Chromosom;

class Individual {
public:
  
  static const ga::Blocks* problem; // This is the problem to solve
  
private:

  double fitness_value;
  void compute_fitness() {
    auto& blocks = *problem;
    
    fitness_value = ga::random(0,1); // This is fake. Change it.
    
    // You may need blocks in this code, in order to compute the
    // fitness that corresponds to the current genotype.
  }
  
public:
  
  Chromosom genotype;

  // Implement the followings in order to be able to use Individuals
  // in collections.
  
  // Default constructor.  It creates a random individual. 
  Individual(const ga::Blocks& blocks)
    : fitness_value(), genotype() {
    
    // make sure that a random genotype is set.
    
    compute_fitness();
  }

  Individual()                                  = delete;
  Individual(const Individual& copy)            = default;
  Individual& operator=(const Individual& copy) = default;
  Individual(Individual&& copy)                 = default;
  Individual& operator=(Individual&& copy)      = default;

  double fitness() const {
    return fitness_value;
  }
  
  // A comparison operator, based on the fitness value.
  bool operator<(const Individual& other) const {
    return fitness_value < other.fitness_value;
  }

  void mutation(void) {
    // genotype should be modified here. Then, consequently, the
    // fitness has to be recomputed.
    
    compute_fitness();
  }
};

// Let us declare here the static variable.
const ga::Blocks* Individual::problem = nullptr;


#define POPULATION_SIZE 200
#define MUTATION_PROBA   .1
#define NB_GENERATIONS  100

typedef std::vector<Individual> Population;

int main(int argc, char* argv[]) {

  // Random seed initialization.
  std::srand(std::time(0));

  // Definition of the optimization problem
  ga::Blocks blocks;   
  ga::make(30,blocks);
  Individual::problem = &blocks; 

  // This stores two populations for two consecutive generations.
  Population  population1;
  Population  population2;

  // These pointers refer the two previous population. pop_front is
  // the one we read, pop_back is the one we write. They will be
  // swapped at each generation.
  Population* pop_front;
  Population* pop_back;

  // Initial population allocation
  population1.reserve(POPULATION_SIZE);
  population2.reserve(POPULATION_SIZE);
  auto out1 = std::back_inserter(population1);
  auto out2 = std::back_inserter(population2);
  for(unsigned int i = 0; i < POPULATION_SIZE; ++i) {
    *(out1++) = Individual(blocks);
    *(out2++) = Individual(blocks);
  }
  

  // Setting front and back
  pop_front = &population1;
  pop_back  = &population2;

  for(int g = 0; g < NB_GENERATIONS; ++g) {

    // Let is manupulate current and previous, rather than *pop_front
    // and *pop_back.
    Population& current  = *pop_back;
    Population& previous = *pop_front;

    // We have to compute current from previous.

    // The reproduction stage can be implemented by selected the best
    // individual in a random set of 10 ones.
    for(auto& individual : current) {
      std::random_shuffle(previous.begin(),previous.end());
      auto best_iter = std::min_element(previous.begin(), previous.begin()+10);
      individual = *best_iter;
    }

    // You should implement the crossing over here.

    // Now, let us apply the mutation
    for(auto& individual : current) 
      if(ga::toss(MUTATION_PROBA))
	individual.mutation();

    // We swap the populations. 
    std::swap(pop_front,pop_back);
  }

  // Let us handle what we have computed.
  Population& result = *pop_front; // last iteration ended with a swap.
    
  // When needed, you can sort the population according to its
  // fitness, since individuals implement a comparison operator.
  std::sort(result.begin(),result.end());

  // Let us print weakest fitnesses first....
  for(auto& individual : result) 
    std::cout << individual.fitness() << std::endl;
  // ... the population may be quite uniform.

  return 0;
}