// ****************************************************************************
//
//          Aevol - An in silico experimental evolution platform
//
// ****************************************************************************
//
// Copyright: See the AUTHORS file provided with the package or <www.aevol.fr>
// Web: http://www.aevol.fr/
// E-mail: See <http://www.aevol.fr/contact/>
// Original Authors : Guillaume Beslon, Carole Knibbe, David Parsons
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ****************************************************************************

// =================================================================
//                              Libraries
// =================================================================
#include <format>
#include <sys/types.h>
#include <sys/stat.h>
#include <set>

// =================================================================
//                            Project Files
// =================================================================
#include "ExpSetup.h"
#include "JumpingMT.h"
#include "utility.h"


//#include "GzHelpers.h"

namespace aevol {

// Static instance
std::unique_ptr<ExpSetup> exp_setup = std::make_unique<ExpSetup>();

ExpSetup::ExpSetup() {
  // -------------------------------------------------------------- Selection
  selection_ = std::make_unique<SelectionParams>();

  #ifdef LINEAR_CHRSM
  linear_chrsm_ = true;
  #endif
  #ifdef DIPLOID
  diploid_ = true;
  #endif
  #ifdef SEX
  sex_ = true;
  #endif
}

std::unique_ptr<ExpSetup> ExpSetup::make_from_checkpoint(std::ifstream& is, mxifstream& mxis, grid_xy_type pop_size) {
  auto exp_s = std::make_unique<ExpSetup>();

  exp_s->load_from_checkpoint(is, mxis, pop_size);

  return exp_s;
}

void ExpSetup::load_from_params(const ParamValues& params) {
  // ------------------------------------------------------ Fuzzy factory parameters
  fuzzy_factory_ = std::make_unique<FuzzyFactory>(params.fuzzy_flavor,
                                                  params.env_sampling,
                                                  params.grid_width * params.grid_height);

  // ------------------------------------------------------ W_MAX
  exp_setup->set_w_max(params.w_max);

  // ------------------------------------------------------ Phenotype normalization
  exp_setup->normalize_phenotype_ = params.normalize_phenotype;

  // ------------------------------------------------------ Mutation parameters
  auto param_mut = std::make_shared<MutationParams>();
  param_mut->set_point_mutation_rate(params.point_mutation_rate);
  param_mut->set_small_insertion_rate(params.small_insertion_rate);
  param_mut->set_small_deletion_rate(params.small_deletion_rate);
  param_mut->set_max_indel_size(params.max_indel_size);
  param_mut->set_duplication_rate(params.duplication_rate);
  param_mut->set_deletion_rate(params.deletion_rate);
  param_mut->set_translocation_rate(params.translocation_rate);
  param_mut->set_inversion_rate(params.inversion_rate);
  exp_setup->set_mutation_parameters(param_mut);

  // -------------------------------------- Parameters for sequence alignements
  if (params.recomb_on_align_) {
    exp_setup->set_alignment_parameters(align::AlignmentParams::make_from_params(params.align_match_bonus_,
                                                                                 params.align_mismatch_cost_,
                                                                                 *params.align_score_function_,
                                                                                 params.align_neighbourhood_rate_,
                                                                                 params.max_align_size_));
  }

  // ---------------------------------------------------------------- Selection
  exp_setup->selection().set_selection_scheme(params.selection_scheme);
  exp_setup->selection().set_selection_pressure(params.selection_pressure);

  exp_setup->selection().set_selection_scope(params.selection_scope);
  exp_setup->selection().set_selection_scope_x(params.selection_scope_x);
  exp_setup->selection().set_selection_scope_y(params.selection_scope_y);

  #ifdef SEX
  exp_setup->selection().set_selfing_control(params.selfing_control_);
  exp_setup->selection().set_selfing_rate(params.selfing_rate_);
  #endif

  exp_setup->selection().set_fitness_function(params.fitness_function);

  //------------------------------------------------------ Genome length limits
  exp_setup->set_genome_limits(params.min_genome_length, params.max_genome_length);

  #ifdef BASE_4
  // -------------------------------------------------- MWH bases configuration
  if (params.mwh_bases_redefined) {
    auto redefined_mapping = std::make_unique<aevol_4b::AA_2_MWH_Value_Mapping>();
    redefined_mapping->reset_aa_base_m(params.aa_base_m);
    redefined_mapping->reset_aa_base_w(params.aa_base_w);
    redefined_mapping->reset_aa_base_h(params.aa_base_h);
    exp_setup->reset_aa_2_mwh_value_mapping(std::move(redefined_mapping));
  }
  #endif
}

void ExpSetup::write_to_checkpoint(std::ofstream& os, mxofstream& mxos) const {
  // Fuzzy parameters
  mxos << "FuzzyFlavor: " << to_string(fuzzy_factory_->flavor()) << "\n";
  mxos << "FuzzySampling: " << fuzzy_factory_->sampling() << "\n";

  os << std::format("W_MAX: {}\n", w_max_);
  os << "NormalizedPhenotype: " << (normalize_phenotype_ ? "ON" : "OFF") << '\n';

  // Selection parameters
  selection_->write_to_checkpoint(os, mxos);

  // Mutation rates etc...
  mut_params_->write_to_checkpoint(os, mxos);

  // Parameters for sequence alignements
  os << "Alignments: " << (alignment_params_ ? "ON" : "OFF") << '\n';
  if (alignment_params_) {
    alignment_params_->write_to_checkpoint(os, mxos);
  }

  // Genome Length limits
  os << "MinGenomeLength: " << min_genome_length() << '\n';
  os << "MaxGenomeLength: " << max_genome_length()  << '\n';
}

void ExpSetup::load_from_checkpoint(std::ifstream& is, mxifstream& mxis, grid_xy_type pop_size) {
  std::string str;

  // Read fuzzy factory parameters
  auto fuzzy_flavor = FuzzyFlavor{};
  get_expected_or_throw(mxis, "FuzzyFlavor:", fuzzy_flavor);
  auto sampling = decltype(std::declval<FuzzyFactory>().sampling()){0};
  get_expected_or_throw(mxis, "FuzzySampling:", sampling);
  fuzzy_factory_ = std::make_unique<FuzzyFactory>(fuzzy_flavor, sampling, pop_size);

  // Read w_max
  get_expected_or_throw(is, "W_MAX:", w_max_);
  get_expected_or_throw(is, "NormalizedPhenotype:", str);
  normalize_phenotype_ = (str == "ON");

  // Read selection parameters
  selection_->load_from_checkpoint(is, mxis);

  // Mutation rates etc...
  mut_params_ = std::shared_ptr<MutationParams>(MutationParams::make_from_checkpoint(is, mxis));

  // Parameters for sequence alignements
  get_expected_or_throw(is, "Alignments:", str);
  if (str == "ON") {
    alignment_params_ = align::AlignmentParams::make_from_checkpoint(is, mxis);
  }

  // Genome Length limits
  auto min_genome_length = dna_size_type{};
  auto max_genome_length = dna_size_type{};
  get_expected_or_throw(is, "MinGenomeLength:", min_genome_length);
  get_expected_or_throw(is, "MaxGenomeLength:", max_genome_length);
  genome_limits_ = std::make_unique<GenomeLimits>(min_genome_length, max_genome_length);
}

std::ostream& operator<<(std::ostream& os, const ExpSetup& obj) {
  os << std::format("W_MAX: {}\n", obj.w_max());
  os << obj.selection() << "\n";

  // Mutation rates etc...
  os << obj.mutation_params();

  // Genome Length limits
  os << "MinGenomeLength: " << obj.min_genome_length() << '\n';
  os << "MaxGenomeLength: " << obj.max_genome_length();

  return os;
}

} // namespace aevol
