#include <fstream>
#include "transitions.h"
#include "generic_individual.h"
#include "generic_fitness.h"
#include "individual.h"
#include "table_fitness.h"

using namespace std;

/**
 * a constructor for a rule-based individual
 */
table_fitness::table_fitness(int *p_rules, int p_size,
    char *p_filename, int p_row_size) {
  rule_tbl = 0;
  read_rule_table(p_rules, p_size); // also sets the block variables

  table = 0;
  read_table(p_filename, p_row_size);
}

/**
 * a constructor for a matrix-based individual
 */
table_fitness::table_fitness(char *p_filename, int p_row_size) {
  rule_tbl = 0;
  block_variants = GF_VARIANTS_UNLIMITED;
  block_bitsize = p_row_size;

  table = 0;
  read_table(p_filename, p_row_size);
}

/**
 * reads the reference table from a file
 */
int table_fitness::read_table(char *p_filename, int p_row_size) {
  // check the parameters
  if(p_filename == 0) return TF_RT_NO_FILENAME_GIVEN;
  if(p_row_size < 1) return TF_RT_ROW_TOO_SHORT;

  // open the input file
  ifstream table_file;
  table_file.open(p_filename);
  if(!table_file.is_open()) return TF_RT_FILE_ERROR;

  // free the table if it already exists
  if(table != 0) delete_table();

  // prepare the necessary structures
  int table_size;
  table_file >> table_size; // stated in the very first line
  table_size++; // to accomodate for a trailing zero line

  // create the table from scratch
  table = new char * [table_size];
  row_size = p_row_size;

  int line_len = row_size + 1; // hit info at the end
  char line_buff [line_len];
  int i = 0;

  // go through the file and construct the table
  while((i < (table_size - 1)) && (!table_file.eof())) {
    table_file.width(line_len);
    table_file >> line_buff;
    table[i] = new char [line_len];

    for(int j = 0; j < line_len; j++) {
      char tmp = line_buff[j];
      if((tmp > 47) && (tmp < 58)) tmp -= 48; // a digit
      table[i][j] = tmp;
    }

    i++;
  }

  table[i] = 0; // secure the end of the table
  table_file.close(); // close the input file

  // return the number of rows constructed
  return i;
}

/**
 * resets the reference table
 */
void table_fitness::reset_table() {
  if(table == 0) return;

  int i = 0;

  while(table[i] != 0)
    table[i++][row_size] = 0;
}

/**
 * deletes the reference table
 */
void table_fitness::delete_table() {
  if(table == 0) return;

  int i = 0;

  while(table[i] != 0)
    delete [] table[i++];

  delete [] table;
  table = 0;
}

/**
 * compares two vectors containing don't care symbols
 */
bool table_fitness::compare_vectors(char *p_first, char *p_second,
    int p_cells) {
  if((p_first == 0) || (p_second == 0)) return false;

  for(int i = 0; i < p_cells; i++)
    if((p_first[i] != DONT_CARE)
        && (p_second[i] != DONT_CARE)
        && (p_first[i] != p_second[i]))
      return false;

  return true;
}

/**
 * attempts to cover as much vectors in the table as possible with
 * a given vector
 */
float table_fitness::cover_table(char *p_vector, int p_cells) {
  int covered = 0;
  int i = 0;

  // go through the whole table
  while(table[i] != 0) {
    // is it a fresh vector?
    if(table[i][p_cells] == 0) {
      if(compare_vectors(p_vector, table[i], p_cells)) {
        table[i][p_cells] = 1;
        covered++; // it is and it just got hit
      }
    }
    else covered++; // no, it ain't

    i++;
  }

  // compute the coverage and return it
  return ((float) covered / i);
}

/**
 * returns the coverage of the table by a number of matrix-generated
 * vectors
 */
float table_fitness::get_matrix_fitness(GENE *p_matrix,
    GENE *p_seed, int p_cells, int p_vector_cnt) {
  // store the seed in the form of a state and create a state buffer
  GENE state [p_cells];
  GENE state_buff [p_cells];
  char tmp_vector [p_cells];

  for(int i = 0; i < p_cells; i++)
    state[i] = p_seed[i];

  // simulate the corresponding automaton for a number of times
  // and evaluate the obtained vector sequence
  float res = 0;

  for(int i = 0; i < p_vector_cnt; i++) {
    // carry out a transition
    transitions::matrix_transition(state_buff, p_cells, p_matrix,
        state);

    // finalise the new state and get the test vector
    for(int j = 0; j < p_cells; j++)
      tmp_vector[j] = (char) (state[j] = state_buff[j]);

    // evaluate the vector
    res = cover_table(tmp_vector, p_cells);
   }

  // finally, return the coverage
  return res;
}

/**
 * returns the coverage of the table by a number of rule-generated
 * vectors
 */
float table_fitness::get_rule_fitness(int *p_rules, GENE *p_seed,
    int p_cells, int p_vector_cnt) {
  // store the seed in the form of a state and create a state buffer
  GENE state [p_cells];
  GENE state_buff [p_cells];
  char tmp_vector [p_cells];

  for(int i = 0; i < p_cells; i++)
    state[i] = p_seed[i];

  // simulate the corresponding automaton for a number of times
  // and evaluate the obtained vector sequence
  float res = 0;

  for(int i = 0; i < p_vector_cnt; i++) {
    // carry out a transition
    transitions::rule_transition(state_buff, p_cells, p_rules, state,
        rule_tbl);

    // finalise the new state and get the test vector
    for(int j = 0; j < p_cells; j++)
      tmp_vector[j] = (char) (state[j] = state_buff[j]);

    // evaluate the vector
    res = cover_table(tmp_vector, p_cells);
  }

  // finally, return the coverage
  return res;
}

/**
 * returns the coverage of the table by a number of vectors
 * generated using the information stored inside of an individual
 */
float table_fitness::get_fitness(generic_individual *p_ind,
    int p_vector_cnt) {
  // reset the table coverage
  reset_table();

  // check the individual type, extract the necessary structures
  // accordingly and call the appropriate fitness function
  int cells =  ((individual *) p_ind)->get_cells();

  // check the compatibility of the given individual with the table
  if(cells != row_size) return TF_GF_WRONG_CELL_COUNT;

  int size = ((individual *) p_ind)->get_size();
  char type = ((individual *) p_ind)->get_type();
  float res;

  if(type == TYPE_MATRIX_SEED) {
    GENE matrix [size - cells];
    ((individual *) p_ind)->get_matrix(matrix);

    GENE seed [cells];
    ((individual *) p_ind)->get_seed(seed);

    res = get_matrix_fitness(matrix, seed, cells, p_vector_cnt);
  }
  else if(type == TYPE_RULES_SEED) {
    int rules [cells];
    ((individual *) p_ind)->get_rules(rules);

    GENE seed [cells];
    ((individual *) p_ind)->get_seed(seed);

    res = get_rule_fitness(rules, seed, cells, p_vector_cnt);
  }

  // finally, return the obtained fitness
  return res;
}

