#include <cstring>
#include <fstream>
#include <iostream>
#include <wait.h>
#include "transitions.h"
#include "generic_individual.h"
#include "generic_fitness.h"
#include "individual.h"
#include "simulation_fitness.h"

using namespace std;

/**
 * a constructor for a rule-based individual
 */
simulation_fitness::simulation_fitness(int *p_rules, int p_rule_cnt,
    char *p_sim_name, char *p_pat_file, char *p_bench_file,
    char **p_rep_elm, int p_rep_elm_size) {
  rule_tbl = 0;
  // also sets the block variables
  read_rule_table(p_rules, p_rule_cnt);

  simulator_name = 0;
  pattern_file = 0;
  bench_file = 0;
  report_element = 0;

  set_simulator_name(p_sim_name);
  set_pattern_file(p_pat_file);
  set_bench_file(p_bench_file);
  set_report_element(p_rep_elm, p_rep_elm_size);
}

/**
 * a constructor for a matrix-based individual
 */
simulation_fitness::simulation_fitness(int p_cells, char *p_sim_name,
    char *p_pat_file, char *p_bench_file, char **p_rep_elm,
    int p_rep_elm_size) {
  rule_tbl = 0;
  block_variants = GF_VARIANTS_UNLIMITED;
  block_bitsize = p_cells;

  simulator_name = 0;
  pattern_file = 0;
  bench_file = 0;
  report_element = 0;

  set_simulator_name(p_sim_name);
  set_pattern_file(p_pat_file);
  set_bench_file(p_bench_file);
  set_report_element(p_rep_elm, p_rep_elm_size);
}

/**
 * a destructor
 */
simulation_fitness::~simulation_fitness() {
  delete [] simulator_name;
  delete [] pattern_file;
  delete [] bench_file;

  int i = 0;

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

  delete [] report_element;
}

/**
 * sets the fault simulator's name
 */
bool simulation_fitness::set_simulator_name(char *p_sim_name) {
  if(p_sim_name == 0) return false;

  if(simulator_name != 0) delete [] simulator_name;
  simulator_name = new char [strlen(p_sim_name) + 1];
  strcpy(simulator_name, p_sim_name);

  return true;
}

/**
 * sets the intermediate pattern file's name
 */
bool simulation_fitness::set_pattern_file(char *p_pat_file) {
  if(p_pat_file == 0) return false;

  if(pattern_file != 0) delete [] pattern_file;
  pattern_file = new char [strlen(p_pat_file) + 1];
  strcpy(pattern_file, p_pat_file);

  return true;
}

/**
 * sets the benchmark file's name
 */
bool simulation_fitness::set_bench_file(char *p_bench_file) {
  if(p_bench_file == 0) return false;

  if(bench_file != 0) delete [] bench_file;
  bench_file = new char [strlen(p_bench_file) + 1];
  strcpy(bench_file, p_bench_file);

  return true;
}

/**
 * sets a name of the retrieved report element
 */
bool simulation_fitness::set_report_element(char **p_shape,
    int p_size) {
  if((p_size <= 0) || (p_shape == 0)) return false;

  // free the element if it already exists
  if(report_element != 0) {
    int i = 0;

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

    delete [] report_element;
  }

  // create the element
  report_element = new char * [p_size + 1];

  // copy in its parts
  for(int i = 0; i < p_size; i++) {
    report_element[i] = new char [strlen(p_shape[i]) + 1];
    strcpy(report_element[i], p_shape[i]);
  }

  // secure the table's end
  report_element[p_size] = 0;

  return true;
}

/**
 * simulates a matrix-based CA and prints the obtained vectors
 * into a file
 */
int simulation_fitness::output_matrix_vectors(ofstream& p_file,
    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];

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

  // simulate the corresponding automaton for a number of times
  int i;

  for(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 print it to the file
    p_file << (i + 1) << ": ";

    for(int j = 0; j < p_cells; j++)
      p_file << (int) (state[j] = state_buff[j]);

    p_file << endl;
  }

  // finally, return the number of the produced vectors
  return i;
}

/**
 * simulates a rule-based CA and prints the obtained vectors
 * into a file
 */
int simulation_fitness::output_rule_vectors(ofstream& p_file,
    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
  int i;

  for(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 print it to the file
    p_file << (i + 1) << ": ";

    for(int j = 0; j < p_cells; j++)
      p_file << (int) (state[j] = state_buff[j]);

    p_file << endl;
  }

  // finally, return the number of the produced vectors
  return i;
}

/**
 * constructs a CA from an individual and iterates
 * a few cycles of the automaton; the results get
 * written to a user specified file
 */
int simulation_fitness::output_vectors(generic_individual *p_ind,
    int p_vector_cnt) {
  // check the desired number of vectors
  if(p_vector_cnt < 1) return 0;

  // open the output file
  ofstream out_file;
  out_file.open(pattern_file);
  if(!out_file.is_open()) return SF_OV_FILE_ERROR;

  // create auxiliary structures
  int ret_val = 0;
  int type = ((individual *) p_ind)->get_type();
  int cells = ((individual *) p_ind)->get_cells();
  int size = ((individual *) p_ind)->get_size();
  GENE seed [cells];

  // simulate the automaton according to the chromosome type
  if(type == TYPE_MATRIX_SEED) {
    // get the c-matrix and seed
    GENE matrix [size - cells];

    ((individual *) p_ind)->get_matrix(matrix);
    ((individual *) p_ind)->get_seed(seed);

    ret_val = output_matrix_vectors(out_file, matrix, seed, cells,
        p_vector_cnt);
  }
  else if(type == TYPE_RULES_SEED) {
    // get the rules and seed
    int rules [cells];

    if(((individual *) p_ind)->get_rules(rules)) {
      ((individual *) p_ind)->get_seed(seed);

      ret_val = output_rule_vectors(out_file, rules, seed, cells,
          p_vector_cnt);
    }
  }

  out_file.close();

  // return the number of produced vectors
  return ret_val;
}

/**
 * parse a report file for the value of a given element
 */
float simulation_fitness::parse_report(char *p_filename) {
  // open the report file or use the standard input
  istream *report_file = &cin;

  if(p_filename != 0) {
    report_file = new ifstream;
    ((ifstream *) report_file)->open(p_filename);

    if(!((ifstream *) report_file)->is_open()) {
      delete report_file;
      return SF_PR_FILE_ERROR;
    }
  }

  // setup the line buffer and such
  int state = 0;
  bool found = false;
  char word_buff [80];
  float res = SF_PR_VALUE_NOT_FOUND;

  // read the report file word by word and search for the element
  while(!found && !report_file->eof()) {
    report_file->width(80);
    (*report_file) >> word_buff;

    if(strcmp(word_buff, report_element[state]) == 0) {
      state++;
  
      if(report_element[state] == 0) {
        found = true;
        if(!report_file->eof()) (*report_file) >> res;
      }
    }
    else state = 0;
  }

  // clean up the auxiliary structures
  if(p_filename != 0) {
    ((ifstream *) report_file)->close();
    delete report_file;
  }

  // return the parsed element's value
  return res;
}

/**
 * implements the parent behaviour
 */
float simulation_fitness::parent_behaviour(int *p_pipe,
    pid_t p_pid) {
  int wait_res;
  float res;

  // try to fit the stdin onto the output of the pipe
  if(dup2(p_pipe[0], 0) == -1) {
    cerr << "Error in dup2()" << endl;
    exit(1);
  }

  // close the unused descriptors
  if((close(p_pipe[0]) == -1)
      || (close(p_pipe[1]) == -1)) {
    cerr << "Error in close()" << endl;
    exit(1);
  }

  // wait for the child to finish
  waitpid(p_pid, &wait_res, 0);

  // parse the results of the simulation
  res = parse_report(0);
  if(individual::get_minimize()) res *= -1;

  // check the results
  if(res == SF_PR_FILE_ERROR) {
    cerr << "Error in parse_report(): File error" << endl;
    exit(1);
  }
  else if(res == SF_PR_VALUE_NOT_FOUND) {
    cerr << "Error in parse_report(): Value not found" << endl;
    exit(1);
  }

  // finally, return the parsed value
  return res;
}

/**
 * implements the child behaviour
 */
void simulation_fitness::child_behaviour(int *p_pipe,
    generic_individual *p_ind, int p_vector_cnt) {
  // try to fit the stdout to the input of the pipe
  if(dup2(p_pipe[1], 1) == -1) {
    cerr << "Error in dup2()" << endl;
    exit(1);
  }

  // close the unused descriptors
  if((close(p_pipe[0]) == -1)
      || (close(p_pipe[1]) == -1)) {
    cerr << "Error in close()" << endl;
    exit(1);
  }

  // generate test vectors
  int vect_res = output_vectors(p_ind, p_vector_cnt);

  // check the results
  if(vect_res == SF_OV_FILE_ERROR) {
    cerr << "Error in output_vectors(): File error" << endl;
    exit(1);
  }

  // run the external fault simulator
  if(vect_res > 0) {
    // evaluating a valid individual
    if(execl(simulator_name, simulator_name, "-t", pattern_file,
          bench_file, 0) == -1) {
      cerr << "Error in execl()" << endl;
      exit(1);
    }
  }
  else {
    // evaluating an invalid individual
    int i = 0;

    while(report_element[i] != 0)
      cout << report_element[i++] << " ";

    cout << DEFAULT_FITNESS;
    exit(0);
  }
}

/**
 * a fitness function
 */
float simulation_fitness::get_fitness(generic_individual *p_ind,
    int p_vector_cnt) {
  float res;
  pid_t pid;
  int comm_pipe [2];

  switch(((individual *) p_ind)->get_type()) {
    case TYPE_MATRIX_SEED:
    case TYPE_RULES_SEED:
      // try to open a communication pipe
      if(pipe(comm_pipe) == -1) {
        cerr << "Error in pipe()" << endl;
        exit(1);
      }

      // attempt to fork
      if((pid = fork()) == -1) {
        cerr << "Error in fork()" << endl;
        exit(1);
      }

      // parent/child behaviour:
      if(pid) {
        // a positive, non-zero pid means that this is the parent
        res = parent_behaviour(comm_pipe, pid);
      }
      else {
        // a zero pid means that this is the child
        child_behaviour(comm_pipe, p_ind, p_vector_cnt);
      }

      break;

    default:
      break;
  }

  return res;
}

