#include <gtest/gtest.h>

#include "fuzzy/Discrete_Double_Fuzzy.h"

using namespace aevol;

class Discrete_Double_Fuzzy_Exposed : public Discrete_Double_Fuzzy {
 public:
  Discrete_Double_Fuzzy_Exposed(size_t vector_size): Discrete_Double_Fuzzy(vector_size) {}
  auto& length() { return nb_intervals_; };
  auto& d_length() { return nb_intervals_d_; };
  auto& points() { return points_; };
};

TEST(DiscreteDoubleFuzzyTest, Flat) {
  auto small_flat_0 = Discrete_Double_Fuzzy_Exposed(2); // 2 points => {0, 1}
  auto small_flat_1 = Discrete_Double_Fuzzy_Exposed(2); // 2 points => {0, 1}
  const auto large_size = size_t{300};
  auto large_flat_p5 = Discrete_Double_Fuzzy_Exposed(large_size);

  EXPECT_EQ(small_flat_0.get_geometric_area(), 0);
  EXPECT_EQ(small_flat_1.get_geometric_area(), 0);
  EXPECT_EQ(large_flat_p5.get_geometric_area(), 0);

  small_flat_0.add_point(0, 0);
  small_flat_0.add_point(1, 0);
  EXPECT_EQ(small_flat_0.points()[0], 0);
  EXPECT_EQ(small_flat_0.points()[1], 0);
  EXPECT_EQ(small_flat_0.get_geometric_area(), 0);

  small_flat_1.add_point(0, 1);
  small_flat_1.add_point(1, 1);
  EXPECT_EQ(small_flat_1.points()[0], 1);
  EXPECT_EQ(small_flat_1.points()[1], 1);
  EXPECT_EQ(small_flat_1.get_geometric_area(), 1);

  for (auto i = size_t{0}; i < large_size; ++i) {
    large_flat_p5.add_point(i / (static_cast<double>(large_size) - 1), .5);
  }
  EXPECT_FLOAT_EQ(large_flat_p5.points()[0], .5);
  EXPECT_FLOAT_EQ(large_flat_p5.points()[150], .5);
  EXPECT_FLOAT_EQ(large_flat_p5.points()[299], .5);
  EXPECT_FLOAT_EQ(large_flat_p5.get_geometric_area(), .5);
}

TEST(DiscreteDoubleFuzzyTest, AddPoint) {
  const auto size = size_t{5}; // 5 points => {0, .25, .5, .75, 1}
  auto fuzzy = Discrete_Double_Fuzzy_Exposed(size);

  // Add point with ~exact match for x
  auto h1 = .1;
  fuzzy.add_point(.5, h1);
  EXPECT_EQ(fuzzy.points()[2], h1);

  // Add point without exact match for x
  fuzzy.add_point(.1, h1); // x between indices 0 and 1 (closest to 0)
  EXPECT_EQ(fuzzy.points()[0], h1);
  EXPECT_EQ(fuzzy.points()[1], 0);
  auto h2 = .5;
  fuzzy.add_point(.2, h2); // x between indices 0 and 1 (closest to 1)
  EXPECT_EQ(fuzzy.points()[0], h1);
  EXPECT_EQ(fuzzy.points()[1], h2);
  auto halfway = .875;
  EXPECT_EQ(1 - halfway, halfway - .75);
  fuzzy.add_point(halfway, .5); // x between indices 3 and 4 (halfway)
  // NB: std::round rounds halfway cases away from zero
  EXPECT_EQ(fuzzy.points()[3], 0);
  EXPECT_EQ(fuzzy.points()[4], .5);
}

TEST(DiscreteDoubleFuzzyTest, AddTriangle) {
  const auto size = size_t{5}; // 5 points => {0, .25, .5, .75, 1}

  // Add triangle with only ~exact matches for x
  auto fuzzy = Discrete_Double_Fuzzy_Exposed(size);
  auto m = .5;
  auto w = .25;
  auto h = .1;
  fuzzy.add_triangle(m, w, h);
  EXPECT_EQ(fuzzy.points()[0], 0);
  EXPECT_EQ(fuzzy.points()[1], 0);
  EXPECT_EQ(fuzzy.points()[2], h);
  EXPECT_EQ(fuzzy.points()[3], 0);
  EXPECT_EQ(fuzzy.points()[4], 0);

  // Add triangle with only m as an exact match for x
  auto fuzzy2 = Discrete_Double_Fuzzy_Exposed(size);
  m = .5;
  w = .3; // spans indices 1 through 3
  h = .1;
  fuzzy2.add_triangle(m, w, h);
  EXPECT_EQ(fuzzy2.points()[0], 0);
  EXPECT_FLOAT_EQ(fuzzy2.points()[1], h / w * (.25 - (m - w)));
  EXPECT_EQ(fuzzy2.points()[2], h);
  EXPECT_FLOAT_EQ(fuzzy2.points()[3], h / w * ((m + w) - .75));
  EXPECT_EQ(fuzzy2.points()[4], 0);

  // Add triangle with no matches for x
  auto fuzzy3 = Discrete_Double_Fuzzy_Exposed(size);
  m = .6;
  w = .3; // spans indices 2 and 3
  h = .25;
  fuzzy3.add_triangle(m, w, h);
  EXPECT_EQ(fuzzy3.points()[0], 0);
  EXPECT_EQ(fuzzy3.points()[1], 0);
  EXPECT_FLOAT_EQ(fuzzy3.points()[2], h / w * (.5 - (m - w)));
  EXPECT_FLOAT_EQ(fuzzy3.points()[3], h / w * ((m + w) - .75));
  EXPECT_EQ(fuzzy3.points()[4], 0);
}
