// ****************************************************************************
//
//          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/>.
//
// ****************************************************************************

#include "Individual.h"

#include "biochemistry/mutations/mutators/DnaMutator.h"
#include "biochemistry/TranscriptionTerminationSequence.h"
#include "biochemistry/TranslationInitiationSequence.h"
#include "biochemistry/folding.h"
#include "Protein.h"

#include "utils/utility.h"
#include "Codon.h"

#include <algorithm>
#include <format>

namespace aevol {

auto Individual::make_empty() -> std::unique_ptr<Individual> {
  auto indiv = std::unique_ptr<Individual>(new Individual());

  indiv->annotated_chromosome_[std::to_underlying(Chrsm::A)] = AnnotatedChromosome::make_empty();

  if (exp_setup->diploid()) {
    indiv->annotated_chromosome_[std::to_underlying(Chrsm::B)] = AnnotatedChromosome::make_empty();
  }

  return indiv;
}

auto Individual::make_from_sequence(const std::string& seq) -> std::unique_ptr<Individual> {
  auto indiv = std::unique_ptr<Individual>(new Individual());

  auto chrsm_id = std::to_underlying(Chrsm::A);
  indiv->annotated_chromosome_[chrsm_id] = AnnotatedChromosome::make_from_sequence(seq);

  if (exp_setup->diploid()) {
    chrsm_id = std::to_underlying(Chrsm::B);
    indiv->annotated_chromosome_[chrsm_id] = AnnotatedChromosome::make_from_sequence(seq);
  }

  return indiv;
}

auto Individual::make_from_chromosomes(std::array<std::unique_ptr<AnnotatedChromosome>, 2> chromosomes)
    -> std::unique_ptr<Individual> {
  assert(exp_setup->diploid());
  auto indiv = std::unique_ptr<Individual>(new Individual());

  // Move the provided chromosomes as the new indiv's chromosomes
  indiv->annotated_chromosome_ = std::move(chromosomes);

  return indiv;
}

auto Individual::make_from_sequences(const std::string& sequence_chrsmA, const std::string& sequence_chrsmB)
    -> std::unique_ptr<Individual> {
  auto indiv = std::unique_ptr<Individual>(new Individual());

  indiv->annotated_chromosome_[std::to_underlying(Chrsm::A)] = AnnotatedChromosome::make_from_sequence(sequence_chrsmA);
  indiv->annotated_chromosome_[std::to_underlying(Chrsm::B)] = AnnotatedChromosome::make_from_sequence(sequence_chrsmB);

  return indiv;
}

/// Create an individual with random genome of length len whose fitness is better than that
/// corresponding to a flat phenotype
auto Individual::make_random(JumpingMT& prng, Dna::size_type len, double w_max, const PhenotypicTarget& target)
    -> std::unique_ptr<Individual> {
  auto indiv = std::unique_ptr<Individual>{};

  std::string seq;
  seq.resize(len);
  std::string seq2; // for diploid organisms
  seq2.resize(len);

  for(;;) {
    for (auto i = 0; i < len; ++i) {
      seq[i] = '0' + prng.random(NB_BASE);
    }
    if (exp_setup->diploid()) {
      for (auto i = 0; i < len; ++i) {
        seq2[i] = '0' + prng.random(NB_BASE);
      }
    }
    indiv = exp_setup->diploid() ? make_from_sequences(seq, seq2)
                                 : make_from_sequence(seq);

    indiv->evaluate(w_max, exp_setup->selection_pressure(), target);

    #ifdef BASE_2
    double r_compare = round((indiv->metabolic_error_ - target.fuzzy().get_geometric_area()) * 1E6) / 1E6;
    #elif BASE_4
    double r_compare = round((indiv->metabolic_error_ - target.fuzzy().get_geometric_area()) * 1E4) / 1E4;
    #endif

    if (r_compare < 0.0) {
      break;
    }
  }

  return indiv;
}

auto Individual::make_clone(const Individual& orig) -> std::unique_ptr<Individual> {
  auto indiv = std::unique_ptr<Individual>(new Individual());

  for (auto chrsm_id: chrsm_ids) {
    indiv->annotated_chromosome_[chrsm_id] = AnnotatedChromosome::make_clone(*orig.annotated_chromosome_[chrsm_id]);

    indiv->fitness_         = orig.fitness_;
    indiv->metabolic_error_ = orig.metabolic_error_;
  }

  return indiv;
}

Individual::~Individual() {
}

void Individual::evaluate(double w_max,
                          double selection_pressure,
                          const PhenotypicTarget& target) {
  locate_promoters();
  prom_compute_RNA();
  start_protein();
  compute_protein();
  translate_protein(w_max);
  compute_phenotype();
  normalize_phenotype(target.fuzzy().get_geometric_area());
  compute_metabolic_error(target);
  compute_fitness(selection_pressure);
}

void Individual::locate_promoters() {
  reset_promoter_lists();
  for (auto chrsm_id: chrsm_ids) {
    for (Dna::size_type dna_pos = 0 ; dna_pos < annotated_chromosome_[chrsm_id]->dna().length() ; ++dna_pos) {
      if (annotated_chromosome_[chrsm_id]->dna().length() < PROM_SIZE) continue;

      auto prom_dist_leading = Promoter::is_promoter_leading(annotated_chromosome_[chrsm_id]->dna(), dna_pos);
      auto prom_dist_lagging = Promoter::is_promoter_lagging(annotated_chromosome_[chrsm_id]->dna(), dna_pos);

      if (prom_dist_leading <= PROM_MAX_DIFF) {
        annotated_chromosome_[chrsm_id]->mutable_promoter_list().promoter_add(
            Promoter(dna_pos, prom_dist_leading, Strand::LEADING));
      }

      if (prom_dist_lagging <= PROM_MAX_DIFF) {
        annotated_chromosome_[chrsm_id]->mutable_promoter_list().promoter_add(
            Promoter(dna_pos, prom_dist_lagging, Strand::LAGGING));
      }
    }
  }
}

void Individual::prom_compute_RNA() {
  for (auto chrsm_id: chrsm_ids) {
    annotated_chromosome_[chrsm_id]->proteins_.clear();
    annotated_chromosome_[chrsm_id]->rna_list_.clear();

    // [farthest only] Data structure to ignore intermediate promoters
    // maps (strand, term_pos) to (promoter, rna_length)
    auto lookup = std::map<std::pair<Strand, Dna::size_type>, std::tuple<Promoter*, Dna::size_type>>{};

    for (auto prom_idx = 0; prom_idx < annotated_chromosome_[chrsm_id]->promoters().promoter_count(); ++prom_idx) {
      auto* prom = annotated_chromosome_[chrsm_id]->mutable_promoter_list().at(prom_idx);

      if (prom != nullptr) {
        const auto& dna       = annotated_chromosome_[chrsm_id]->dna();
        auto dna_length       = dna.length();
        auto prom_pos         = prom->pos();
        auto strand           = prom->strand();
        auto terminator_found = false;
        auto loop_size        = Dna::size_type{0};

        if (strand == Strand::LEADING) {
          /* Search for terminators */
          auto cur_pos =
              exp_setup->linear_chrsm() ? prom_pos + PROM_SIZE : mod(prom_pos + PROM_SIZE, dna_length);
          auto start_pos = cur_pos;

          while (!terminator_found) {
            if (TranscriptionTerminationSequence::is_terminator(dna, cur_pos, Strand::LEADING)) {
              terminator_found = true;
              if (not exp_setup->farthest()) {  // nominal case: add rna
                annotated_chromosome_[chrsm_id]->rna_list_.rna_add(prom, loop_size + TERM_SIZE, dna_length);
              } else {  // farthest:
                auto candidate_rna_length = loop_size + TERM_SIZE;
                auto [it, inserted]       = lookup.emplace(std::make_pair(Strand::LEADING, cur_pos),
                                                     std::make_tuple(prom, candidate_rna_length));
                if (not inserted) {
                  // There already is a promoter whose corresponding terminator is that which we've just found
                  // => keep only the longest candidate rna (i.e. the "farthest" promoter)
                  if (candidate_rna_length > std::get<1>(it->second)) {
                    std::get<0>(it->second) = prom;
                    std::get<1>(it->second) = candidate_rna_length;
                  }
                }
              }
            } else {
              cur_pos = exp_setup->linear_chrsm() ? cur_pos + 1 : mod(cur_pos + 1, dna_length);

              if (cur_pos == start_pos) {
                break;
              }
              if (exp_setup->linear_chrsm() && cur_pos > dna_length - TERM_SIZE) {  // No room for a terminator
                break;
              }
            }

            ++loop_size;
          }
        } else {  // Strand::LAGGING
          /* Search for terminator */
          auto cur_pos =
              exp_setup->linear_chrsm() ? prom_pos - PROM_SIZE : mod(prom_pos - PROM_SIZE, dna_length);
          auto start_pos = cur_pos;

          while (!terminator_found) {
            if (TranscriptionTerminationSequence::is_terminator(dna, cur_pos, Strand::LAGGING)) {
              terminator_found = true;
              if (not exp_setup->farthest()) {  // nominal case: add rna
                annotated_chromosome_[chrsm_id]->rna_list_.rna_add(prom, loop_size + TERM_SIZE, dna_length);
              } else {  // farthest:
                auto candidate_rna_length = loop_size + TERM_SIZE;
                auto [it, inserted] = lookup.emplace(std::make_pair(Strand::LAGGING, cur_pos),
                                                     std::make_tuple(prom, candidate_rna_length));
                if (not inserted) {
                  // There already is a promoter whose corresponding terminator is that which we've just found
                  // => keep only the longest candidate rna (i.e. the "farthest" promoter)
                  if (candidate_rna_length > std::get<1>(it->second)) {
                    std::get<0>(it->second) = prom;
                    std::get<1>(it->second) = candidate_rna_length;
                  }
                }
              }
            } else {
              cur_pos = exp_setup->linear_chrsm() ? cur_pos - 1 : mod(cur_pos - 1, dna_length);

              if (cur_pos == start_pos) {
                break;
              }
              if (exp_setup->linear_chrsm() && cur_pos < TERM_SIZE - 1) {  // No room for a terminator
                break;
              }
            }

            ++loop_size;
          }
        }
      }
    }

    if (exp_setup->farthest()) {
      assert(annotated_chromosome_[chrsm_id]->rna_list_.size() == 0);
      for (const auto& lookup_elt: lookup) {
        annotated_chromosome_[chrsm_id]->rna_list_.rna_add(std::get<0>(lookup_elt.second),
                                                           std::get<1>(lookup_elt.second),
                                                           annotated_chromosome_[chrsm_id]->dna().length());
      }
    }
  }
}

void Individual::start_protein() {
  for (auto chrsm_id: chrsm_ids) {
    for (auto& rna: annotated_chromosome_[chrsm_id]->mutable_rna_list()) {
      int32_t dna_length = annotated_chromosome_[chrsm_id]->dna().length_;

      #ifdef BASE_2
      if (rna.to_compute_start_pos_) {
        rna.rbs_positions_.clear();

        int32_t rna_length = rna.length();
        auto strand        = rna.strand_;
        if (rna.is_init_) {
          int32_t s_pos = rna.begin;
          if (rna_length >= 21) {
            if (exp_setup->linear_chrsm()) {
              assert(s_pos < dna_length);
              assert(s_pos >= 0);
            }
            if (strand == Strand::LEADING) {
              s_pos += PROM_SIZE;
              s_pos = s_pos >= dna_length ? s_pos - dna_length : s_pos;
            } else {
              s_pos -= PROM_SIZE;
              s_pos = s_pos < 0 ? static_cast<int>(dna_length) + s_pos : s_pos;
            }

            for (int32_t loop_size = 0; loop_size < rna_length - DO_TRANSLATION_LOOP; loop_size++) {
              int32_t c_pos = s_pos;
              if (strand == Strand::LEADING) {
                c_pos += loop_size;
                c_pos = c_pos >= dna_length ? c_pos - dna_length : c_pos;
              } else {
                c_pos -= loop_size;
                c_pos = c_pos < 0 ? dna_length + c_pos : c_pos;
              }

              if (TranslationInitiationSequence::is_translation_initiation_sequence(
                      annotated_chromosome_[chrsm_id]->dna(), c_pos, strand)) {
                rna.rbs_positions_.push_back(c_pos);
              }
            }
          }
        }
      }
      #elif BASE_4
      if (rna.is_init_ && rna.length() >= DO_TRANSLATION_LOOP) {
        int c_pos = rna.begin;

        if (rna.strand_ == Strand::LEADING) {
          c_pos += PROM_SIZE;
          c_pos = c_pos >= dna_length ? c_pos - dna_length : c_pos;
        } else {
          c_pos -= PROM_SIZE;
          c_pos = c_pos < 0 ? dna_length + c_pos : c_pos;
        }

        int loop_size = 0;
        while (loop_size + DO_TRANSLATION_LOOP < rna.length()) {
          // If there is a translation initiation sequence at current pos...
          if (TranslationInitiationSequence::
              is_translation_initiation_sequence(annotated_chromosome_[chrsm_id]->dna(), c_pos, rna.strand_)) {
            rna.rbs_positions_.push_back(c_pos);
          }

          if (rna.strand_ == Strand::LEADING) {
            c_pos++;
            c_pos = c_pos >= dna_length ? c_pos - dna_length : c_pos;
          } else {
            c_pos--;
            c_pos = c_pos < 0 ? dna_length + c_pos : c_pos;
          }
          loop_size++;
        }
      }
      #endif
    }
  }
}

void Individual::compute_protein() {
  for (auto chrsm_id: chrsm_ids) {
    const auto& dna = annotated_chromosome_[chrsm_id]->dna();
    const auto& dna_length = dna.length();

    for (auto& rna: annotated_chromosome_[chrsm_id]->mutable_rna_list()) {
      if (rna.is_init_) {
        for (const auto& rbs_position : rna.rbs_positions_) { // For each RBS (i.e. ~ candidate protein)
          // Compute position of protein's first Amino-Acid
          auto first_aa_pos =
              mod(rna.strand_ == Strand::LEADING ? rbs_position + RBS_SIZE : rbs_position - RBS_SIZE, dna_length);

          auto already_processed_rna_length = (rna.strand_ == Strand::LEADING)
              ? mod(first_aa_pos - rna.first_transcribed_pos(), dna_length)
              : mod(rna.first_transcribed_pos() - first_aa_pos, dna_length);

          auto cur_aa_pos = first_aa_pos;
          while (rna.length() - already_processed_rna_length >= CODON_SIZE) {
            auto is_protein = (rna.strand_ == Strand::LEADING)
                ? is_stop_codon(dna.get_lead(cur_aa_pos), dna.get_lead(cur_aa_pos + 1), dna.get_lead(cur_aa_pos + 2))
                : is_stop_codon(dna.get_lag(cur_aa_pos), dna.get_lag(cur_aa_pos - 1), dna.get_lag(cur_aa_pos - 2));

            if (is_protein) {
              auto prot_length = (rna.strand_ == Strand::LEADING)
                  ? mod(cur_aa_pos - first_aa_pos, dna_length) / CODON_SIZE
                  : mod(first_aa_pos - cur_aa_pos, dna_length) / CODON_SIZE;

              auto last_base_stop_codon_pos = (rna.strand_ == Strand::LEADING)
                  ? mod(cur_aa_pos + 2, dna_length)
                  : mod(cur_aa_pos - 2, dna_length);

              if (prot_length > 0) {
                auto new_prot = new Protein(first_aa_pos,
                                            last_base_stop_codon_pos,
                                            prot_length,
                                            rna.strand_,
                                            rna.e,
                                            &rna);

                annotated_chromosome_[chrsm_id]->proteins_.add(new_prot);
                rna.is_coding_ = true;
              }

              break;
            }

            cur_aa_pos = (rna.strand_ == Strand::LEADING)
                  ? mod(cur_aa_pos + CODON_SIZE, dna_length)
                  : mod(cur_aa_pos - CODON_SIZE, dna_length);

            already_processed_rna_length += CODON_SIZE;
          }
        }
      }
    }
  }
}

void Individual::translate_protein(double w_max) {
  for (auto chrsm_id: chrsm_ids) {
    int32_t dna_length = annotated_chromosome_[chrsm_id]->dna().length();

    for (const auto& prot: annotated_chromosome_[chrsm_id]->proteins()) {
      if (not prot->is_duplicate()) {
        int c_pos = prot->position_first_aa();

        if (prot->strand() == Strand::LEADING) {
          #ifdef BASE_2
          while (prot->codon_list_size() < prot->size() && prot->codon_list_size() < 64 * 3) {
            auto value = decltype(prot->codon_list(std::declval<size_t>())){0};
            for (int8_t i = 0; i < 3; i++) {
              if (annotated_chromosome_[chrsm_id]->dna().get_lead(c_pos + i) == '1')
                value += 1 << (CODON_SIZE - i - 1);
            }

            prot->add_codon(value);
          #elif BASE_4
          while (prot->codon_list_size() < prot->size()) {
            prot->add_codon(bases_to_codon_value(annotated_chromosome_[chrsm_id]->dna().get_lead(c_pos),
                                                 annotated_chromosome_[chrsm_id]->dna().get_lead(c_pos + 1),
                                                 annotated_chromosome_[chrsm_id]->dna().get_lead(c_pos + 2)));
          #endif

            c_pos += CODON_SIZE;
            if (exp_setup->linear_chrsm()) {
              assert(c_pos < dna_length);
            } else {
              c_pos = c_pos >= dna_length ? c_pos - dna_length : c_pos;
            }
          }
        } else { // LAGGING
          #ifdef BASE_2
          while (prot->codon_list_size() < prot->size() && prot->codon_list_size() < 64 * 3) {
            auto value = decltype(prot->codon_list(std::declval<size_t>())){0};
            for (int8_t i = 0; i < 3; i++) {
              if (annotated_chromosome_[chrsm_id]->dna().get_lag(c_pos - i) == '1')
                value += 1 << (CODON_SIZE - i - 1);
            }

            prot->add_codon(value);
          #elif BASE_4
          while (prot->codon_list_size() < prot->size()) {

            prot->add_codon(bases_to_codon_value(annotated_chromosome_[chrsm_id]->dna().get_lag(c_pos),
                                                 annotated_chromosome_[chrsm_id]->dna().get_lag(c_pos - 1),
                                                 annotated_chromosome_[chrsm_id]->dna().get_lag(c_pos - 2)));
          #endif

            c_pos -= CODON_SIZE;
            c_pos = c_pos < 0 ? c_pos + dna_length : c_pos;
          }
        }

        #ifdef BASE_2
        if (prot->codon_list_size() >= 64 * 3) {
          std::ofstream last_gener_file("aevol_run.log", std::ofstream::out);
          last_gener_file << "Stop translating protein before end (length is greater than 196" << std::endl;
          std::cout << "Stop translating protein before end (length is greater than 196" << std::endl;
          last_gener_file.close();
        }

        double M = 0.0;
        double W = 0.0;
        double H = 0.0;
        #elif BASE_4
        //  --------------------------------
        //  1) Compute values for M, W and H
        //  --------------------------------
        auto base_m = exp_setup->aa_2_mwh_value_mapping().aa_base_m();
        auto base_w = exp_setup->aa_2_mwh_value_mapping().aa_base_w();
        auto base_h = exp_setup->aa_2_mwh_value_mapping().aa_base_h();

        int8_t base_m_size = exp_setup->aa_2_mwh_value_mapping().aa_base_m_size();
        int8_t base_w_size = exp_setup->aa_2_mwh_value_mapping().aa_base_w_size();
        int8_t base_h_size = exp_setup->aa_2_mwh_value_mapping().aa_base_h_size();

        long double M = 0.0;
        long double W = 0.0;
        long double H = 0.0;
        #endif

        int32_t nb_m = 0;
        int32_t nb_w = 0;
        int32_t nb_h = 0;

        #ifdef BASE_2
        bool bin_m = false;  // Initializing to false will yield a conservation of the high weight bit
        bool bin_w = false;  // when applying the XOR operator for the Gray to standard conversion
        bool bin_h = false;

        for (auto i = decltype(prot->codon_list_size()){0}; i < prot->codon_list_size(); ++i) {
          switch (prot->codon_list(i)) {
            case CODON_M0: {
              // M codon found
              nb_m++;

              // Convert Gray code to "standard" binary code
              bin_m ^= false;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

              // A lower-than-the-previous-lowest weight bit was found, make a left bitwise shift
              //~ M <<= 1;
              M *= 2;

              // Add this nucleotide's contribution to M
              if (bin_m)
                M += 1;

              break;
            }
            case CODON_M1: {
              // M codon found
              nb_m++;

              // Convert Gray code to "standard" binary code
              bin_m ^= true;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

              // A lower-than-the-previous-lowest bit was found, make a left bitwise shift
              //~ M <<= 1;
              M *= 2;

              // Add this nucleotide's contribution to M
              if (bin_m)
                M += 1;

              break;
            }
            case CODON_W0: {
              // W codon found
              nb_w++;

              // Convert Gray code to "standard" binary code
              bin_w ^= false;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

              // A lower-than-the-previous-lowest weight bit was found, make a left bitwise shift
              //~ W <<= 1;
              W *= 2;

              // Add this nucleotide's contribution to W
              if (bin_w)
                W += 1;

              break;
            }
            case CODON_W1: {
              // W codon found
              nb_w++;

              // Convert Gray code to "standard" binary code
              bin_w ^= true;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

              // A lower-than-the-previous-lowest weight bit was found, make a left bitwise shift
              //~ W <<= 1;
              W *= 2;

              // Add this nucleotide's contribution to W
              if (bin_w)
                W += 1;

              break;
            }
            case CODON_H0:
            case CODON_START:  // Start codon codes for the same amino-acid as H0 codon
            {
              // H codon found
              nb_h++;

              // Convert Gray code to "standard" binary code
              bin_h ^= false;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

              // A lower-than-the-previous-lowest weight bit was found, make a left bitwise shift
              //~ H <<= 1;
              H *= 2;

              // Add this nucleotide's contribution to H
              if (bin_h)
                H += 1;

              break;
            }
            case CODON_H1: {
                // H codon found
                nb_h++;

                // Convert Gray code to "standard" binary code
                bin_h ^= true;  // as bin_m was initialized to false, the XOR will have no effect on the high weight bit

                // A lower-than-the-previous-lowest weight bit was found, make a left bitwise shift
                //~ H <<= 1;
                H *= 2;

                // Add this nucleotide's contribution to H
                if (bin_h)
                  H += 1;

                break;
            }
          }
        }
        #elif BASE_4
        auto raw_m = std::vector<int8_t>{};
        auto raw_w = std::vector<int8_t>{};
        auto raw_h = std::vector<int8_t>{};
        for (auto i = decltype(prot->codon_list_size()){0}; i < prot->codon_list_size(); ++i) {
          auto amino_acid = codon_value_to_aminoacid(prot->codon_list(prot->codon_list_size() - i - 1));
          if (base_m[std::to_underlying(amino_acid)] != -1) {
            raw_m.push_back(base_m[std::to_underlying(amino_acid)]);
          }
          if (base_w[std::to_underlying(amino_acid)] != -1) {
            raw_w.push_back(base_w[std::to_underlying(amino_acid)]);
          }
          if (base_h[std::to_underlying(amino_acid)] != -1) {
            raw_h.push_back(base_h[std::to_underlying(amino_acid)]);
          }
        }

        double m_base_exponent = 1.25;
        double w_base_exponent = 1.25;
        double h_base_exponent = 1.25;
        from_base_n_reversed(gray_to_base_n(raw_m, base_m_size), M, m_base_exponent);
        from_base_n_reversed(gray_to_base_n(raw_w, base_w_size), W, w_base_exponent);
        from_base_n_reversed(gray_to_base_n(raw_h, base_h_size), H, h_base_exponent);
        nb_m = raw_m.size();
        nb_w = raw_w.size();
        nb_h = raw_h.size();
        #endif

        //  ----------------------------------------------------------------------------------
        //  2) Normalize M, W and H values in [0;1] according to number of codons of each kind
        //  ----------------------------------------------------------------------------------
        #ifdef BASE_2
        prot->set_m(nb_m != 0 ? M / (pow(2, nb_m) - 1) : 0.5);
        prot->set_w(nb_w != 0 ? W / (pow(2, nb_w) - 1) : 0.0);
        prot->set_h(nb_h != 0 ? H / (pow(2, nb_h) - 1) : 0.5);
        #elif BASE_4
        if (nb_m != 0) {
          prot->set_m(normalize(M, base_m_size, m_base_exponent, nb_m));
        } else {
          prot->set_m(0.5);
        }

        if (nb_w != 0) {
          prot->set_w(normalize(W, base_w_size, w_base_exponent, nb_w));
        } else {
          prot->set_w(0.0);
        }

        if (nb_h != 0) {
          prot->set_h(normalize(H, base_h_size, h_base_exponent, nb_h));
        } else {
          prot->set_h(0.5);
        }
        #endif

        //  ------------------------------------------------------------------------------------
        //  3) Normalize M, W and H values according to the allowed ranges (defined in macros.h)
        //  ------------------------------------------------------------------------------------
        // x_min <= M <= x_max
        // w_min <= W <= w_max
        // h_min <= H <= h_max
        prot->set_m((X_MAX - X_MIN) * prot->m() + X_MIN);
        prot->set_w((w_max - W_MIN) * prot->w() + W_MIN);
        prot->set_h((H_MAX - H_MIN) * prot->h() + H_MIN);

        if (nb_m == 0 || nb_w == 0 || nb_h == 0 || prot->w() == 0.0 || prot->h() == 0.0) {
          prot->set_is_functional(false);
        } else {
          prot->set_is_functional(true);
        }
      }
    }

    std::map<int32_t, Protein*> lookup;

    for (const auto& prot: annotated_chromosome_[chrsm_id]->proteins()) {
      if (not prot->is_duplicate() && prot->strand() == Strand::LEADING) {
        if (lookup.find(prot->position_first_aa()) == lookup.end()) {
          lookup[prot->position_first_aa()] = prot;
        } else {
          assert(not aevol::exp_setup->farthest());
          lookup[prot->position_first_aa()]->add_to_e(prot->e());
          lookup[prot->position_first_aa()]->add_rna(&prot->rna_list().front());
          prot->set_duplicate();
        }
      }
    }

    lookup.clear();

    for (const auto& prot: annotated_chromosome_[chrsm_id]->proteins()) {
      if (not prot->is_duplicate() && prot->strand() == Strand::LAGGING) {
        if (lookup.find(prot->position_first_aa()) == lookup.end()) {
          lookup[prot->position_first_aa()] = prot;
        } else {
          assert(not aevol::exp_setup->farthest());
          lookup[prot->position_first_aa()]->add_to_e(prot->e());
          lookup[prot->position_first_aa()]->add_rna(&prot->rna_list().front());
          prot->set_duplicate();
        }
      }
    }
  }
}

void Individual::compute_phenotype() {
    auto activ_phenotype = exp_setup->fuzzy_factory().get_fuzzy();
    auto inhib_phenotype = exp_setup->fuzzy_factory().get_fuzzy();


    std::vector<Protein*> protein_vector;
    auto chrsm_id = std::to_underlying(Chrsm::A);
    for (const auto& prot : annotated_chromosome_[chrsm_id]->proteins()) {
      if (not prot->is_duplicate()) {
        protein_vector.push_back(prot);
      }
    }

    if (exp_setup->diploid()) {
      chrsm_id = std::to_underlying(Chrsm::B);
      for (const auto& prot: annotated_chromosome_[chrsm_id]->proteins()) {
        if (not prot->is_duplicate()) {
          protein_vector.push_back(prot);
        }
      }
    }

    std::sort(protein_vector.begin(), protein_vector.end(), [](Protein* a, Protein* b) { return *a < *b; });

    for (auto prot : protein_vector) {
    if (fabs(prot->w()) >= 1e-15 && fabs(prot->h()) >= 1e-15) {
      if (prot->is_functional()) {
        bool verbose = false;
        if (prot->h() > 0)
          activ_phenotype->add_triangle(prot->m(), prot->w(), prot->h() * prot->e(), verbose);
        else
          inhib_phenotype->add_triangle(prot->m(), prot->w(), prot->h() * prot->e(), verbose);
      }
    }
  }

  activ_phenotype->clip(AbstractFuzzy::max,  Y_MAX);
  inhib_phenotype->clip(AbstractFuzzy::min, -Y_MAX);

  activ_phenotype->simplify();
  inhib_phenotype->simplify();

  phenotype_ = exp_setup->fuzzy_factory().get_fuzzy();
  phenotype_->copy(*activ_phenotype);
  phenotype_->add(*inhib_phenotype);
  phenotype_->clip(AbstractFuzzy::min, Y_MIN);
  phenotype_->simplify();
}

void Individual::normalize_phenotype(fuzzy_area_t area) {
  if (exp_setup->normalize_phenotype())
    phenotype_->normalize(area);
}

void Individual::compute_metabolic_error(const PhenotypicTarget& target) {
  auto delta = exp_setup->fuzzy_factory().get_fuzzy();

  delta->copy(*phenotype_);
  delta->sub(target.fuzzy(), false);

  metabolic_error_ = delta->get_geometric_area();
}

void Individual::compute_fitness(double selection_pressure) {
  // Special case: if the individual has no genes its fitness is set to 0
  fitness_ = ((not exp_setup->diploid() and proteins().size() == 0) or
              (exp_setup->diploid() and proteins(Chrsm::A).size() + proteins(Chrsm::B).size() == 0))
                 ? 0.
                 : exp(-selection_pressure * static_cast<double>(metabolic_error_));
}

}
