#include <iostream>
#include <cstring>
#include "generic_fitness.h"
#include "table_fitness.h"
#include "simulation_fitness.h"
#include "individual.h"
#include "evolution.h"
#include "random_solution.h"

using namespace std;

/**
 * parses a list of command line arguments
 */
char *parse_argument(char *p_buffer, int p_arg_cnt,
    char **p_arg_lst, char *p_arg_name) {
  if((p_buffer == 0) || (p_arg_cnt < 2) || (p_arg_lst == 0)
      || (p_arg_name == 0)) return 0;

  char *res = 0;

  while(--p_arg_cnt > 0) {
    if(!strcmp(*p_arg_lst++, p_arg_name)) {
      res = strcpy(p_buffer, *p_arg_lst);
      break;
    }
  }

  return res;
}

/**
 * parses a string from the standard input
 */
char *parse_string(char *p_arg, char *p_buffer, int p_buff_size,
    char *p_msg) {
  if((p_buffer == 0) || (p_buff_size < 2) || (p_msg == 0)) return 0;

  if(p_arg != 0) {
    if(p_buffer != p_arg) strcpy(p_buffer, p_arg);
    cout << p_msg << p_buffer << endl;
  }
  else {
    int line_size = 80;
    char line_buff [line_size];
    int r_margin = (p_buff_size < line_size) ? p_buff_size
      : line_size;
    r_margin--; // make it a valid array index

    do {
      cout << p_msg;
      cin.getline(line_buff, line_size);
    } while(line_buff[0] == 0);

    int i = 0;

    while((i < r_margin) && (line_buff[i] != ' ')) {
      p_buffer[i] = line_buff[i];
      i++;
    }

    p_buffer[i] = 0;
  }

  return p_buffer;
}

/**
 * parses a number which satisfies the given constraints from
 * the standard input
 */
float parse_number(char *p_arg, bool p_float, float p_lower,
    float p_upper, char *p_msg) {
  int line_size = 80;
  char line_buff [line_size];

  bool is_float;
  int res_man;
  int res_exp;
  float res;

  int i;
  int tmp;

  do {
    // parse a string
    parse_string(p_arg, line_buff, line_size, p_msg);

    // reset auxilliary variables
    is_float = false;
    res_man = 0;
    res_exp = 0;
    i = 0;

    // convert the string to a number
    while((tmp = line_buff[i++]) != 0) {
      if((tmp < 48) || (tmp > 57)) {
        // not a digit
        if(!is_float && ((char) tmp == '.')) {
          // the first '.'
          is_float = true;
          res_exp = 0;
        }
        else {
          p_arg = 0; // force the user input
          tmp = -1;
          break;
        }
      }
      else {
        // got a digit
        tmp -= 48;
        res_man = res_man * 10 + tmp;
        res_exp++;
      }
    }

    // apply the exponent if necessary
    res = (float) res_man;

    if(is_float)
      while(res_exp--) res /= 10;
  } while((tmp == -1) || (is_float != p_float) || (res < p_lower)
      || (res > p_upper));

  return res;
}

/**
 * prints obtained results
 */
void print_results(char *p_phase, double p_proc_time,
    time_t p_wall_time, float p_fitness, int p_individuals) {
  cout << p_phase << " results:" << endl;
  cout << "  process time [s]: " << p_proc_time << endl;
  cout << "  wall time [s]: " << p_wall_time << endl;
  cout << "  solution fitness: " << p_fitness << endl;
  cout << "  processed individuals: " << p_individuals << endl
    << endl;
}

/**
 * the main function implementing a simple CLI user interface
 */
int main(int argc, char **argv) {
  cout << "***************************************" << endl;
  cout << "* GA Optimizer for CA-based BIST TPGs *" << endl;
  cout << "***************************************" << endl << endl;
  cout << "Please specify the following options:" << endl;

  // declare auxilliary variables
  int tmp_rules [] = {60, 90, 150, 240};
  int tmp_rule_cnt = 4;
  bool verbose = true;
  int buff_size = 80;
  char in_buff [buff_size];
  char bench [buff_size];
  char bench_2 [buff_size];
  char res_tbl [buff_size];

  // set up all static variables
  //individual::set_simulator_name(parse_string(parse_argument(
  //        in_buff, argc, argv, "-s"), in_buff, buff_size,
  //      "  simulator name: "));

  //individual::set_pattern_file("tmp.pat");

  individual::set_vector_count((int) parse_number(parse_argument(
          in_buff, argc, argv, "-v"), false, 1, INT_MAX,
        "  test vector count (> 0): "));

  parse_string(parse_argument(bench, argc, argv, "-b"), bench,
      buff_size, "  benchmark filename (table): ");

  parse_string(parse_argument(bench_2, argc, argv, "-bs"), bench_2,
      buff_size, "  benchmark filename (bench): ");

  parse_string(parse_argument(res_tbl, argc, argv, "-r"), res_tbl,
      buff_size, "  result filename: ");

  char *tmp_elm [] = {"Fault", "coverage", ":"};
  //char *tmp_elm [] = {"undetected", "faults", ":"};
  //individual::set_minimize(true);

  // set up all the variables necessary for the evolution process
  int pop_size = (int) parse_number(parse_argument(in_buff, argc,
        argv, "-ps"), false, 1, 1000,
      "  population size (1-1000): ");

  char ind_type = (char) parse_number(parse_argument(in_buff, argc,
        argv, "-it"), false, 0, 1, "  type of individual (0, 1): ");

  int ind_cells = (int) parse_number(parse_argument(in_buff, argc,
        argv, "-ic"), false, 1, 300,
      "  cells in each individual (1-300): ");

  float elite_ratio = parse_number(parse_argument(in_buff, argc,
        argv, "-er"), true, 0, 1, "  elite ratio (0.0-1.0): ");

  float extr_ratio = parse_number(parse_argument(in_buff, argc,
        argv, "-xr"), true, 0, 1, "  extremist ratio (0.0-1.0): ");

  float ones_ratio = parse_number(parse_argument(in_buff, argc,
        argv, "-or"), true, 0, 1, "  ones ratio (0.0-1.0): ");

  char tmp_buff [80];
  sprintf(tmp_buff, "  pick count (1-%d): ", pop_size);

  int pick_cnt = (int) parse_number(parse_argument(in_buff, argc,
        argv, "-pc"), false, 1, pop_size, tmp_buff);

  float cross_odds = parse_number(parse_argument(in_buff, argc,
        argv, "-co"), true, 0, 1, "  cross-over odds (0.0-1.0): ");

  float mutation_odds = parse_number(parse_argument(in_buff, argc,
        argv, "-mo"), true, 0, 1, "  mutation odds (0.0-1.0): ");

  int gen_cnt = (int) parse_number(parse_argument(in_buff, argc,
        argv, "-gc"), false, 1, INT_MAX,
      "  generation count (> 0): ");

  char sel_type = (char) parse_number(parse_argument(in_buff, argc,
        argv, "-st"), false, 0, 1, "  selection type (0/T, 1/R): ");

  char co_type = (char) parse_number(parse_argument(in_buff, argc,
        argv, "-ct"), false, 0, 2,
      "  cross-over type (0/S, 1/D, 2/U): ");

  cout << endl;

  // set the evaluator object of class individual
  generic_fitness *my_evaluator = new table_fitness (tmp_rules,
      tmp_rule_cnt, bench, ind_cells);

  individual::set_evaluator(my_evaluator);

  generic_fitness *tmp_evaluator = new simulation_fitness (tmp_rules,
      tmp_rule_cnt, "fsim", "tmp.pat", bench_2, tmp_elm, 3);

  // create an evolution object
  evolution my_evolution (time(0));

  // define the loop variables
  int max_cycles = 5;
  float evo_solution = 0;
  float evo_sim_solution = 0;
  float ran_solution = 0;
  float ran_sim_solution = 0;

  // repeat the measurement a few times
  for(int cycles = 0; cycles < max_cycles; cycles++) {
    // start measuring time
    cout << "Evolution ..." << endl << endl;
    if(verbose) cout << "Generation statistics: " << endl;
    random_solution::start_timer();

    // get the solution via the evolution process
    individual *solution = my_evolution.get_solution(pop_size,
        ind_type, ind_cells, elite_ratio, extr_ratio, ones_ratio,
        pick_cnt, cross_odds, mutation_odds, gen_cnt, sel_type,
        co_type, verbose);

    // stop measuring time
    random_solution::stop_timer();
    if(verbose) cout << endl;

    // print the results
    print_results("Evolution", random_solution::get_process_time(),
        random_solution::get_wall_time(), solution->get_fitness(),
        pop_size * gen_cnt);

    evo_solution += solution->get_fitness();

    evo_sim_solution += tmp_evaluator->get_fitness(solution,
        solution->get_vector_count());

    delete solution;

    // start measuring time
    cout << "Random selection ..." << endl << endl;
    random_solution::start_timer();

    // get the solution via the random-selection process
    solution = random_solution::get_solution(ind_type, ind_cells);

    // stop measuring time
    random_solution::stop_timer();

    // print the results
    print_results("Random selection",
        random_solution::get_process_time(),
        random_solution::get_wall_time(), solution->get_fitness(),
        random_solution::get_individuals());

    ran_solution += solution->get_fitness();

    ran_sim_solution += tmp_evaluator->get_fitness(solution,
        solution->get_vector_count());

    delete solution;
  }

  // print the measurement statistics
  cout << "Avg. solution fitness:" << endl;
  cout << "  evolution: " << (evo_solution / max_cycles) << endl;

  cout << "  evolution (sim.): "
    << (evo_sim_solution / max_cycles) << endl;

  cout << "  random sel.: " << (ran_solution / max_cycles) << endl;

  cout << "  random sel. (sim.): "
    << (ran_sim_solution / max_cycles) << endl;

  cout << endl;

  // clean up other variables
  delete my_evaluator;
  delete tmp_evaluator;

  // save the results to a file
  my_evolution.flush_results(res_tbl);

  return 0;
}

