// Copyright (C) 2018  INRIA
//
// Treerecs is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Treerecs 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

// Include Googletest
#include <gtest/gtest.h>

// Include Bpplib
#include <Bpp/Phyl/Tree/PhyloTree.h>

// Include containers
#include <memory>
#include <limits>
#include <vector>
#include <list>
#include <treerecs/algorithms/NeighborJoining.h>
#include <treerecs/tools/Statistics.h>

// Include handlers, tools
#include <treerecs/tools/utils.h>
#include <treerecs/tools/random.h>

using namespace treerecs;

class Random_tests: public testing::Test {
protected:
  virtual void SetUp() {}

  virtual void TearDown() {}

  std::list<int> list = {0, 1, 2, 3, 4, 5};
  std::vector<int> random_vect_long = {83, 52, 16, 68, 39, 69, 94, 83,
                                       7, 12, 74, 22, 47, 2, 24, 58, 96,
                                       25, 93, 88, 70};
};

TEST_F(Random_tests, Random) {
  // Random tools
  std::vector<double> random_seq;
  std::size_t random_seq_size = 100000;
  random_seq.reserve(random_seq_size);
  while (random_seq.size() < random_seq_size)
    random_seq.emplace_back(Random(-1, 2000));
  ASSERT_FALSE(utils::contains(random_seq.begin(), random_seq.end(),
                               [](const double e) {
                                 return (e < -1 or e > 2000);
                               }));

  random_seq.clear();
  while (random_seq.size() < random_seq_size)
    random_seq.emplace_back(Random(-999, -10));
  ASSERT_FALSE(utils::contains(random_seq.begin(), random_seq.end(),
                               [](const double e) {
                                 return (e < -999 or e > -10);
                               }));

  random_seq.clear();
  while (random_seq.size() < random_seq_size)
    random_seq.emplace_back(Random(0, 1));
  ASSERT_FALSE(utils::contains(random_seq.begin(), random_seq.end(),
                               [](const double e) {
                                 return (e < 0 or e > 1);
                               }));
}

TEST_F(Random_tests, WeightedRandomPick) {
  ASSERT_EQ(0, WeightedRandomPick<double>({1.0, 0.0, 0.0}));
  ASSERT_EQ(1, WeightedRandomPick<double>({0.0, 1.0, 0.0}));
  ASSERT_EQ(2, WeightedRandomPick<double>({0.0, 0.0, 1.0}));

  std::vector<std::size_t> expected_result_0 {0, 0, 0};
  std::vector<std::size_t> expected_result_1 {1, 1, 1};
  std::vector<std::size_t> expected_result_2 {2, 2, 2};
  auto observed_result_0 = WeightedRandomPick<double>({1.0, 0.0, 0.0}, 3);
  auto observed_result_1 = WeightedRandomPick<double>({0.0, 1.0, 0.0}, 3);
  auto observed_result_2 = WeightedRandomPick<double>({0.0, 0.0, 1.0}, 3);

  ASSERT_TRUE(
      utils::comp_all(expected_result_0.begin(), expected_result_0.end(),
                      observed_result_0.begin(), observed_result_0.end()));
  ASSERT_TRUE(
      utils::comp_all(expected_result_1.begin(), expected_result_1.end(),
                      observed_result_1.begin(), observed_result_1.end()));
  ASSERT_TRUE(
      utils::comp_all(expected_result_2.begin(), expected_result_2.end(),
                      observed_result_2.begin(), observed_result_2.end()));
}

TEST_F(Random_tests, Frequencies) {

  // Compute frequencies
  auto frequencies
      = Statistics::frequencies(random_vect_long.begin(),
                                random_vect_long.end(),
                                10);

  ASSERT_EQ(10, frequencies.size());

  // Check if the sum of the frequency equal to 1 (100%).
  double cumulated_frequency = 0.0;
  for (auto class_freq : frequencies) {
    cumulated_frequency += class_freq.second;
  }
  ASSERT_TRUE(utils::double_equivalence(1.0, cumulated_frequency));

  // Check if there is the correct number of lines displayed.
  std::stringstream sstr;
  Statistics::plotFrequencies(frequencies, sstr);
  std::string printedRes = sstr.str();
  auto lprintedRes = utils::splitString(printedRes, "\n");
  ASSERT_EQ(10, lprintedRes.size());
}
