// ****************************************************************************
//
//          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 <inttypes.h>
#include <cstring>

#include <list>
#include <vector>
#include <memory>

#include <gtest/gtest.h>

#include "DnaFactory.h"
#include "ExpSetup.h"
#include "Individual.h"
#include "macros.h"
#include "MutationParams.h"
#include "Strand.h"

using namespace aevol;

class EukTest_withrnas : public testing::Test {
 protected:
  virtual void SetUp(void);
  virtual void TearDown(void);

  std::unique_ptr<Individual> indiv1;
  std::unique_ptr<Individual> indiv2;
  std::unique_ptr<Individual> indiv3;
  std::unique_ptr<Individual> indiv4;
  std::unique_ptr<Individual> indiv5;
  std::unique_ptr<Individual> indiv6;
  std::unique_ptr<Individual> indiv7;
  std::unique_ptr<Individual> indiv8;
  std::unique_ptr<Individual> indiv9;
  std::unique_ptr<Individual> indiv10;
};

// ===========================================================================
//                                 Public Methods
// ===========================================================================
void EukTest_withrnas::SetUp(void) {
  // Build ad-hoc genomes
  // (and reverse to test the same things on the lagging strand.):
  //
  // indiv1: LEAD (prom + AS + AG + AS + term)
  // 	  size 22+5+28+6+11=72
  // indiv2: LEAD (prom + AS + term)
  // 	  size 22+5+11=38
  // indiv3: LAGG (term + AS + AG + AS + prom)
  // 	  size 11+5+28+6+22=72
  // indiv4: LAGG (term + AS + prom)
  // 	  size 22+5+11=38
  //
  // AS = Arbitrary Sequence
  // AG = Arbitrary Gene
  // Do not modify the sequences !

  // Define a few arbitrary sequences
  std::array<std::string, 5> as = {"0011", "11101", "110011", "11000", "000101"};

  const char* SHINE_DAL_SEQ     = "011011";
  const char* SHINE_DAL_SEQ_LAG = "001001";
  // Define an arbitrary gene
  std::string gene = std::string(SHINE_DAL_SEQ) + "0011000100110110010001";

  std::string lag_gene = "0111011001001101110011" + std::string(SHINE_DAL_SEQ_LAG);

  // Define an arbitrary terminator
  std::string term = "01000001101";

  // Define a few arbitrary promoters
  std::array<std::string, 2> prom = {
      "0101010001110110010110",  // dist from consensus: 2 => basal level: 0.6
      "0101011001110010010010"   // dist from consensus: 1 => basal level: 0.8
  };

  std::array<std::string, 2> lag_prom = {
      "1001011001000111010101",  // dist from consensus: 2 => basal level: 0.6
      "1011011011000110010101"   // dist from consensus: 1 => basal level: 0.8
  };

  // Build REF indiv1
  // Construct a genome with these arbitrary sequences
  auto genome = prom[0] + as[1] + gene + as[2] + term;

  indiv1 = Individual::make_from_sequences(genome, genome);

  // Do transcription and translation
  indiv1->locate_promoters();
  indiv1->prom_compute_RNA();
  indiv1->start_protein();
  indiv1->compute_protein();
  indiv1->translate_protein(1.0);

  // Build indiv2
  // Reverse the whole genome
  genome = prom[0] + as[2] + term;
  indiv2 = Individual::make_from_sequences(genome, genome);

  // Do transcription and translation
  indiv2->locate_promoters();
  indiv2->prom_compute_RNA();
  indiv2->start_protein();
  indiv2->compute_protein();
  indiv2->translate_protein(1.0);

  // indiv3: (prom-1 + AS + AG + AS + term)
  // indiv4: reverse

  genome = term + as[1] + lag_gene + as[4] + lag_prom[0];
  indiv3 = Individual::make_from_sequences(genome, genome);

  // Do transcription and translation
  indiv3->locate_promoters();
  indiv3->prom_compute_RNA();
  indiv3->start_protein();
  indiv3->compute_protein();
  indiv3->translate_protein(1.0);

  genome = term + as[1] + lag_prom[0];
  indiv4 = Individual::make_from_sequences(genome, genome);

  // Do transcription and translation
  indiv4->locate_promoters();
  indiv4->prom_compute_RNA();
  indiv4->start_protein();
  indiv4->compute_protein();
  indiv4->translate_protein(1.0);
}

void EukTest_withrnas::TearDown() {
  //  for (auto indiv : indivs1) {
  //    delete indiv;
  //  }
}

// For each version of each individual constructed with a different
// fuzzy set implementation, we check that all values are correct.
// We don't need to change the fuzzy set implementation in the
// experimental setup again because it's only used at transcription and
// translation time.

TEST_F(EukTest_withrnas, TestIndiv1) {
  // Check that we have the right number of promoters, terminators etc
  // and at the right positions
  // "right" means those values we have computed by hand
  for (auto chrsm: {Chrsm::A, Chrsm::B}) {
    // Check genome size
    EXPECT_EQ(72, indiv1->dna(chrsm).length());

    // Check Prom list
    EXPECT_EQ(1, indiv1->annotated_chromosome(chrsm).promoters().promoter_count());
    EXPECT_EQ(0, indiv1->annotated_chromosome(chrsm).promoters().at(0)->pos());

    // Check RNA list
    auto rna_list = indiv1->rnas(chrsm);
    EXPECT_EQ(1, rna_list.size());

    auto rna_it     = rna_list.begin();
    const auto& rna = *rna_it;
    EXPECT_EQ(Strand::LEADING, rna.strand_);
    EXPECT_EQ(0, rna.prom->pos());
    EXPECT_FLOAT_EQ(0.6, rna.e);
    EXPECT_EQ(50, rna.length());

    // Check protein list
    auto& prot_list = indiv1->proteins(chrsm);
    EXPECT_EQ(1, prot_list.size());

    auto prot_it     = prot_list.begin();
    const auto* prot = *prot_it;
    EXPECT_EQ(Strand::LEADING, prot->strand());
    EXPECT_EQ(27 + 13, prot->position_first_aa());
    EXPECT_EQ(4, prot->size());
    EXPECT_FLOAT_EQ(0.6, prot->e());
    EXPECT_EQ(false, prot->is_duplicate());
    EXPECT_EQ(1, prot->rna_list().size());
  }
}

TEST_F(EukTest_withrnas, TestIndiv2) {
  for (auto chrsm: {Chrsm::A, Chrsm::B}) {
    // Check genome size
    EXPECT_EQ(39, indiv2->dna(chrsm).length());

    // Check Prom list
    EXPECT_EQ(1, indiv2->annotated_chromosome(chrsm).promoters().promoter_count());
    EXPECT_EQ(0, indiv2->annotated_chromosome(chrsm).promoters().at(0)->pos());

    // Check RNA list
    auto rna_list = indiv2->rnas(chrsm);
    EXPECT_EQ(1, rna_list.size());

    auto rna_it     = rna_list.begin();
    const auto& rna = *rna_it;
    EXPECT_EQ(Strand::LEADING, rna.strand_);
    EXPECT_EQ(0, rna.prom->pos());
    EXPECT_FLOAT_EQ(0.6, rna.e);
    EXPECT_EQ(17, rna.length());

    // Check protein list
    auto& prot_list = indiv2->proteins(chrsm);
    EXPECT_EQ(0, prot_list.size());
  }
}

TEST_F(EukTest_withrnas, TestIndiv3) {
  // Check that we have the right number of promoters, terminators etc
  // and at the right positions
  // "right" means those values we have computed by hand
  for (auto chrsm: {Chrsm::A, Chrsm::B}) {
    // Check genome size
    EXPECT_EQ(72, indiv3->dna(chrsm).length());

    // Check Prom list
    EXPECT_EQ(1, indiv3->annotated_chromosome(chrsm).promoters().promoter_count());
    EXPECT_EQ(71, indiv3->annotated_chromosome(chrsm).promoters().at(0)->pos());

    // Check RNA list
    auto rna_list = indiv3->rnas(chrsm);
    EXPECT_EQ(1, rna_list.size());

    auto rna_it     = rna_list.begin();
    const auto& rna = *rna_it;
    EXPECT_EQ(Strand::LAGGING, rna.strand_);
    EXPECT_EQ(71, rna.prom->pos());
    EXPECT_FLOAT_EQ(0.6, rna.e);
    EXPECT_EQ(47, rna.length());

    // Check protein list
    auto& prot_list = indiv3->proteins(chrsm);
    EXPECT_EQ(1, prot_list.size());

    auto prot_it     = prot_list.begin();
    const auto* prot = *prot_it;
    EXPECT_EQ(Strand::LAGGING, prot->strand());
    EXPECT_EQ(43 - 13, prot->position_first_aa());
    EXPECT_EQ(4, prot->size());
    EXPECT_FLOAT_EQ(0.6, prot->e());
    EXPECT_EQ(false, prot->is_duplicate());
    EXPECT_EQ(1, prot->rna_list().size());
  }
}

TEST_F(EukTest_withrnas, TestIndiv4) {
  // Check that we have the right number of promoters, terminators etc
  // and at the right positions
  // "right" means those values we have computed by hand
  for (auto chrsm: {Chrsm::A, Chrsm::B}) {
    // Check genome size
    EXPECT_EQ(38, indiv4->dna(chrsm).length());

    // Check Prom list
    EXPECT_EQ(2, indiv4->annotated_chromosome(chrsm).promoters().promoter_count());
    EXPECT_EQ(2, indiv4->annotated_chromosome(chrsm).promoters().at(0)->pos());
    EXPECT_EQ(37, indiv4->annotated_chromosome(chrsm).promoters().at(1)->pos());

    // Check RNA list
    auto rna_list = indiv4->rnas(chrsm);
    EXPECT_EQ(1, rna_list.size());

    auto rna_it     = rna_list.begin();
    const auto& rna = *rna_it;
    EXPECT_EQ(Strand::LAGGING, rna.strand_);
    EXPECT_EQ(37, rna.prom->pos());
    EXPECT_FLOAT_EQ(0.6, rna.e);
    EXPECT_EQ(13, rna.length());

    // Check protein list
    auto& prot_list = indiv4->proteins(chrsm);
    EXPECT_EQ(0, prot_list.size());
  }
}

// ===========================================================================
//                                Protected Methods
// ===========================================================================

// ===========================================================================
//                              Non inline accessors
// ===========================================================================
