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

#include <array>
#include <memory>
#include <string>
#include <sstream>

#include "Dna.h"
#include "Individual.h"
#include "macros.h"
#include "Strand.h"

using namespace aevol;

//############################################################################
//                                                                           #
//                         Class IndividualTest                              #
//                                                                           #
//############################################################################
class DnaTest : public testing::Test
{
 protected:
  virtual void SetUp(void);
  virtual void TearDown(void);

  std::vector<std::string> genomes;
  std::vector<std::unique_ptr<Individual>> indivs;
};

// ===========================================================================
//                                 Public Methods
// ===========================================================================
void DnaTest::SetUp(void) {
  // Build ad-hoc dna
  //
  // AS = Arbitrary Sequence
  // AG = Arbitrary Gene
  // Do not modify the sequences !

  // Define 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"; // S-D SPACER START M0 H0 H0 W0 STOP

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

  // Build genomes
  //                0-------4---------26------31-----59------65-----76------81--------103 (total size: 109)
  genomes.push_back(as[0] + prom[0] + as[1] + gene + as[2] + term + as[3] + prom[1] + as[4]);

  for (const auto& genome : genomes) {
    indivs.push_back(Individual::make_from_sequence(genome));
  }
}

void DnaTest::TearDown() {
  indivs.clear();
  genomes.clear();
}

TEST_F(DnaTest, TestDna) {
  // Check genome size
  EXPECT_EQ(genomes[0].size(), 109);
  EXPECT_EQ(genomes[0].size(), indivs[0]->dna().length());
}

TEST_F(DnaTest, TestProteins) {
  auto i = 0;
  indivs[i]->locate_promoters();
  indivs[i]->prom_compute_RNA();
  indivs[i]->start_protein();
  indivs[i]->compute_protein();
  indivs[i]->translate_protein(0.1);

  const auto& proteins = indivs[i]->proteins();
  EXPECT_EQ(proteins.size(), 2);
  auto prot_it = proteins.begin();
  auto prot = *prot_it;
  EXPECT_EQ(prot->position_first_aa(), 44);
  EXPECT_EQ(prot->is_duplicate(), false);
  EXPECT_EQ(prot->size(), 4);
  ++prot_it;
  prot = *prot_it;
  EXPECT_EQ(prot->position_first_aa(), 44);
  EXPECT_EQ(prot->is_duplicate(), true);
}


struct subseq_test_params {
  Strand strand;
  size_t first;
  size_t count;
  bool spans_oriC = false;
};
std::ostream& operator<<(std::ostream& os, const subseq_test_params& o) {
  os << "strand: " << o.strand << " ; first: " << o.first << " ; count: " << o.count << " ; spans_oriC: "
     << (o.spans_oriC ? "true" : "false");
  return os;
}

TEST_F(DnaTest, Test_subseq) {
  std::array<subseq_test_params, 4> tests_leading = {{
      {Strand::LEADING, 0, 10, false},
      {Strand::LEADING, 10, 42, false},
      {Strand::LEADING, 100, 42, true},
      {Strand::LEADING, 100, genomes[0].size(), true} // Whole genome
  }};

  for (const auto& test : tests_leading) {
    auto subseq = indivs[0]->dna().subseq(test.first, test.count, test.strand);

    // Check subseq size
    EXPECT_EQ(test.count, subseq.size());

    // Check subseq content
    if (test.spans_oriC) {
      auto expected_subseq = genomes[0].substr(test.first);
      expected_subseq += genomes[0].substr(0, test.count - expected_subseq.size());
      EXPECT_EQ(expected_subseq, subseq)
          << "\tNote: testing (" << test << ')';

    } else {
      EXPECT_EQ(genomes[0].substr(test.first, test.count), subseq)
          << "\tNote: testing (" << test << ')';
    }
  }

  std::array<subseq_test_params, 3> tests_lagging = {{
      {Strand::LAGGING, 15, 12, false},
      {Strand::LAGGING, genomes[0].size() - 1, genomes[0].size(), false}, // Whole genome
      {Strand::LAGGING, 4, 11, true}
  }};

  for (const auto& test : tests_lagging) {
    auto subseq = indivs[0]->dna().subseq(test.first, test.count, test.strand);

    // Construct expected result (naïve mode)
    std::string expected_subseq(test.count, '0');
    if (test.spans_oriC) {
      for (size_t i = 0; i < test.first + 1; ++i) {
        expected_subseq[i] = genomes[0][test.first - i] == '0' ? '1' : '0';
      }
      for (size_t i = 1; i < test.count - test.first; ++i) {
        expected_subseq[test.first + i] = genomes[0][genomes[0].size() - i] == '0' ? '1' : '0';
      }
    } else {
      for (size_t i = 0 ; i < test.count ; ++i) {
        expected_subseq[i] = genomes[0][test.first - i] == '0' ? '1' : '0';
      }
    }

    EXPECT_EQ(expected_subseq, subseq)
        << "\tNote: testing (" << test << ')';
  }
}
