#include <climits>
#include "generic_individual.h"
#include "../rand/randomc.h"
#include "individual.h"
#include "population_block.h"
#include "genetic_operators.h"

using namespace std;

/**
 * returns the lower block boundary
 */
int genetic_operators::get_block_boundary(int p_idx, int p_size,
    int p_cells) {
  if(p_idx < (p_size - p_cells)) {
    // the index points to the block region
    int tmp_mask = ULONG_MAX;
    int tmp_bits = individual::get_block_size() - 1;

    while(tmp_bits > 0) {
      tmp_mask <<= 1; // shift a 0 from the right side
      tmp_bits >>= 1;
    }

    p_idx &= tmp_mask;
  }

  return p_idx;
}

/**
 * returns a mutated individual based off of the individual
 * specified by the first parameter
 */
individual *genetic_operators::mutate(individual *p_ind,
    RANDOM_GENERATOR *p_rg, int p_point) {
  // get the parent's chromosome
  GENE *tmp_chr = new GENE [p_ind->get_size()];
  p_ind->get_chromosome(tmp_chr);

  // select a mutation point if necessary
  if(p_point == DEFAULT_MUT_POINT)
    p_point = p_rg->IRandom(0, p_ind->get_size() - 1);

  // mutate the selected gene
  if(GENE_COUNT == 2) {
    if(tmp_chr[p_point] == 1)
      tmp_chr[p_point] = 0;
    else tmp_chr[p_point] = 1;
  }
  else {
    tmp_chr[p_point]++;

    if(tmp_chr[p_point] >= GENE_COUNT)
      tmp_chr[p_point] %= GENE_COUNT;
  }

  // create a new individual with such chromosome and return it
  individual *res = new individual (p_ind->get_type(),
      p_ind->get_cells(), DEFAULT_FITNESS, tmp_chr);

  res->get_fitness(); // to behave like cross-over
  delete [] tmp_chr;

  return res;
}

/**
 * returns two individuals created as a result of a cross-over
 * performed on parent individuals (expects both the male and female
 * to be of the same type and size)
 */
population_block *genetic_operators::cross_over(char p_type,
    individual *p_male, individual *p_female, RANDOM_GENERATOR *p_rg,
    int p_first, int p_second) {
  // get the parents' chromosomes
  char ind_type = p_male->get_type();
  int ind_size = p_male->get_size();
  int ind_cells = p_male->get_cells();
  GENE *male_chr = new GENE [ind_size];
  GENE *female_chr = new GENE [ind_size];
  p_male->get_chromosome(male_chr);
  p_female->get_chromosome(female_chr);

  // select cross-over points if necessary
  if(p_first == DEFAULT_CO_POINT) {
    if(p_type != CO_TYPE_UNIFORM)
      p_first = get_block_boundary(p_rg->IRandom(0, ind_size - 1),
          ind_size, ind_cells);
    else p_first = 0; // uniform c-o doesn't need it but just in case
  }

  if(p_second == DEFAULT_CO_POINT) {
    if(p_type == CO_TYPE_DOUBLE_POINT) 
      p_second = get_block_boundary(p_rg->IRandom(0, ind_size - 1),
          ind_size, ind_cells);
    else p_second = ind_size; // we don't need it but just in case
  }

  // check the correct order of the two points
  if(p_first > p_second) {
    int tmp = p_first;
    p_first = p_second;
    p_second = tmp;
  }

  // perform cross-over of the selected type
  switch(p_type) {
    // n-point cross-over
    case CO_TYPE_SINGLE_POINT:
    case CO_TYPE_DOUBLE_POINT:
      for(int i = p_first; i < p_second; i++) {
        GENE tmp_gene = male_chr[i];
        male_chr[i] = female_chr[i];
        female_chr[i] = tmp_gene;
      }

      break;

    // uniform cross-over
    case CO_TYPE_UNIFORM:
      for(int i = 0; i < ind_size; i++) {
        if(p_rg->Random() < 0.5) {
          GENE tmp_gene = male_chr[i];
          male_chr[i] = female_chr[i];
          female_chr[i] = tmp_gene;
        }
      }

      break;

    default:
      break;
  }

  // create new individuals from the modified chromosomes
  // and pass them outside
  population_block *res_lst = 0;

  individual *tmp_ind = new individual (ind_type, ind_cells,
      DEFAULT_FITNESS, male_chr);

  res_lst = new population_block (tmp_ind, res_lst); // gets fitness
  delete tmp_ind;

  tmp_ind = new individual (ind_type, ind_cells,
      DEFAULT_FITNESS, female_chr);

  res_lst = new population_block (tmp_ind, res_lst); // gets fitness
  delete tmp_ind;

  delete [] male_chr;
  delete [] female_chr;

  return res_lst;
}

