// ****************************************************************************
//
//          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 <charconv>
#include <cmath>
#include <ostream>
#include <vector>

#include "aevol.h"

using namespace aevol;

TEST(Folding4b_Test, FromBaseN) {
  auto base = 10;
  auto base10_sequence = std::vector<int8_t>{1, 7, 8, 9};
  auto val = uint32_t{0};
  EXPECT_EQ(from_base_n(base10_sequence, val, base), 1789);
  EXPECT_EQ(from_base_n_reversed(base10_sequence, val, base), 9871);

  base = 2;
  auto base2_sequence = std::vector<int8_t>{1, 0, 1, 0};
  EXPECT_EQ(from_base_n(base2_sequence, val, base), 10);
  EXPECT_EQ(from_base_n_reversed(base2_sequence, val, base), 5);

  base = 6;
  auto base6_sequence = std::vector<int8_t>{5, 4, 3, 5};
  auto expected       =
      base6_sequence[0] * pow(base, 3) +
      base6_sequence[1] * pow(base, 2) +
      base6_sequence[2] * base +
      base6_sequence[3];
  auto expected_reversed =
      base6_sequence[3] * pow(base, 3) +
      base6_sequence[2] * pow(base, 2) +
      base6_sequence[1] * base +
      base6_sequence[0];
  EXPECT_EQ(from_base_n(base6_sequence, val, base), expected);
  EXPECT_EQ(from_base_n_reversed(base6_sequence, val, base), expected_reversed);
  // Test with a floating-point type
  auto fval = double{0};
  EXPECT_EQ(from_base_n(base6_sequence, fval, base), expected);
  EXPECT_EQ(from_base_n_reversed(base6_sequence, fval, base), expected_reversed);

  // Tweaked base n (exponent != base)
  base = 6;
  auto exponent = 1.25;
  auto tweaked_base6_sequence = std::vector<int8_t>{5, 4, 3, 5};
  expected =
      tweaked_base6_sequence[0] * pow(exponent, 3) +
      tweaked_base6_sequence[1] * pow(exponent, 2) +
      tweaked_base6_sequence[2] * exponent +
      tweaked_base6_sequence[3];
  expected_reversed =
      tweaked_base6_sequence[3] * pow(exponent, 3) +
      tweaked_base6_sequence[2] * pow(exponent, 2) +
      tweaked_base6_sequence[1] * exponent +
      tweaked_base6_sequence[0];
  EXPECT_EQ(from_base_n(tweaked_base6_sequence, fval, exponent), expected);
  EXPECT_EQ(from_base_n_reversed(tweaked_base6_sequence, fval, exponent), expected_reversed);
}

TEST(Folding4b_Test, Normalize_NormalBase) {
  auto base = 10;
  EXPECT_EQ(normalize(9, base, base, 1), 1.0);
  EXPECT_EQ(normalize(13, base, base, 2), 13./99.);
  base = 8;
  EXPECT_EQ(normalize(0777, base, base, 3), 1.0);
  EXPECT_EQ(normalize(056, base, base, 2), (5. * 8. + 6.) / 077);
}

TEST(Folding4b_Test, Normalize_TweakedBase) {
  // Test value "9" (base 10, exponent 4)
  EXPECT_EQ(normalize(9, 10, 4, 1), 1.0);

  // Test value "13" (base 10, exponent 2)
  auto base = 10;
  auto exponent = 2.;
  auto nb_digits = 2;
  auto val = 1 * exponent + 3;
  auto max_val = (base - 1) * exponent + (base - 1);
  EXPECT_EQ(normalize(val, base, exponent, 2), val / max_val);

  // Test value "36" (base 7, exponent 1.25)
  base = 7;
  exponent = 1.25;
  val = 3 * exponent + 6;
  max_val = (base - 1) * exponent + (base - 1);
  EXPECT_EQ(normalize(val, base, exponent, nb_digits), val / max_val);

  // Test value "5342" (base 6, exponent 1.25)
  auto seq = std::vector<int8_t>{5, 3, 4, 2};
  base = 6;
  exponent = 1.25;
  from_base_n(seq, val, exponent);
  from_base_n({5, 5, 5, 5}, max_val, exponent);
  EXPECT_EQ(normalize(val, base, exponent, seq.size()), val / max_val);
}

// Gray code tests and helper functions
std::ostream& operator<<(std::ostream& os, const std::vector<int8_t>& sequence);
auto lee_distance(std::vector<int8_t> seq1, std::vector<int8_t> seq2, int exponent) -> int;

TEST(Gray4b_Test, Empty) {
  auto base = 6;
  auto gray_sequence = std::vector<int8_t>{};
  auto decoded = gray_to_base_n(gray_sequence, base);
  EXPECT_TRUE(decoded.empty());
}

TEST(Gray4b_Test, Exhaustive_Base4_Size3) {
  auto base = 4;
  auto sequence = std::vector<int8_t>{0, 0, 0};
  auto gray_sequences = std::vector<decltype(sequence)>{};

  for (auto value = 0; value < std::pow(base, sequence.size()); ++value) {
    // Construct normal base-n number
    auto val = value;
    for (int i = sequence.size() - 1; i >= 0; --i) {
      sequence[i] = val % base;
      val /= base;
    }

    // Convert to Gray
    auto gray_sequence = decltype(sequence){};
    gray_sequence.push_back(sequence[0]);
    for (auto i = size_t{1}; i < sequence.size(); ++i) {
      gray_sequence.push_back((base + sequence[i] - sequence[i - 1]) % base);
    }

    // Check round trip
    auto res = value;
    EXPECT_EQ(from_base_n(gray_to_base_n(gray_sequence, base), res, base), value);

    // Add to gray sequences
    gray_sequences.push_back(gray_sequence);
  }

  // Check that the Lee distance between 2 successive values is 1
  for (auto seq_i = size_t{1}; seq_i < gray_sequences.size(); ++seq_i) {
    EXPECT_EQ(lee_distance(gray_sequences[seq_i], gray_sequences[seq_i - 1], base), 1);
  }

  // Check that the code is cyclic
  EXPECT_EQ(lee_distance(gray_sequences[0], gray_sequences[gray_sequences.size() - 1], base), 1);
}

TEST(Gray4b_Test, Manual_Base10_Size3) {
  auto base      = 10;
  auto gray_sequences = std::vector<std::vector<int8_t>>{};

  // Add arbitrary successive Gray sequences with overflow on the 2 digits with highest weight
  gray_sequences.push_back({3, 5, 1});
  gray_sequences.push_back({3, 6, 1});
  gray_sequences.push_back({3, 6, 2});
  gray_sequences.push_back({3, 6, 3});
  gray_sequences.push_back({3, 6, 4});
  gray_sequences.push_back({3, 6, 5});
  gray_sequences.push_back({3, 6, 6});
  gray_sequences.push_back({3, 6, 7});
  gray_sequences.push_back({3, 6, 8});
  gray_sequences.push_back({3, 6, 9});
  gray_sequences.push_back({3, 6, 0});
  gray_sequences.push_back({4, 6, 0});

  // Check that these sequences code for successive values
  for (auto seq_i = size_t{1}; seq_i < gray_sequences.size(); ++seq_i) {
    auto res1 = uint32_t{0};
    auto res2 = res1;
    from_base_n(gray_to_base_n(gray_sequences[seq_i], base), res1, base);
    from_base_n(gray_to_base_n(gray_sequences[seq_i - 1], base), res2, base);
    EXPECT_EQ(res1 - res2, 1);
  }
}

std::ostream& operator<<(std::ostream& os, const std::vector<int8_t>& sequence) {
  for (const auto& digit : sequence) {
    os << static_cast<int>(digit);
  }
  return os;
}

auto lee_distance(std::vector<int8_t> seq1, std::vector<int8_t> seq2, int exponent) -> int {
  auto dist = 0;
  for (auto i = size_t{0}; i < seq1.size(); ++i) {
    auto abs_diff = abs(seq1[i] - seq2[i]);
    dist += std::min(abs_diff, exponent - abs_diff);
  }
  return dist;
}
