#include <iostream>
#include <cmath>
#include "generic_individual.h"
#include "individual.h"
#include "population_block.h"
#include "population.h"
#include "population_test.h"

using namespace std;

/**
 * performs a series of tests and returns on the first error
 * with a corresponding error code
 */
int population_test::test(int p_cells) {
  // test indeces generating and sorting functions
/*
  population tmp_pop (50);
  int indeces [5];
  tmp_pop.quick_sort(tmp_pop.random_tuple(indeces, 5, 50), 5);

  cout << endl << "Indeces: ";

  for(int i = 0; i < 5; i++)
    cout << indeces[i] << " ";

  cout << endl;
*/

  // test the constructors and getters at the same time
  int pop_size = 10;
  population pop_1 (pop_size);
  population pop_10 (&pop_1);

  if((pop_1.get_size() != 0)
      || (pop_1.get_fresh() != 0)
      || (pop_1.get_maximum_size() != pop_size))
    return TP_INIT_ERROR;

  if((pop_10.get_size() != 0)
      || (pop_10.get_fresh() != 0)
      || (pop_10.get_maximum_size() != pop_size))
    return TP_INIT_ERROR;

  // test the simple predicates
  if(!pop_1.is_empty()
      || pop_1.is_full()
      || pop_1.fresh_left())
    return TP_PREDICATE_ERROR;

  RANDOM_GENERATOR rg (time(0));

  // test refill():
  // fill up an empty population
  pop_1.refill(TYPE_RULES_SEED, p_cells, 0.0, 0.0, &rg);

  if((pop_1.get_size() != pop_size)
      || (pop_1.get_size() != pop_1.get_fresh())
      || pop_1.is_empty()
      || !pop_1.is_full()
      || !pop_1.fresh_left())
    return TP_REFILL_ERROR;

  int tmp_size = pop_1.get_size();

  // attempt to refill the already full population
  pop_1.refill(TYPE_RULES_SEED, p_cells, 0.0, 0.0, &rg);

  // did it change any attributes?
  if((pop_1.get_size() != tmp_size)
      || (pop_1.get_size() != pop_1.get_fresh())
      || pop_1.is_empty()
      || !pop_1.is_full()
      || !pop_1.fresh_left())
    return TP_REFILL_ERROR;

  int tuple_size = 2;
  int tuples = 0;

  // test pick_random_tuple():
  // simulate one round of a tournament
  while(pop_1.fresh_left()) {
    // pick a tuple ...
    population_block *tmp_blk = pop_1.pick_random_tuple(tuple_size,
        &rg);

    tuples++;

    // ... and destroy it right away
    int ind_recvd = 0;

    while(tmp_blk != 0) {
      ind_recvd++;

      population_block *tmp = tmp_blk;
      tmp_blk = tmp_blk->get_next();
      delete tmp;
    }

    // did we obtain the requested amount of individuals at the most?
    if(ind_recvd > tuple_size) return TP_PICK_RANDOM_TUPLE_ERROR;
  }

  // check the number of tuples picked
  if(tuples != ceil((float) pop_1.get_maximum_size() / tuple_size))
    return TP_PICK_RANDOM_TUPLE_ERROR;

  // try picking one more time from a supposedly used up population
  population_block *tmp_blk = pop_1.pick_random_tuple(tuple_size,
      &rg);

  // did we really obtain anything?
  if(tmp_blk != 0) {
    while(tmp_blk != 0) {
      population_block *tmp = tmp_blk;
      tmp_blk = tmp_blk->get_next();
      delete tmp;
    }

    return TP_PICK_RANDOM_TUPLE_ERROR;
  }

  // test refresh():
  // refresh the used up population
  tmp_size = pop_1.get_size();
  pop_1.refresh();

  // check the result (did it change any other attribute save for
  // fresh_left?)
  if(!pop_1.fresh_left()
      || (pop_1.get_size() != tmp_size)
      || (pop_1.get_fresh() != pop_1.get_size())
      || !pop_1.is_full())
    return TP_REFRESH_ERROR;

  // attempt to refresh the already refreshed population
  tmp_size = pop_1.get_size();
  pop_1.refresh();

  // did it change any attribute?
  if(!pop_1.fresh_left()
      || (pop_1.get_size() != tmp_size)
      || (pop_1.get_fresh() != pop_1.get_size())
      || !pop_1.is_full())
    return TP_REFRESH_ERROR;

  // how about a partly used up population?
  tmp_size = pop_1.get_size();
  delete pop_1.pick_random_tuple(1, &rg);
  pop_1.refresh();

  // check the result (did it change any other attribute save for
  // fresh_left?)
  if(!pop_1.fresh_left()
      || (pop_1.get_size() != tmp_size)
      || (pop_1.get_fresh() != pop_1.get_size())
      || !pop_1.is_full())
    return TP_REFRESH_ERROR;

  // now, we'll add a few individuals into another population
  pop_size = 3;
  population pop_2 (pop_size);

  // but before that, try to pick an individual from that empty
  // population
  individual *tmp_ind = pop_2.pick_individual(0);

  // did we get something?
  if(tmp_ind != 0) {
    delete tmp_ind;
    return TP_PICK_INDIVIDUAL_ERROR;
  }

  // check the population's average fitness
  if(pop_2.get_average_fitness() != 0.0)
    return TP_GET_AVERAGE_FITNESS_ERROR;

  // check the population's total fitness
  if(pop_2.get_total_fitness() != 0.0)
    return TP_GET_TOTAL_FITNESS_ERROR;

  GENE tmp_chr [p_cells];
  for(int i = 0; i < p_cells; i++) tmp_chr[i] = 0;

  tmp_chr[0] = 1;
  individual ind_1 (TYPE_RULES_SEED, p_cells, 5.0, tmp_chr);

  tmp_chr[0] = 0;
  tmp_chr[1] = 1;
  individual ind_2 (TYPE_RULES_SEED, p_cells, 3.0, tmp_chr);

  tmp_chr[0] = 1;
  individual ind_3 (TYPE_RULES_SEED, p_cells, 1.0, tmp_chr);

  pop_2.add_individual(&ind_1);
  pop_2.add_individual(&ind_2);
  pop_2.add_individual(&ind_3);

  // check if it's properties were updated accordingly
  if((pop_2.get_size() != pop_size)
      || (pop_2.get_fresh() != pop_2.get_size())
      || !pop_2.is_full())
    return TP_ADD_INDIVIDUAL_ERROR;

  // check the population's average fitness
  if(pop_2.get_average_fitness() != 3.0)
    return TP_GET_AVERAGE_FITNESS_ERROR;

  // check the population's total fitness
  if(pop_2.get_total_fitness() != 9.0)
    return TP_GET_TOTAL_FITNESS_ERROR;

  // try adding one more into the full population
  tmp_size = pop_2.get_size();
  pop_2.add_individual(&ind_3);

  // did it change any attribute?
  if((pop_2.get_size() != tmp_size)
      || (pop_2.get_fresh() != pop_2.get_size())
      || !pop_2.is_full())
    return TP_ADD_INDIVIDUAL_ERROR;

  // try to pick a handful of individuals now ...
  // pick the middle one (with regards to the fresh list)
  tmp_ind = pop_2.pick_individual(1);

  if(tmp_ind != 0) {
    if(!tmp_ind->compare(&ind_2)) {
      delete tmp_ind;
      return TP_PICK_INDIVIDUAL_ERROR;
    }
    else delete tmp_ind;
  }
  else return TP_PICK_INDIVIDUAL_ERROR;

  // pick the first one
  pop_2.refresh();
  tmp_ind = pop_2.pick_individual(0);

  if(tmp_ind != 0) {
    if(!tmp_ind->compare(&ind_1)) {
      delete tmp_ind;
      return TP_PICK_INDIVIDUAL_ERROR;
    }
    else delete tmp_ind;
  }
  else return TP_PICK_INDIVIDUAL_ERROR;

  // pick the last one
  tmp_ind = pop_2.pick_individual(2);

  if(tmp_ind != 0) {
    if(!tmp_ind->compare(&ind_3)) {
      delete tmp_ind;
      return TP_PICK_INDIVIDUAL_ERROR;
    }
    else delete tmp_ind;
  }
  else return TP_PICK_INDIVIDUAL_ERROR;

  // does more than one fresh remain?
  if(pop_2.get_fresh() != 1)
    return TP_PICK_INDIVIDUAL_ERROR;

  // refresh the population and try to pick
  // a non-existent individual
  pop_2.refresh();
  tmp_ind = pop_2.pick_individual(3);

  if(tmp_ind != 0) {
    delete tmp_ind;
    return TP_PICK_INDIVIDUAL_ERROR;
  }

  // pick the first fresh individual
  pop_2.refresh();
  tmp_ind = pop_2.pick_first_fresh();

  if(tmp_ind != 0) {
    if(!tmp_ind->compare(&ind_3)) {
      delete tmp_ind;
      return TP_PICK_FIRST_FRESH_ERROR;
    }
    else delete tmp_ind;
  }
  else return TP_PICK_FIRST_FRESH_ERROR;

  // use up the rest of the population
  while(pop_2.fresh_left())
    delete pop_2.pick_first_fresh();

  // try to pick the first fresh again
  tmp_ind = pop_2.pick_first_fresh();

  if(tmp_ind != 0) {
    delete tmp_ind;
    return TP_PICK_FIRST_FRESH_ERROR;
  }

  // pick two best individuals from the population
  bool hit = true; // let's presume we'll get the right ones
  pop_2.refresh();
  tmp_blk = pop_2.get_best(2);

  // did we get anything?
  if(tmp_blk != 0) {
    individual *tmp_ind = tmp_blk->get_individual();

    population_block *tmp = tmp_blk;
    tmp_blk = tmp_blk->get_next();
    delete tmp;

    // is it one of the elite individuals?
    hit &= (tmp_ind->compare(&ind_1)
        || tmp_ind->compare(&ind_2));

    delete tmp_ind;
  }
  else return TP_GET_BEST_ERROR;

  if(!hit) return TP_GET_BEST_ERROR;

  // this time around, we'll try with more individuals than available
  int tmp_fresh = pop_2.get_fresh();
  tmp_blk = pop_2.get_best(tmp_fresh + 1);
  int returned = 0;

  // did we get anything?
  while(tmp_blk != 0) {
    returned++;

    population_block *tmp = tmp_blk;
    tmp_blk = tmp_blk->get_next();
    delete tmp;
  }

  if(returned != tmp_fresh) return TP_GET_BEST_ERROR;

  // let's check the diversity measure ...
  pop_size = 3;
  float tmp_elite = 1; // consider the first individual as an elite
  population pop_3 (pop_size);

  // ... of an empty population
  if(pop_3.get_diversity(tmp_elite) > 0.001)
    return TP_GET_DIVERSITY_ERROR;

  if(tmp_elite > 0.001) return TP_GET_ELITE_ERROR;

  // ... of a homogenous population
  pop_3.add_individual(&ind_1);
  pop_3.add_individual(&ind_1);
  float tmp_diversity = pop_3.get_diversity(tmp_elite);

  if((tmp_diversity < 0.49) || (tmp_diversity > 0.51))
    return TP_GET_DIVERSITY_ERROR;

  if(tmp_elite < 0.999) return TP_GET_ELITE_ERROR;

  // ... of a heterogenous population
  pop_3.add_individual(&ind_2);
  tmp_diversity = pop_3.get_diversity(tmp_elite);

  if((tmp_diversity < 0.65) || (tmp_diversity > 0.67))
    return TP_GET_DIVERSITY_ERROR;

  if((tmp_elite < 0.65) || (tmp_elite > 0.67))
    return TP_GET_ELITE_ERROR;

  return TP_OK;
}

