#include "YODA/AnalysisObject.h"
#include "YODA/IO.h"
#include "YODA/Utils/MetaUtils.h"

#include <iostream>
#include <string>
#include <type_traits>
#include <vector>

using namespace YODA;
using namespace std;

namespace {

  template <typename... Args, class F>
  constexpr void for_each_arg(F&& f) {
    (( f(Args{}) ), ...);
  }

}

template<typename T>
vector<T> edges() {
  if constexpr (is_same_v<T, double>)       return vector<double>{0.0, 0.2, 0.4, 0.6, 0.7, 0.8, 1.0};
  else if constexpr (is_same_v<T, int>)     return vector<int>{0, 2, 4, 6, 7, 8, 10};
  else if constexpr (is_same_v<T, string>)  return vector<string>{"A"s, "B"s, "C"s, "D"s, "E"s, "F"s, "G"s};
  return vector<T>{};
}

template<typename ... Args>
void createDefaultTypes(vector<AnalysisObject*>& aos) {
  aos.push_back(new Counter());
  aos.push_back(new Estimate0D());
  aos.push_back(new Scatter1D());
  aos.push_back(new Scatter2D());
  aos.push_back(new Scatter3D());

  // add BinnedHisto/BinnedProfile in 1D
  for_each_arg<Args...>([&](auto&& arg) {
    using A1 = std::decay_t<decltype(arg)>;
    using BH = BinnedHisto<A1>;
    const vector<A1> e1(edges<A1>());
    aos.push_back(new BH(e1));
    using BP = BinnedProfile<A1>;
    aos.push_back(new BP(e1));
    using BE = BinnedEstimate<A1>;
    aos.push_back(new BE(e1));

    // add BinnedHisto/BinnedProfile in 2D
    for_each_arg<Args...>([&](auto&& arg) {
      using A2 = std::decay_t<decltype(arg)>;
      using BH = BinnedHisto<A1,A2>;
      const vector<A2> e2(edges<A2>());
      aos.push_back(new BH(e1,e2));
      using BP = BinnedProfile<A1,A2>;
      aos.push_back(new BP(e1,e2));
      using BE = BinnedEstimate<A1,A2>;
      aos.push_back(new BE(e1,e2));

      // add BinnedHisto/BinnedProfile in 3D
      for_each_arg<Args...>([&](auto&& arg) {
        using A3 = std::decay_t<decltype(arg)>;
        const vector<A3> e3(edges<A3>());
        //using BH = BinnedHisto<A1,A2,A3>;
        //aos.push_back(new BH(e1,e2,e3));
        //using BP = BinnedProfile<A1,A2,A3>;
        //aos.push_back(new BP(e1,e2,e3));
        using BE = BinnedEstimate<A1,A2,A3>;
        aos.push_back(new BE(e1,e2,e3));
      });
    });
  });
  const vector<double> e0(edges<double>());
  aos.push_back(new HistoND<3>(e0,e0,e0));
}


template <typename... Args>
auto testTemplates() {

  try {
    vector<AnalysisObject*> aos;
    createDefaultTypes(aos);
    for (auto& ao : aos) {
      AnalysisObject* newao = ao->mkInert();
      delete newao;
      delete ao;
    }
  }
  catch(...) {
    return CHECK_TEST_RES(false);
  }

  return CHECK_TEST_RES(true);
}

int main() {
  return CHECK_TEST_RES(testTemplates() == EXIT_SUCCESS);
}
