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




// =================================================================
//                              Includes
// =================================================================
#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 EukIndividualTest                          #
//                                                                           #
//############################################################################
class EukIndividualTest : 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 EukIndividualTest::SetUp(void)
{
  // Build ad-hoc genomes
  // (and reverse to test the same things on the lagging strand.):
  //
  // indiv1: (prom + AS + AG + AS + term + AS + prom)
  // indiv2: reverse
  // indiv3: (prom-1 + AS + AG + AS + term)
  // indiv4: reverse
  // indiv5: (term + AS + AG + AS + prom-1)
  // indiv6: reverse
  // indiv7: (prom + AS + term + AS + AG + AS + prom)
  // indiv8: reverse
  // indiv9: (term + AS + AG + AS + prom-1)
  // indiv10: reverse
  //
  // 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"
  };

  // Define an arbitrary gene
  std::string gene =  std::string(SHINE_DAL_SEQ) + "0011000100110110010001";

  // 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
  };


  std::array<std::string, 2> bad_prom = {
    "101010001110110010110", // removed first base : add 0 at end
    "010101100111001001001"  // removed last base : add 0 at begin
  };

  std::array<std::string, 2> bad_lag_prom = {
    "001011001000111010101", // removed first base : add 1 at end
    "101101101100011001010"  // removed last base : add 1 at begin
  };

  const uint32_t seed = 347982374;
  std::shared_ptr<JumpingMT> prng = std::make_shared<JumpingMT>(seed);

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

  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 = indiv1->dna(Chrsm::A).subseq(0, indiv1->dna(Chrsm::A).length(), aevol::Strand::LAGGING);
  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 = bad_prom[0] + as[1] + gene + as[2] + term;

  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 = indiv3->dna(Chrsm::A).subseq(indiv3->dna(Chrsm::A).length() - 1,
                                        indiv3->dna(Chrsm::A).length(),
                                        aevol::Strand::LAGGING);
  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);


  // indiv5: (term + AS + AG + AS + prom-1)
  // indiv6: reverse

  genome = term + as[0] + gene + as[4] + bad_lag_prom[1];

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

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


  genome = indiv5->dna(Chrsm::A).subseq(indiv5->dna(Chrsm::A).length() - 1,
                                        indiv5->dna(Chrsm::A).length(),
                                        aevol::Strand::LAGGING);
  indiv6 = Individual::make_from_sequences(genome, genome);

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



  // indiv7: (prom + AS + term + AS + AG + AS + prom)
  // indiv8: reverse
  genome = prom[1] + as[0] + term + as[4] + gene + as[3] + prom[0];

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

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


  genome = indiv7->dna(Chrsm::A).subseq(indiv7->dna(Chrsm::A).length() - 1,
                                        indiv7->dna(Chrsm::A).length(),
                                        aevol::Strand::LAGGING);
  indiv8 = Individual::make_from_sequences(genome, genome);

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


  // indiv9: (term + AS + AG + AS + prom-1)
  // indiv10: reverse
  genome = term + as[4] + gene + as[3] + bad_lag_prom[1];

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

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


  genome =  indiv9->dna(Chrsm::A).subseq(indiv9->dna(Chrsm::A).length() - 1,
                                         indiv9->dna(Chrsm::A).length(),
                                         aevol::Strand::LAGGING);
  indiv10 = Individual::make_from_sequences(genome, genome);

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

}

void EukIndividualTest::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(EukIndividualTest, 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(99, indiv1->dna(chrsm).length());

    // Check RNA list
    auto rna_list = indiv1->rnas(chrsm);
    EXPECT_EQ(2, 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(40, 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(EukIndividualTest, TestIndiv2) {
  for (auto chrsm: {Chrsm::A, Chrsm::B}) {
   // Check genome size
   EXPECT_EQ(99, indiv2->dna(chrsm).length());

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

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

TEST_F(EukIndividualTest, 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(71, indiv3->dna(chrsm).length());

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

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


TEST_F(EukIndividualTest, 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(71, indiv4->dna(chrsm).length());

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

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

TEST_F(EukIndividualTest, TestIndiv5) {
  // 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(70, indiv5->dna(chrsm).length());

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

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

TEST_F(EukIndividualTest, TestIndiv6) {
  // 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(70, indiv6->dna(chrsm).length());

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

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

TEST_F(EukIndividualTest, TestIndiv7) {
  // 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(98, indiv7->dna(chrsm).length());

    // Check RNA list
    auto rna_list = indiv7->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.8, rna.e);
    EXPECT_EQ(15, rna.length());

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

TEST_F(EukIndividualTest, TestIndiv8) {
  // 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(98, indiv8->dna(chrsm).length());

    // Check RNA list
    auto rna_list = indiv8->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(97, rna.prom->pos());
    EXPECT_FLOAT_EQ(0.8, rna.e);
    EXPECT_EQ(15, rna.length());

    // Check protein list
    auto& prot_list = indiv8->proteins(chrsm);
    EXPECT_EQ(0, prot_list.size());
    }
}
TEST_F(EukIndividualTest, TestIndiv9) {
  // 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(71, indiv9->dna(chrsm).length());

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

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

TEST_F(EukIndividualTest, TestIndiv10) {
  // 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(71, indiv10->dna(chrsm).length());

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

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

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

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