// ****************************************************************************
//
//          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 <cassert>
#include <charconv>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <list>
#include <string>
#include <sstream>

#include "SelectionScheme.h"
#include "SelectionScope.h"
#include "utils/utility.h"

#ifdef BASE_4
  #include "aevol_4b/biochemistry/AminoAcid.h"
#endif

// =================================================================
//                            Project Files
// =================================================================
#include "ParamReader.h"

#include "Gaussian.h"

namespace aevol {

// =================================================================
//                          Class declarations
// =================================================================


//##############################################################################
//                                                                             #
//                             Class ParamReader                               #
//                                                                             #
//##############################################################################

// =================================================================
//                    Definition of static attributes
// =================================================================

// =================================================================
//                            Public Methods
// =================================================================
auto ParamReader::read_file(const std::filesystem::path& file_name) -> std::unique_ptr<ParamValues> {
  auto param_values = std::make_unique<ParamValues>();

  // Open parameter file
  auto file = std::ifstream(file_name);
  if (not file) {
    std::cerr << "error: failed to open file " << file_name << std::endl;
    exit(EXIT_FAILURE);
  }

  int32_t cur_line = 0;

  // TODO : write parameter_line = new ParameterLine(param_file_) => ParameterLine::ParameterLine(char*)
  while (auto parameter_line = line(file, cur_line)) {
    interpret_line(*parameter_line, cur_line, file_name.c_str(), *param_values);
  }

  // Check consistency of parameter values
  check_consistency(*param_values);

  return param_values;
}

// =================================================================
//                           Protected Methods
// =================================================================
void ParamReader::check_consistency(ParamValues& params) {
  if (params.chromosome_maximal_length == -1)
    params.chromosome_maximal_length = params.max_genome_length;
  if (params.chromosome_minimal_length == -1)
    params.chromosome_minimal_length = params.min_genome_length;
  if (params.chromosome_minimal_length > params.chromosome_initial_length) {
    printf("ERROR: CHROMOSOME_INITIAL_LENGTH is lower than CHROMOSOME_MINIMAL_LENGTH\n");
    exit(EXIT_FAILURE);
  }
  if (params.chromosome_maximal_length < params.chromosome_initial_length) {
    printf("ERROR: CHROMOSOME_INITIAL_LENGTH is higher than CHROMOSOME_MAXIMAL_LENGTH\n");
    exit(EXIT_FAILURE);
  }
}

/*!
  \brief Get a line in a file and format it

  \return formated line

  \see format_line(char* line)
*/
auto ParamReader::line(std::ifstream& file, int32_t& cur_line) -> std::optional<std::vector<std::string>> {
  auto line = std::string{};
  while (!file.eof()) {
    std::getline(file, line);
    ++cur_line;
    auto formated_line = format_line(line);
    if (formated_line) return formated_line;
  }

  // Reached EOF
  return std::nullopt;
}

/*!
  \brief Format a line by parsing it and the words inside

  \param line original line in char*
*/
auto ParamReader::format_line(const std::string& line) -> std::optional<std::vector<std::string>> {
  auto formated_line = std::vector<std::string>{};

  auto word = std::string{};
  auto line_iss = std::istringstream{line};

  // Parse line
  while (line_iss >> word) {
    // Search for comments
    auto hash_pos = word.find('#');
    if (hash_pos != std::string::npos) {
      // Remove trailing characters (# included)
      word.resize(hash_pos);
      // If there was content before the hash, add it
      if (not word.empty()) formated_line.push_back(word);
      // Stop processing line (remaining stuff is part of comment)
      break;
    }

    // Add word
    formated_line.push_back(word);
  }

  if (formated_line.empty()) return std::nullopt;
  else return formated_line;
}

void ParamReader::interpret_line(const std::vector<std::string>& words,
                                 int32_t cur_line,
                                 const char* file_name,
                                 ParamValues& values) {
  if (words.empty()) {
    std::cerr << std::format("{}:{}: error: unexpected error while reading parameter file", file_name, cur_line);
    exit(EXIT_FAILURE);
  }
  if (words[0] == "STRAIN_NAME") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "MIN_TRIANGLE_WIDTH") {
    stale_option_error(file_name, cur_line, words[0], "its value is fixed to 0");
  }
  else if (words[0] == "MAX_TRIANGLE_WIDTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MAX_TRIANGLE_WIDTH <value>"});
    values.w_max = stod(words[1]);
  }
  else if (words[0] == "ENV_AXIS_FEATURES") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "ENV_SEPARATE_SEGMENTS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TREE_MODE") {
    printf("ERROR in param file \"%s\" on line %" PRId32 ": "
           "Tree mode management has been removed.\n",
           file_name, cur_line);
    exit(EXIT_FAILURE);
  }
  else if (words[0] == "RECORD_LIGHT_TREE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "MORE_STATS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "DUMP_PERIOD") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "DUMP_STEP") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "BACKUP_STEP") {
    stale_option_error(file_name, cur_line, words[0], "use CHECKPOINT_STEP instead");
  }
  else if (words[0] == "CHECKPOINT_STEP") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"CHECKPOINT_STEP <value>"});
    values.checkpoint_frequency = stol(words[1]);
  }
  else if (words[0] == "BIG_BACKUP_STEP") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TREE_STEP") {
    stale_option_error(file_name, cur_line, words[0], "use RECORD_TREE instead");
  } else if (words[0] == "RECORD_TREE") {
    auto usage = std::vector<std::string>{"RECORD_TREE OFF", "RECORD_TREE ON <frequency>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "ON") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      auto val = decltype(values.tree_output_frequency)::value_type{};
      auto [ptr, ec] = std::from_chars(words[2].data(), words[2].data() + words[2].size(), val);
      if (ec != std::errc()) {
        std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                  << "\tinvalid value for option " << words[0] << "\n";
        exit(EXIT_FAILURE);
      }
      values.tree_output_frequency = val;
    } else if (words[1] == "OFF") {
      values.tree_output_frequency = std::nullopt;
    } else {
      std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                << "\tinvalid value for option " << words[0] << "\n";
      exit(EXIT_FAILURE);
    }
  } else if (words[0] == "STATS_BEST") {
    auto usage = std::vector<std::string>{"STATS_BEST OFF", "STATS_BEST ON <frequency>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "ON") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      auto val = decltype(values.best_indiv_stats_output_frequency)::value_type{};
      auto [ptr, ec] = std::from_chars(words[2].data(), words[2].data() + words[2].size(), val);
      if (ec != std::errc()) {
        std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                  << "\tinvalid value for option " << words[0] << "\n";
        exit(EXIT_FAILURE);
      }
      values.best_indiv_stats_output_frequency = val;
    } else if (words[1] == "OFF") {
      values.best_indiv_stats_output_frequency = std::nullopt;
    } else {
      std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                << "\tinvalid value for option " << words[0] << "\n";
      exit(EXIT_FAILURE);
    }
  } else if (words[0] == "STATS_POP") {
    auto usage = std::vector<std::string>{"STATS_POP OFF", "STATS_POP ON <frequency>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "ON") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      auto val = decltype(values.pop_stats_output_frequency)::value_type{};
      auto [ptr, ec] = std::from_chars(words[2].data(), words[2].data() + words[2].size(), val);
      if (ec != std::errc()) {
        std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                  << "\tinvalid value for option " << words[0] << "\n";
        exit(EXIT_FAILURE);
      }
      values.pop_stats_output_frequency = val;
    } else if (words[1] == "OFF") {
      values.pop_stats_output_frequency = std::nullopt;
    } else {
      std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                << "\tinvalid value for option " << words[0] << "\n";
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "NB_GENER") {
    stale_option_error(file_name, cur_line, words[0], "use command line arguments of aevol_run instead");
  }
  else if (words[0] == "INITIAL_GENOME_LENGTH") {
    stale_option_error(file_name, cur_line, words[0], "use CHROMOSOME_INITIAL_LENGTH instead");
  }
  else if (words[0] == "CHROMOSOME_INITIAL_LENGTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"CHROMOSOME_INITIAL_LENGTH <value>"});
    values.chromosome_initial_length = stol(words[1]);
  }
  else if (words[0] == "MIN_GENOME_LENGTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MIN_GENOME_LENGTH <value>"});
    if (words[1] == "NONE")
    {
      values.min_genome_length = 1; // Must not be 0
    }
    else
    {
      values.min_genome_length = stol(words[1]);
      if (values.min_genome_length == 0)
      {
        printf("ERROR in param file \"%s\" on line %" PRId32 " : MIN_GENOME_LENGTH must be > 0.\n",
               file_name, cur_line);
        exit(EXIT_FAILURE);
      }
    }
  }
  else if (words[0] == "MAX_GENOME_LENGTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MAX_GENOME_LENGTH <value>"});
    if (words[1] == "NONE")
    {
      values.max_genome_length = INT32_MAX;
    }
    else
    {
      values.max_genome_length = stol(words[1]);
    }
  }
  else if (words[0] == "INIT_POP_SIZE") {
    stale_option_error(file_name, cur_line, words[0], "use GRID_SIZE <width> <height> instead");
  }
  else if (words[0] == "WORLD_SIZE") {
    stale_option_error(file_name, cur_line, words[0], "use GRID_SIZE <width> <height> instead");
  }
  else if (words[0] == "GRID_SIZE") {
    if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], {"GRID_SIZE <width> <height>"});
    values.grid_width   = stoi(words[1]);
    values.grid_height  = stoi(words[2]);
  }
  else if (words[0] == "POP_STRUCTURE") {
    stale_option_error(file_name, cur_line, words[0], "use GRID_SIZE <width> <height> instead");
  }
  else if (words[0] == "MIGRATION_NUMBER") {
    stale_option_error(file_name, cur_line, words[0],
                       "use INDIV_MIXING instead.\n\tusage: INDIV_MIXING WELL_MIXED|NONE|PARTIAL <n>");
  }
  else if (words[0] == "INDIV_MIXING") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"INDIV_MIXING WELL_MIXED|NONE|PARTIAL"});
    if (words[1] == "WELL_MIXED")
      values.well_mixed = true;
    else if (words[1] == "NONE")
      values.well_mixed = false;
    else if (words[1] == "PARTIAL")
      values.partial_mix_nb_permutations = stol(words[2]);
    else {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": unknown mixing option.\n", file_name, cur_line);
      printf("usage: INDIV_MIXING WELL_MIXED|NONE|PARTIAL <n>\n");
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "INIT_METHOD") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "POINT_MUTATION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"POINT_MUTATION_RATE <value>"});
    values.point_mutation_rate = stod(words[1]);
  }
  else if (words[0] == "SMALL_INSERTION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"SMALL_INSERTION_RATE <value>"});
    values.small_insertion_rate = stod(words[1]);
  }
  else if (words[0] == "SMALL_DELETION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"SMALL_DELETION_RATE <value>"});
    values.small_deletion_rate = stod(words[1]);
  }
  else if (words[0] == "MAX_INDEL_SIZE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MAX_INDEL_SIZE <value>"});
    values.max_indel_size = stol(words[1]);
  }
  else if (words[0] == "DUPLICATION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"DUPLICATION_RATE <value>"});
    values.duplication_rate = stod(words[1]);
  }
  else if (words[0] == "DELETION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"DELETION_RATE <value>"});
    values.deletion_rate = stod(words[1]);
  }
  else if (words[0] == "TRANSLOCATION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"TRANSLOCATION_RATE <value>"});
    values.translocation_rate = stod(words[1]);
  }
  else if (words[0] == "INVERSION_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"INVERSION_RATE <value>"});
    values.inversion_rate = stod(words[1]);
  }
  else if (words[0] == "DUPLICATION_PROPORTION") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "DELETION_PROPORTION") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TRANSLOCATION_PROPORTION") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "INVERSION_PROPORTION") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "RECOMB_ON_ALIGN") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"RECOMB_ON_ALIGN true|false"});
    if (words[1] == "true") {
      values.recomb_on_align_ = true;
    }
    else if (words[1] == "false") {
      values.recomb_on_align_ = false;
    }
    else {
      std::cout << "ERROR in param file \"" << file_name << "\" on line " << cur_line <<
          ": unknown RECOMB_ON_ALIGN option (use true/false)." << std::endl;
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "ALIGN_FUNCTION") {
    auto usage = std::vector<std::string>{"ALIGN_FUNCTION LINEAR <min_value> <max_value>",
                                          "ALIGN_FUNCTION SIGMOID <mean_value> <lambda>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "LINEAR") {
      if (words.size() != 4) nb_params_error(file_name, cur_line, words[0], usage);
      auto min = align::score_t{};
      auto& min_str = words[2];
      std::from_chars(min_str.data(), min_str.data() + min_str.size(), min);
      auto max = align::score_t{};
      auto& max_str = words[3];
      std::from_chars(max_str.data(), max_str.data() + max_str.size(), max);
      values.align_score_function_ = align::ScoreFunction::make_linear_function(min, max);
    } else if (words[1] == "SIGMOID") {
      if (words.size() != 4) nb_params_error(file_name, cur_line, words[0], usage);
      auto mean = align::score_t{};
      auto& mean_str = words[2];
      std::from_chars(mean_str.data(), mean_str.data() + mean_str.size(), mean);
      auto lambda = double{};
      auto& lambda_str = words[3];
      std::from_chars(lambda_str.data(), lambda_str.data() + lambda_str.size(), lambda);
      values.align_score_function_ = align::ScoreFunction::make_sigmoid_function(mean, lambda);
    }
  }
  else if (words[0] == "ALIGN_MAX_SHIFT") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "ALIGN_W_ZONE_H_LEN") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "ALIGN_MATCH_BONUS") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"ALIGN_MATCH_BONUS <value>"});
    auto& str = words[1];
    std::from_chars(str.data(), str.data() + str.size(), values.align_match_bonus_);
  }
  else if (words[0] == "ALIGN_MISMATCH_COST") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"ALIGN_MISMATCH_COST <value>"});
    auto& str = words[1];
    std::from_chars(str.data(), str.data() + str.size(), values.align_mismatch_cost_);
  }
  else if (words[0] == "MAX_ALIGN_SIZE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MAX_ALIGN_SIZE <value>"});
    auto& str = words[1];
    std::from_chars(str.data(), str.data() + str.size(), values.max_align_size_);
  }
  else if (words[0] == "NEIGHBOURHOOD_RATE") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"NEIGHBOURHOOD_RATE <value>"});
    auto& str = words[1];
    std::from_chars(str.data(), str.data() + str.size(), values.align_neighbourhood_rate_);
  }
  else if (words[0] == "STOCHASTICITY") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SELECTION_SCHEME") {
    auto usage = std::vector<std::string>{"SELECTION_SCHEME linear_ranking <selection_pressure>",
                                          "SELECTION_SCHEME exponential_ranking <selection_pressure>",
                                          "SELECTION_SCHEME fitness_proportionate <selection_pressure>",
                                          "SELECTION_SCHEME fittest"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "linear_ranking") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      values.selection_scheme    = SelectionScheme::RANK_LINEAR;
      values.selection_pressure  = stod(words[2]);
    }
    else if (words[1] == "exponential_ranking") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      values.selection_scheme    = SelectionScheme::RANK_EXPONENTIAL;
      values.selection_pressure  = stod(words[2]);
    }
    else if (words[1] == "fitness_proportionate") {
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      values.selection_scheme    = SelectionScheme::FITNESS_PROPORTIONATE;
      values.selection_pressure  = stod(words[2]);
    }
    else if (words[1] == "fittest") {
      values.selection_scheme = SelectionScheme::FITTEST;
    }
    else {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": unknown selection scheme \"%s\".\n",
             file_name, cur_line, words[1].c_str());
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "SELFING_CONTROL") {
    #ifdef SEX
    auto usage = std::vector<std::string>{"SELFING_CONTROL false",
                                          "SELFING_CONTROL true <selfing_rate>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "true") {
      values.selfing_control_ = true;
      if (words.size() != 3) nb_params_error(file_name, cur_line, words[0], usage);
      const auto& selfing_rate_str = words[2];
      std::from_chars(selfing_rate_str.data(), selfing_rate_str.data() + selfing_rate_str.size(), values.selfing_rate_);

      if (values.selfing_rate_ < 0 || values.selfing_rate_ > 1) {
        std::cout << "ERROR in param file \"" << file_name << "\" on line " << cur_line <<
                     ": selfing rate must be in [0, 1]." << std::endl;
        exit(EXIT_FAILURE);
      }
    }
    else if (words[1] == "false") {
      values.selfing_control_ = false;
    }
    else {
      std::cout << "ERROR in param file \"" << file_name << "\" on line " << cur_line <<
             ": unknown selfing control option (use true/false)." << std::endl;
      exit(EXIT_FAILURE);
    }
    #else // SEX
    std::cout << "ERROR in param file \"" << file_name << "\" on line " << cur_line <<
        ": selfing is only valid in the eukaryote setting." << std::endl;
    exit(EXIT_FAILURE);
    #endif // SEX
  }
  else if (words[0] == "SELECTION_SCOPE") {
    auto usage = std::vector<std::string>{"SELECTION_SCOPE global",
                                          "SELECTION_SCOPE local <scope_x> <scope_y>"};
    if (words.size() < 2) nb_params_error(file_name, cur_line, words[0], usage);
    if (words[1] == "global") {
      values.selection_scope = SelectionScope::GLOBAL;
    }
    else if (words[1] == "local") {
      if (words.size() != 4) nb_params_error(file_name, cur_line, words[0], usage);
      values.selection_scope    = SelectionScope::LOCAL;
      values.selection_scope_x  = stoi(words[2]);
      values.selection_scope_y  = stoi(words[3]);
    }
    else
    {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": unknown selection scope \"%s\".\n",
             file_name, cur_line, words[1].c_str());
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "SEED") {
    static bool seed_already_set = false;
    if (seed_already_set)
    {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": duplicate entry for SEED.\n",
             file_name, cur_line);
      exit(EXIT_FAILURE);
    }
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"SEED <value>"});
    values.seed      = stol(words[1]);
    seed_already_set = true;
  }
  else if (words[0] == "MUT_SEED") {
    static bool mut_seed_already_set = false;
    if (mut_seed_already_set)
    {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": duplicate entry for MUT_SEED.\n",
             file_name, cur_line);
      exit(EXIT_FAILURE);
    }
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"MUT_SEED <value>"});
    values.mut_seed      = stol(words[1]);
    mut_seed_already_set = true;
  }
  else if (words[0] == "STOCH_SEED") {
    static bool stoch_seed_already_set = false;
    if (stoch_seed_already_set)
    {
      printf("ERROR in param file \"%s\" on line %" PRId32
                 ": duplicate entry for STOCH_SEED.\n",
             file_name, cur_line);
      exit(EXIT_FAILURE);
    }
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"STOCH_SEED <value>"});
    values.stoch_seed      = stol(words[1]);
    stoch_seed_already_set = true;
  }
  else if (words[0] == "WITH_4PTS_TRANS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "WITH_ALIGNMENTS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "WITH_TRANSFER") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "REPL_TRANSFER_WITH_CLOSE_POINTS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SWAP_GUS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TRANSFER_INS_RATE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TRANSFER_REPL_RATE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "REPL_TRANSFER_DETACH_RATE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TRANSLATION_COST") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "ENV_ADD_POINT") {
    // custom_points
    printf("ERROR in param file \"%s\" on line %" PRId32
               ": Custom points management has been removed.\n",
        file_name, cur_line);
    exit(EXIT_FAILURE);
  }
  else if (words[0] == "ENV_ADD_GAUSSIAN" || words[0] == "ENV_GAUSSIAN") {
    if (words.size() != 4) nb_params_error(file_name, cur_line, words[0], {"ENV_ADD_GAUSSIAN <height> <mean> <width>"});
    values.std_env_gaussians.push_back(Gaussian(stod(words[1]), stod(words[2]), stod(words[3])));
  }
  else if (words[0] == "ENV_SAMPLING") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"ENV_SAMPLING <value>"});
    values.env_sampling = stoi(words[1]);
  }
  else if (words[0] == "ENV_VARIATION") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"ENV_VARIATION none"});
    if (words[1] != "none") {
      std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
                << "\t" << words[0] << " option is currently not available.\n"
                << "\t" << "Please use \"none\" or remove line from parameter file.\n";
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "ENV_NOISE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PHENOTYPE_NORMALIZATION") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"PHENOTYPE_NORMALIZATION true|false"});
    if (words[1] == "true") {
      values.normalize_phenotype = true;
    } else if (words[1] == "false") {
      values.normalize_phenotype = false;
    } else {
      std::cout << "ERROR in param file \"" << file_name << "\" on line " << cur_line <<
          ": unknown PHENOTYPE_NORMALIZATION option (use true/false)." << std::endl;
      exit(EXIT_FAILURE);
    }
  }
  else if (words[0] == "SECRETION_FITNESS_CONTRIB") {
    stale_option_error(file_name, cur_line, words[0], "use SECRETION_CONTRIB_TO_FITNESS instead");
  }
  else if (words[0] == "SECRETION_CONTRIB_TO_FITNESS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SECRETION_DIFFUSION_PROP") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SECRETION_DEGRADATION_PROP") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SECRETION_INITIAL") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "SECRETION_COST") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "ALLOW_PLASMIDS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PLASMID_INITIAL_LENGTH") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PLASMID_INITIAL_GENE") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PLASMID_MINIMAL_LENGTH") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PLASMID_MAXIMAL_LENGTH") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "CHROMOSOME_MINIMAL_LENGTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"CHROMOSOME_MINIMAL_LENGTH <value>"});
    values.chromosome_minimal_length = stoi(words[1]);
  }
  else if (words[0] == "CHROMOSOME_MAXIMAL_LENGTH") {
    if (words.size() != 2) nb_params_error(file_name, cur_line, words[0], {"CHROMOSOME_MAXIMAL_LENGTH <value>"});
    values.chromosome_maximal_length = stoi(words[1]);
  }
  else if (words[0] == "PROB_HORIZONTAL_TRANS") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "PROB_PLASMID_HT") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TUNE_DONOR_ABILITY") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "TUNE_RECIPIENT_ABILITY") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "DONOR_COST") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "RECIPIENT_COST") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "COMPUTE_PHEN_CONTRIB_BY_GU") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "LOG") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  else if (words[0] == "FUZZY_FLAVOR") {
    if (words.size() != 2)
      nb_params_error(file_name, cur_line, words[0], {"FUZZY_FLAVOR VECTOR|DISCRETE_DOUBLE_TABLE"});
    if (words[1] == "VECTOR") {
      values.fuzzy_flavor = FuzzyFlavor::VECTOR;
    }
    else if (words[1] == "DISCRETE_DOUBLE_TABLE") {
      values.fuzzy_flavor = FuzzyFlavor::DISCRETE_DOUBLE_TABLE;
    }
    else {
      printf("ERROR in param file \"%s\" on line %" PRId32 " : unknown fuzzy flavor %s.\n",
             file_name, cur_line, words[1].c_str());
      exit(EXIT_FAILURE);
    }
  }  else if (words[0] == "SIMD_METADATA_FLAVOR") {
    stale_option_error(file_name, cur_line, words[0]);
  }
  #ifdef BASE_4
  else if (words[0] == "AMINO_ACID") {
    if (!values.mwh_bases_redefined) {
      for (auto i = 0; i < aevol_4b::NB_AMINO_ACIDS; i++) {
        values.aa_base_m[i] = (int8_t)-1;
        values.aa_base_w[i] = (int8_t)-1;
        values.aa_base_h[i] = (int8_t)-1;
      }

      values.mwh_bases_redefined = true;
    }

    if (words.size() < 3)
      nb_params_error(file_name,
                      cur_line,
                      words[0],
                      {"AMINO_ACID <amino_acid_trigram> <amino_acid_contribution>+"},
                      {"AMINO_ACID arg W1", "AMINO_ACID phe M0 H3"});

    aevol_4b::AminoAcid amino_acid;
    if (values.str_to_aminoacid.find(std::string(words[1])) != values.str_to_aminoacid.end()) {
      amino_acid = values.str_to_aminoacid.at(std::string(words[1]));
    } else {
      printf("ERROR in param file \"%s\" on line %" PRId32 " : unrecognized amino-acid string : %s\n",
             file_name,
             cur_line,
             words[1].c_str());
      exit(EXIT_FAILURE);
    }

    for (auto i = size_t{2}; i < words.size(); i++) {
      if (words[i].size() < 2) {
        printf("ERROR in param file \"%s\" on line %" PRId32 " : malformed base digit : %s\n",
               file_name,
               cur_line,
               words[i].c_str());
        exit(EXIT_FAILURE);
      }

      int8_t digit;

      try {
        digit = (int8_t)std::stoi(std::string(&(words[i][1])));
      } catch (std::exception const& e) {
        printf("ERROR in param file \"%s\" on line %" PRId32 " : malformed base digit (invalid value) : %s\n",
               file_name,
               cur_line,
               words[i].c_str());
        exit(EXIT_FAILURE);
      }

      if (digit < 0) {
        printf("ERROR in param file \"%s\" on line %" PRId32
               " : negative values aren't supported for base digits : %s\n",
               file_name,
               cur_line,
               words[i].c_str());
        exit(EXIT_FAILURE);
      }

      switch (words[i][0]) {
        case 'M':
          values.aa_base_m[std::to_underlying(amino_acid)] = digit;
          break;
        case 'W':
          values.aa_base_w[std::to_underlying(amino_acid)] = digit;
          break;
        case 'H':
          values.aa_base_h[std::to_underlying(amino_acid)] = digit;
          break;
      }
    }
  }
  #endif
  else
  {
    printf("ERROR in param file \"%s\" on line %" PRId32 " : undefined key word \"%s\"\n",
           file_name,
           cur_line,
           words[0].c_str());
    exit(EXIT_FAILURE);
  }
}

void ParamReader::nb_params_error(const std::string file_name,
                                  int32_t line,
                                  std::string option,
                                  std::vector<std::string> usage,
                                  std::vector<std::string> examples) {
  std::cerr << std::format("{}:{}: error: incorrect number of parameters for option {}\n", file_name, line, option);
  if (not usage.empty()) {
    std::cerr << std::format("\tusage:\t{}\n", usage.front());
  }
  for (auto i = size_t{1}; i < usage.size(); ++i) {
    std::cerr << std::format("\t   or:\t{}\n", usage[i]);
  }
  if (not examples.empty()) {
    std::cerr << "\texamples:\n";
    for (const auto& example : examples) {
      std::cerr << std::format("\t\t{}\n", example);
    }
  }
  exit(EXIT_FAILURE);
}

void ParamReader::stale_option_error(const std::string file_name,
                                     int32_t cur_line,
                                     const std::string option,
                                     const std::string comment) {
  std::cout << "ERROR in param file: " << file_name << ':' << cur_line << ":\n"
            << "\t" << option << " is no longer a valid option.\n";
  if (not comment.empty()) std::cout << "\t" << comment << ".\n";
  exit(EXIT_FAILURE);
}

} // namespace aevol
