// -*- C++ -*-
#include "YODA/Histo.h"
#include "YODA/Utils/Paths.h"
#include "YODA/Utils/MetaUtils.h"
#include "YODA/Utils/StringUtils.h"
#include <chrono>
#include <random>
#include <fenv.h>

using namespace YODA;
using namespace std;

#define TIMEIT(TYPE, DIM, CODE)                                                          \
  do {                                                                                   \
    const auto start = chrono::steady_clock::now();                                      \
    { CODE; }                                                                            \
    const auto end = chrono::steady_clock::now();                                        \
    const chrono::duration<double> elapsed = end - start;                                \
    cout << TYPE << " with " << DIM << " dim -> " << elapsed.count() << " secs" << endl; \
  } while (0)


auto testTiming() {

#if WITH_OSX
  feraiseexcept (FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID);
#else
  feenableexcept (FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID);
#endif

  // Number of fills and max fill dimension
  constexpr size_t N = 1000000;
  constexpr size_t DMAX = 6;

  // Assemble the fill values in advance, outside the timing tests
  random_device rd{};
  mt19937 gen{rd()};
  normal_distribution d{1.0, 1.0}; // 1 +- 1
  uniform_int_distribution<int> idist(0,10);
  vector<double> vals(N*DMAX, 0.0);
  for (size_t i = 0; i < N*DMAX; ++i) {
    vals[i] = d(gen);
  }

  // Uniform and log axes
  const vector<double> linedges = linspace(20, 1e-2, 2.0);
  const vector<double> logedges = logspace(20, 1e-2, 2.0);
  const vector<vector<double>> edgeses = {linedges, logedges};
  const vector<string> edgestrs = {"lin", "log"};

  auto customAdapter = [](auto& storedNumber, ...) {
    storedNumber = storedNumber + 1;
  };

  // Run tests for lin/log binnings and 1..DMAX dimensionalities
  for (size_t ie = 0; ie < 2; ++ie) {
    cout << "Running timing with " << edgestrs[ie] << "-distributed bin edges" << endl;
    const vector<double>& edges = edgeses[ie];
    try {

      HistoND<1> h1(edges);
      TIMEIT("Dbn", 1, for (size_t j = 0; j < N; ++j) h1.fill(vals[1*j]));

      HistoND<2> h2(edges, edges);
      TIMEIT("Dbn", 2, for (size_t j = 0; j < N; ++j) h2.fill({vals[2*j+0], vals[2*j+1]}));

      HistoND<3> h3(edges, edges, edges);
      TIMEIT("Dbn", 3, for (size_t j = 0; j < N; ++j) h3.fill({vals[3*j+0], vals[3*j+1], vals[3*j+2]}));

      HistoND<4> h4(edges, edges, edges, edges);
      TIMEIT("Dbn", 4, for (size_t j = 0; j < N; ++j) h4.fill({vals[4*j+0], vals[4*j+1], vals[4*j+2], vals[4*j+3]}));

      HistoND<5> h5(edges, edges, edges, edges, edges);
      TIMEIT("Dbn", 5, for (size_t j = 0; j < N; ++j) h5.fill({vals[5*j+0], vals[5*j+1], vals[5*j+2], vals[5*j+3], vals[5*j+4]}));

      // HistoND<6> h6(edges, edges, edges, edges, edges, edges);
      // TIMEIT(6, for (size_t j = 0; j < N; ++j) h6.fill({vals[6*j+0], vals[6*j+1], vals[6*j+2], vals[6*j+3], vals[6*j+4], vals[6*j+5]}));

      auto binStorage1 = FillableStorage<1,int,double>(edges, customAdapter);
      TIMEIT("Int", 1, for (size_t j = 0; j < N; ++j) binStorage1.fill({vals[1*j]}));

      auto binStorage2 = FillableStorage<2,int,double,double>(edges, edges, customAdapter);
      TIMEIT("Int", 2, for (size_t j = 0; j < N; ++j) binStorage2.fill({vals[2*j+0], vals[2*j+1]}));

      auto binStorage3 = FillableStorage<3,int,double,double,double>(edges, edges, edges, customAdapter);
      TIMEIT("Int", 3, for (size_t j = 0; j < N; ++j) binStorage3.fill({vals[3*j+0], vals[3*j+1], vals[3*j+2]}));

      auto binStorage4 = FillableStorage<4,int,double,double,double,double>(edges, edges, edges, edges, customAdapter);
      TIMEIT("Int", 4, for (size_t j = 0; j < N; ++j) binStorage4.fill({vals[4*j+0], vals[4*j+1], vals[4*j+2], vals[4*j+3]}));

      auto binStorage5 = FillableStorage<5,int,double,double,double,double,double>(edges, edges, edges, edges, edges, customAdapter);
      TIMEIT("Int", 5, for (size_t j = 0; j < N; ++j) binStorage5.fill({vals[5*j+0], vals[5*j+1], vals[5*j+2], vals[5*j+3], vals[5*j+4]}));

    } catch (const bad_alloc&) {
      cerr << "Memory-allocation failure..." << endl;
      return CHECK_TEST_RES(false);
    }

    cout << endl;
  }

  return CHECK_TEST_RES(true);
}

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