#include "YODA/BinnedEstimate.h"
#include "YODA/Utils/MetaUtils.h"
#include "YODA/Utils/MathUtils.h"
#include <cmath>
#include <fstream>

using namespace YODA;
using namespace std;

template<typename, typename = std::void_t<>>
struct has_ptr : std::false_type {};

template<typename T>
struct has_ptr<T, std::void_t<typename T::Ptr>> : std::true_type {};

template<typename BaseT, typename... EdgeT>
auto testStorage(const vector<EdgeT>& ... edges) {

  unique_ptr<BaseT> e0 = make_unique<BaseT>(edges ...);
  e0->reset();

  e0 = make_unique<BaseT>(edges ...);

  if (e0->dim() != sizeof...(EdgeT)+1)  return CHECK_TEST_RES(false);
  if (e0->numBins() != std::pow(3,sizeof...(EdgeT)))  return CHECK_TEST_RES(false);

  // Set central value / error
  for (auto& b : e0->bins(true)) {
    b.setVal(42);
    b.setErr({-4,5});
    b.setErr({2,-3}, "syst");
    b.setErr({-1,2}, "stats");
    if (b.val() != 42)  return CHECK_TEST_RES(false);
  }

  e0->scale(2);
  if (e0->bin(1).val() != 84)  return CHECK_TEST_RES(false);

  try {
    e0->_renderYODA(std::cout);
    e0->_renderFLAT(std::cout);
    if (e0->_config() != mkAxisConfig<EdgeT...>()) {
      return CHECK_TEST_RES(false);
    }
    AnalysisObject* ao = e0->mkInert();
    (void)ao;
    BaseT* e1 = e0->newclone();
    if (e0->bin(1).val() != e1->bin(1).val()) {
      delete e1;
      return CHECK_TEST_RES(false);
    }
    BaseT e2(e1->clone(), "&&");
    e2 = BaseT(*e1);
    const std::vector<double> e2vec = e2.serializeContent();

    e1->reset();
    e1->deserializeContent(e2vec);
    BaseT e3 = BaseT(*e1, "serial");
    const std::vector<double> e3vec = e3.serializeContent();

    delete e1;
    if (e2.lengthContent() != e3.lengthContent())  return CHECK_TEST_RES(false);
    if (!std::equal(e2vec.begin(), e2vec.end(), e3vec.begin())) {
      return CHECK_TEST_RES(false);
    }
  }
  catch(...) {
    return CHECK_TEST_RES(false);
  }

  if constexpr (has_ptr<BaseT>::value) {
    return testStorage<typename BaseT::BaseT>(edges ...);
  }

  return CHECK_TEST_RES(true);
}


template<typename EdgeT>
auto testEstimate(const vector<EdgeT>& edges) {
  using BE1D = BinnedEstimate<EdgeT>;
  using BE2D = BinnedEstimate<EdgeT,EdgeT>;
  using BE3D = BinnedEstimate<EdgeT,EdgeT,EdgeT>;
  return CHECK_TEST_RES(testStorage<BE1D>(edges) == EXIT_SUCCESS &&
                        testStorage<BE2D>(edges, edges) == EXIT_SUCCESS &&
                        testStorage<BE3D>(edges, edges, edges) == EXIT_SUCCESS);
}


int main() {

  int rtn = CHECK_TEST_RES( testEstimate<double>({0.0, 0.25, 0.75, 1.0}) == EXIT_SUCCESS &&
                            testEstimate<int>({0, 1, 2}) == EXIT_SUCCESS &&
                            testEstimate<string>({"A"s, "B"s, "C"s}) == EXIT_SUCCESS);

  return rtn;
}
