#include "cpp-gen/Term.h"
#include "cpp-gen/Minterm.h"

#include <catch2/catch_test_macros.hpp>

#include <bitset>
#include <limits>
#include <string>
#include <tuple>


// std::get helper

namespace detail {

template <std::size_t, typename>
struct std_get_f;

template <std::size_t _i, typename ..._Ts>
struct std_get_f<_i, std::tuple<_Ts...>> {
  using Function =
    std::tuple_element_t<_i, std::tuple<_Ts...>> const &(std::tuple<_Ts...> const &);

  static constexpr
  auto value =
    static_cast<Function *>(&std::get<_i, _Ts...>);
};

} // namespace detail

template <std::size_t _i, typename _T>
constexpr auto std_get =
  detail::std_get_f<_i, _T>::value;


constexpr
bool isOdd(int const &value) {
  return value & 1;
}

constexpr
bool isEven(int const &value) {
  return !isOdd(value);
}


constexpr
int popCount(int const &value) {
  int result = 0;
  for (std::size_t i = 0; i < std::numeric_limits<int>::digits; ++i) {
    result += (value >> i) & 1;
  }
  return result;
}


TEST_CASE("popCount") {
  CHECK(0 == popCount(0x00));
  CHECK(1 == popCount(0x01));
  CHECK(1 == popCount(0x02));
  CHECK(2 == popCount(0x03));
  CHECK(1 == popCount(0x04));
  CHECK(2 == popCount(0x05));
  CHECK(2 == popCount(0x06));
  CHECK(3 == popCount(0x07));
  CHECK(1 == popCount(0x08));
  CHECK(2 == popCount(0x09));
  CHECK(2 == popCount(0x0A));
  CHECK(3 == popCount(0x0B));
  CHECK(2 == popCount(0x0C));
  CHECK(3 == popCount(0x0D));
  CHECK(3 == popCount(0x0E));
  CHECK(4 == popCount(0x0F));
  CHECK(1 == popCount(0x10));
  CHECK(2 == popCount(0x11));
  CHECK(2 == popCount(0x12));
  CHECK(3 == popCount(0x13));
  CHECK(2 == popCount(0x14));
  CHECK(3 == popCount(0x15));
  CHECK(3 == popCount(0x16));
  CHECK(4 == popCount(0x17));
  CHECK(2 == popCount(0x18));
  CHECK(3 == popCount(0x19));
  CHECK(3 == popCount(0x1A));
  CHECK(4 == popCount(0x1B));
  CHECK(3 == popCount(0x1C));
  CHECK(4 == popCount(0x1D));
  CHECK(4 == popCount(0x1E));
  CHECK(5 == popCount(0x1F));
}


// Foo

struct Foo {
  std::string name;
  int value;
};


// Bar

struct Bar {
  int value;
};


// Aggregate

struct Aggregate {
  Foo foo;
  Bar bar;
};

constexpr
auto aggFooValue =
  Term<&Aggregate::foo, &Foo::value>{};

constexpr
auto aggBarValue =
  Term<&Aggregate::bar, &Bar::value>{};

constexpr
auto fooEqBar =
  aggFooValue == aggBarValue;


// Pair of Foo

using FooFoo = std::pair<Foo, Foo>;

constexpr
auto fooEqFoo =
  Term<&FooFoo::first, &Foo::value>{} == Term<&FooFoo::second, &Foo::value>{};


// Tuple of Foo

using FooFooFoo = std::tuple<Foo, Foo, Foo>;

constexpr
auto fooEqFoo2 =
  Term<std_get<0, FooFooFoo>, &Foo::value>{} == Term<std_get<1, FooFooFoo>, &Foo::value>{};


//
// Constraints
//


TEST_CASE("Term popCount") {
  CHECK(2 == Term<&Aggregate::foo, &Foo::value, popCount>{}({Foo{"foo", 3}, {7}}));
  CHECK_FALSE(Term<&Aggregate::foo, &Foo::value, popCount, isOdd>{}({Foo{"foo", 3}, {7}}));
  CHECK(1 == Term<&Aggregate::foo, &Foo::value, popCount>{}({Foo{"foo", 2}, {7}}));
  CHECK(Term<&Aggregate::foo, &Foo::value, popCount, isOdd>{}({Foo{"foo", 2}, {7}}));
}


TEST_CASE("Minterm") {
  { using MT = Minterm<
      Term<&Foo::value, isOdd>,
      Term<&Foo::value, popCount, isOdd>
    >;
    CHECK(MT({"", 0}).value == std::bitset<2>(0b00));
    CHECK(MT({"", 1}).value == std::bitset<2>(0b11));
    CHECK(MT({"", 2}).value == std::bitset<2>(0b10));
    CHECK(MT({"", 3}).value == std::bitset<2>(0b01));
  }
  { using MT = Minterm<
      Term<&Aggregate::foo, &Foo::value, isOdd>,
      Term<&Aggregate::foo, &Foo::value, popCount, isOdd>,
      Term<&Aggregate::bar, &Bar::value, isEven>,
      Term<&Aggregate::bar, &Bar::value, popCount, isEven>
    >;
    CHECK(MT({Foo{"", 0}, Bar{0}}).value == std::bitset<4>(0b1100));
    CHECK(MT({Foo{"", 0}, Bar{1}}).value == std::bitset<4>(0b0000));
    CHECK(MT({Foo{"", 0}, Bar{2}}).value == std::bitset<4>(0b0100));
    CHECK(MT({Foo{"", 0}, Bar{3}}).value == std::bitset<4>(0b1000));
    CHECK(MT({Foo{"", 1}, Bar{0}}).value == std::bitset<4>(0b1111));
    CHECK(MT({Foo{"", 1}, Bar{1}}).value == std::bitset<4>(0b0011));
    CHECK(MT({Foo{"", 1}, Bar{2}}).value == std::bitset<4>(0b0111));
    CHECK(MT({Foo{"", 1}, Bar{3}}).value == std::bitset<4>(0b1011));
    CHECK(MT({Foo{"", 2}, Bar{0}}).value == std::bitset<4>(0b1110));
    CHECK(MT({Foo{"", 2}, Bar{1}}).value == std::bitset<4>(0b0010));
    CHECK(MT({Foo{"", 2}, Bar{2}}).value == std::bitset<4>(0b0110));
    CHECK(MT({Foo{"", 2}, Bar{3}}).value == std::bitset<4>(0b1010));
    CHECK(MT({Foo{"", 3}, Bar{0}}).value == std::bitset<4>(0b1101));
    CHECK(MT({Foo{"", 3}, Bar{1}}).value == std::bitset<4>(0b0001));
    CHECK(MT({Foo{"", 3}, Bar{2}}).value == std::bitset<4>(0b0101));
    CHECK(MT({Foo{"", 3}, Bar{3}}).value == std::bitset<4>(0b1001));
  }
}


TEST_CASE("Equal") {
  CHECK((Term<popCount>{} == Term<popCount>{})(13));
  Foo foo { "foo", 5 };
  Bar bar { 0 };
  CHECK_FALSE(fooEqBar({foo, bar}));
  CHECK_FALSE(fooEqFoo({foo, Foo{"foo2", 4}}));
  CHECK(fooEqFoo2({Foo{"foo0", 0}, Foo{"foo1", 0}, Foo{"foo2", 0}}));
  bar.value = 5;
  CHECK(fooEqBar({foo, bar}));
  CHECK(fooEqFoo({foo, Foo{"foo2", 5}}));
  CHECK_FALSE(fooEqFoo2({Foo{"foo0", 0}, Foo{"foo1", 1}, Foo{"foo2", 2}}));
}