// ****************************************************************************
//
//          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 "AnnotatedChromosome.h"

#include <cstring>
#include <numeric>

#include "ExpSetup.h"

namespace aevol {

AnnotatedChromosome::AnnotatedChromosome(const std::string& seq) {
  dna_ = Dna::make_from_sequence(seq);
  dna_->set_chrsm(this);
}

AnnotatedChromosome::AnnotatedChromosome() {
}

auto AnnotatedChromosome::make_empty() -> std::unique_ptr<AnnotatedChromosome> {
  return std::unique_ptr<AnnotatedChromosome>(new AnnotatedChromosome());
  // Private ctor -> can't use make_unique
}

auto AnnotatedChromosome::make_from_sequence(const std::string& seq)
    -> std::unique_ptr<AnnotatedChromosome> {
  return std::unique_ptr<AnnotatedChromosome>(new AnnotatedChromosome(seq));
  // Private ctor -> can't use make_unique
}

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

  annotated_chromosome->dna_ = Dna::make_dna(orig.dna_->length());
  annotated_chromosome->dna_->set_chrsm(annotated_chromosome.get());
  annotated_chromosome->dna_->reset_sequence(orig.dna_->data(), orig.dna_->length());

  for (auto& strand: {Strand::LEADING, Strand::LAGGING}) {
    for (auto& promoter: orig.promoters(strand)) {
      annotated_chromosome->mutable_promoter_list(strand).emplace_back(
          promoter.pos(), promoter.nb_errors(), promoter.strand());
    }
  }

  return annotated_chromosome;
}

/**
 * \brief Create a new object by recombining chrom1 and chrom2
 *
 * The resulting chromosome's sequence will begin with that of chrom1 up to pos1,
 * followed by that of chrom2 from pos2 to the end
 * Relevant promoters are copied from chrom1 and chrom2 and new promoters are searched for around the breakpoint
 *
 * Example:
 *              v
 *  chrom1: GGAT ACTAG       pos1: 4
 *  chrom2: AGCCT GA         pos2: 5
 *               ^
 * -->
 *               v
 *  created: GGAT GA
 */
auto AnnotatedChromosome::make_recombinant(
    const AnnotatedChromosome& chrsm1, Dna::size_type pos1,
    const AnnotatedChromosome& chrsm2, Dna::size_type pos2) -> std::unique_ptr<AnnotatedChromosome> {
  // Create new chromosome
  auto new_chrsm = AnnotatedChromosome::make_empty();
  new_chrsm->dna_ = Dna::make_recombinant(chrsm1.dna(), pos1, chrsm2.dna(), pos2);
  new_chrsm->dna_->set_chrsm(new_chrsm.get());

  // Copy promoters
  new_chrsm->promoter_list_ = chrsm1.promoter_list_.duplicate_promoters_included_in(0, pos1, chrsm1.length());
  PromoterList proms = chrsm2.promoter_list_.duplicate_promoters_included_in(pos2, chrsm2.length(), chrsm2.length());
  proms.shift_promoters(pos1, new_chrsm->length());
  new_chrsm->promoter_list_.insert_promoters(std::move(proms));

  // Locate potential new promoters around breakpoint
  new_chrsm->look_for_new_promoters_around(pos1);

  return new_chrsm;
}

AnnotatedChromosome::~AnnotatedChromosome() {
  Dna::release(std::move(dna_));
}

auto AnnotatedChromosome::promoters() const -> const PromoterList& {
  return promoter_list_;
}

auto AnnotatedChromosome::mutable_promoter_list() -> PromoterList& {
  return promoter_list_;
}

auto AnnotatedChromosome::promoters(Strand strand) const -> const PromoterList::SingleStranded& {
  return promoter_list_[strand];
}

auto AnnotatedChromosome::mutable_promoter_list(Strand strand) -> PromoterList::SingleStranded& {
  return promoter_list_[strand];
}

void AnnotatedChromosome::reset_promoter_lists() {
  for (auto& strand: {Strand::LEADING, Strand::LAGGING}) {
    mutable_promoter_list(strand).clear();
  }
}

void AnnotatedChromosome::look_for_new_promoters_around(Dna::size_type pos) {
  promoter_list_.look_for_new_promoters_around(*dna_, pos);
}

auto AnnotatedChromosome::compute_rna_stats() const -> std::tuple<uint32_t, float, uint32_t> {
  auto rna_stats = decltype(compute_rna_stats()){0,0,0};
  auto& [nb_coding, av_len_coding, nb_non_coding] = rna_stats;

  for (const auto& rna: rnas()) {
    if (rna.is_coding_) {
      ++nb_coding;
      av_len_coding += rna.length();
    } else {
      ++nb_non_coding;
    }
  }
  av_len_coding /= nb_coding;

  return rna_stats;
}

auto AnnotatedChromosome::compute_gene_stats() const -> std::tuple<uint32_t, float, uint32_t> {
  auto gene_stats = decltype(compute_gene_stats()){0,0,0};
  auto& [nb_functional, av_len_functional, nb_non_functional] = gene_stats;

  for (const auto& prot : proteins()) {
    if (prot != nullptr) {
      if (prot->is_functional()) {
        ++nb_functional;
        av_len_functional += (prot->size() + 2) * CODON_SIZE;
      } else {
        ++nb_non_functional;
      }
    }
  }
  av_len_functional /= nb_functional;

  return gene_stats;
}

/*!
 * \brief Compute the number of bases that are "non essential"
 *
 * A base is considered "essential" when it is either part of:
 *   - a gene (from RBS to STOP, both included), regardless of whether the gene is functional or non-functional
 *   - a promoter whose corresponding RNA bears ar least one gene
 *   - a terminator whose corresponding RNA bears ar least one gene
 * NB: UTRs are *not* considered "essential"
 */
auto AnnotatedChromosome::compute_non_coding() const -> dna_size_type {
  // Create a table of <genome_length> bools initialized to false (non-coding)
  int32_t genome_length = dna_->length_;
  // Genes (both functional and non-functional, including RBS, spacer, START and STOP) + prom + term (but not UTRs)
  auto is_essential_DNA_including_nf_genes = std::vector<bool>(genome_length, false);

  // Parse protein lists and mark the corresponding bases as coding
  for (const auto& prot : proteins()) {
    if (not prot->is_duplicate()) {
      int32_t first_coding_base;
      int32_t last_coding_base;

      switch (prot->strand()) {
        case Strand::LEADING:
          first_coding_base = exp_setup->linear_chrsm()
                                  ? prot->position_first_aa() - RBS_SIZE
                                  : mod(prot->position_first_aa() - RBS_SIZE, genome_length);
          last_coding_base = prot->position_last_base_stop_codon();
          break;
        case Strand::LAGGING:
          last_coding_base  = exp_setup->linear_chrsm()
                                  ? prot->position_first_aa() + RBS_SIZE
                                  : mod(prot->position_first_aa() + RBS_SIZE, genome_length);
          first_coding_base = prot->position_last_base_stop_codon();
          break;
        default:
          assert(false); // error: should never happen
      }

      if (first_coding_base <= last_coding_base) {
        for (int32_t i = first_coding_base; i <= last_coding_base; i++) {
          is_essential_DNA_including_nf_genes[i] = true;
        }
      } else {
        for (int32_t i = first_coding_base; i < genome_length; i++) {
          is_essential_DNA_including_nf_genes[i] = true;
        }
        for (int32_t i = 0; i <= last_coding_base; i++) {
          is_essential_DNA_including_nf_genes[i] = true;
        }
      }

      // Include the promoter and terminator to essential DNA
      for (const auto& rna: prot->rna_list()) {
        if (rna.is_init_) {
          int32_t prom_first;
          int32_t prom_last;
          int32_t term_first;
          int32_t term_last;

          if (prot->strand() == Strand::LEADING) {
            prom_first = rna.begin;
            prom_last = mod(prom_first + PROM_SIZE - 1, genome_length);
            term_last = rna.end;
            if (exp_setup->linear_chrsm()) {
              assert(prom_first + PROM_SIZE - 1 < genome_length && prom_first + PROM_SIZE - 1 >= 0);
              assert(term_last - TERM_SIZE + 1 < genome_length && term_last - TERM_SIZE + 1 >= 0);
            }
            term_first = mod(term_last - TERM_SIZE + 1, genome_length);
          } else {
            prom_last  = rna.begin;
            prom_first = mod(prom_last - PROM_SIZE + 1, genome_length);
            term_first = rna.end;
            term_last = mod(term_first + TERM_SIZE - 1, genome_length);
            if (exp_setup->linear_chrsm()) {
              assert(prom_first + PROM_SIZE - 1 < genome_length && prom_first + PROM_SIZE - 1 >= 0);
              assert(term_last - TERM_SIZE + 1 < genome_length && term_last - TERM_SIZE + 1 >= 0);
            }
          }

          // Essential DNA
          if (prom_first <= prom_last) {
            for (int32_t i = prom_first; i <= prom_last; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
          }
          else {
            for (int32_t i = prom_first; i < genome_length; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
            for (int32_t i = 0; i <= prom_last; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
          }

          if (term_first <= term_last) {
            for (int32_t i = term_first; i <= term_last; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
          } else {
            for (int32_t i = term_first; i < genome_length; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
            for (int32_t i = 0; i <= term_last; i++) {
              is_essential_DNA_including_nf_genes[i] = true;
            }
          }
        }
      }
    }
  }

  Dna::size_type nb_bases_non_essential_including_nf_genes =
      std::accumulate(is_essential_DNA_including_nf_genes.cbegin(),
                      is_essential_DNA_including_nf_genes.cend(),
                      0,
                      [](const size_t& a, const bool& b) { return std::move(a) + (b ? 0 : 1); });

  return nb_bases_non_essential_including_nf_genes;
}

}
