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


#ifndef AEVOL_INDIVIDUAL_H_
#define AEVOL_INDIVIDUAL_H_

#include <array>
#include <cstring>
#include <vector>
#include <map>
#include <set>
#include <tuple>

#include "ae_types.h"
#include "biochemistry/mutations/mutators/EukaryoteIndividualMutator.h"
#include "biochemistry/mutations/mutators/ProkaryoteIndividualMutator.h"
#include "ExpSetup.h"
#include "JumpingMT.h"
#include "phenotype/fuzzy/Vector_Fuzzy.h"
#include "phenotype/PhenotypicTarget.h"
#include "AnnotatedChromosome.h"
#include "Chrsm.h"

namespace aevol {

class Promoter;
class Rna;

class Individual {
 public:
  Individual(const Individual&) = delete;
  Individual(Individual&&) = delete;
  Individual& operator=(const Individual&) = delete;
  Individual& operator=(Individual&&) = delete;

 private:
  Individual() = default;

 public:
  static auto make_empty() -> std::unique_ptr<Individual>;
  static auto make_from_sequence(const std::string& seq) -> std::unique_ptr<Individual>;
  static auto make_from_chromosomes(std::array<std::unique_ptr<AnnotatedChromosome>, 2> chromosomes)
      -> std::unique_ptr<Individual>;
  static auto make_from_sequences(const std::string& sequence_chrsmA, const std::string& sequence_chrsmB)
      -> std::unique_ptr<Individual>;
  static auto make_random(JumpingMT& prng, Dna::size_type len, double w_max, const PhenotypicTarget& target)
      -> std::unique_ptr<Individual>;
  static auto make_clone(const Individual& orig) -> std::unique_ptr<Individual>;

  ~Individual();

  // Member type
  #ifdef DIPLOID
  using mutator_type = EukaryoteIndividualMutator;
  #else   // DIPLOID
  using mutator_type = ProkaryoteIndividualMutator;
  #endif  // DIPLOID

  // Accessors
  const auto& phenotype() const { return *phenotype_; };
  auto fitness() const { return fitness_; };
  auto metabolic_error() const { return metabolic_error_; };

  auto annotated_chromosome(Chrsm chrsm) const -> const AnnotatedChromosome& {
    return *annotated_chromosome_[std::to_underlying(chrsm)];
  }
  const auto& dna(Chrsm chrsm) const {
    return annotated_chromosome_[std::to_underlying(chrsm)]->dna();
  }
  auto& mutable_dna(Chrsm chrsm) {
    return annotated_chromosome_[std::to_underlying(chrsm)]->mutable_dna();
  }
  auto proteins(Chrsm chrsm) const -> const ProteinList& {
    return annotated_chromosome_[std::to_underlying(chrsm)]->proteins();
  }
  auto mutable_protein_list(Chrsm chrsm) -> ProteinList& {
    return annotated_chromosome_[std::to_underlying(chrsm)]->mutable_protein_list();
  }
  auto rnas(Chrsm chrsm) const {
    return annotated_chromosome_[std::to_underlying(chrsm)]->rnas();
  }
  auto mutable_rna_list(Chrsm chrsm) {
    return annotated_chromosome_[std::to_underlying(chrsm)]->mutable_rna_list();
  }

  auto annotated_chromosome() const -> const AnnotatedChromosome& { return annotated_chromosome(Chrsm::A); };
  const auto& dna() const { return dna(Chrsm::A); }
  auto& mutable_dna() { return mutable_dna(Chrsm::A); }
  auto proteins() const -> const ProteinList& { return proteins(Chrsm::A); }
  auto mutable_protein_list() -> ProteinList& { return mutable_protein_list(Chrsm::A); }
  auto rnas() const { return rnas(Chrsm::A); }
  auto mutable_rna_list() { return mutable_rna_list(Chrsm::A); }

  void force_fitness_value(double fitness) { fitness_ = fitness; }
  void force_metabolic_error_value(double metabolic_error) { metabolic_error_ = metabolic_error; }

  void evaluate(double w_max,
                double selection_pressure,
                const PhenotypicTarget& target);

  void locate_promoters();
  void prom_compute_RNA();
  void start_protein();
  void compute_protein();
  void translate_protein(double w_max);
  void compute_phenotype();
  void normalize_phenotype(fuzzy_area_t area);
  void compute_metabolic_error(const PhenotypicTarget& target);
  void compute_fitness(double selection_pressure);

  void reset_promoter_lists() {
    for (auto chrsm_id: chrsm_ids) {
      annotated_chromosome_[chrsm_id]->reset_promoter_lists();
    }
  };

 protected:
  fuzzy_unique_ptr phenotype_ = nullptr;
  double fitness_;
  double metabolic_error_;

 public:
  double* fitness_by_env_id_;
  double* metabolic_error_by_env_id_;

 protected:
  std::array<std::unique_ptr<AnnotatedChromosome>, 2> annotated_chromosome_ = {nullptr, nullptr};
};

// Compare function for lexicographical order on DNA sequence
inline auto dna_lexicographical_compare = [](const Individual* const lhs, const Individual* const rhs) {
#ifdef DIPLOID
  auto c1 = std::strcmp(lhs->dna(Chrsm::A).data(), rhs->dna(Chrsm::A).data());
  return c1 < 0 or (c1 == 0 and std::strcmp(lhs->dna(Chrsm::B).data(), rhs->dna(Chrsm::B).data()) < 0);
#else
  return std::strcmp(lhs->dna().data(), rhs->dna().data()) < 0;
#endif
};

} // namespace aevol

#endif // AEVOL_INDIVIDUAL_H_
