#include "YODA/Histo.h"
#include "YODA/BinnedEstimate.h"
#include "YODA/Scatter.h"
#include "YODA/Utils/TestHelpers.h"
#include "YODA/Utils/MathUtils.h"
#include <random>
#include <cmath>
#include <fstream>

using namespace YODA;

TestHelpers::TestData testData;

auto testHisto1D() {
  Histo1D histo(10, 0.0, 10.0);

  for (const auto& elem : testData.data) histo.fill(elem);


  if (histo.xMin() != 0.0 || histo.xMax() != 10.0) return CHECK_TEST_RES(false);

  if (!fuzzyEquals(histo.xMean(),     testData.meanVal) ||
      !fuzzyEquals(histo.xVariance(), testData.varVal) ||
      !fuzzyEquals(histo.xStdDev(),   testData.stdDevVal) ||
      !fuzzyEquals(histo.xStdErr(),   testData.stdErrVal) ||
      !fuzzyEquals(histo.xRMS(),      testData.RMSVal) ||
      histo.numEntries() != testData.data.size())
    return CHECK_TEST_RES(false);

  const double oldIntegral = histo.integral();

  //|underflow|0|1|2|3|4|5|6|7|8|9|overflow|
  histo.mergeBins<0>({1,7});

  if(histo.integral() != oldIntegral)
    return CHECK_TEST_RES(false);

  histo.normalize(1.0);

  if(histo.integral() != 1.0)
    return CHECK_TEST_RES(false);

  histo.scaleW(2.0);

  if(histo.integral() != 2.0)
    return CHECK_TEST_RES(false);

  HistoND<1> histo2 = histo.clone();

  if(histo.integral() != histo2.integral())
    return CHECK_TEST_RES(false);

  HistoND<1>* histo3 = histo.newclone();

  if (histo.integral() != histo3->integral())
    return CHECK_TEST_RES(false);

  // Normalization divided every weight by 1000
  if (!fuzzyEquals(histo.sumW() * histo.sumW(), histo3->sumW2() * 1000))
    return CHECK_TEST_RES(false);

  return CHECK_TEST_RES(true);
}

auto testHisto2D() {
  HistoND<2> histo(10, 0.0, 10.0, 10, 0.0, 10.0);

  double xCoord = 0.1;

  for (const auto& elem2 : testData.data) {
    histo.fill(xCoord, elem2);
  }


  // std::clog << histo.xMean() << std::endl;
  // std::clog << histo.xStdDev() << std::endl;
  // std::clog << histo.xVariance() << std::endl;
  // std::clog << histo.xRMS() << std::endl;
  // std::clog << histo.xStdErr() << std::endl;

  // std::clog << histo.yMean() << std::endl;
  // std::clog << histo.yStdDev() << std::endl;
  // std::clog << histo.yVariance() << std::endl;
  // std::clog << histo.yRMS() << std::endl;
  // std::clog << histo.yStdErr() << std::endl;

  // std::clog << histo.numEntries() << std::endl;
  // std::clog << histo.integral() << std::endl;


  if(!fuzzyEquals(histo.yMean(),     testData.meanVal) ||
     !fuzzyEquals(histo.yVariance(), testData.varVal) ||
     !fuzzyEquals(histo.yStdDev(),   testData.stdDevVal) ||
     !fuzzyEquals(histo.yStdErr(),   testData.stdErrVal) ||
     !fuzzyEquals(histo.yRMS(),      testData.RMSVal))
      return CHECK_TEST_RES(false);

  HistoND<2> histoOld = histo.clone();

  histo.mergeBins<1>({1,7});

  HistoND<2> histo2 = histo.clone();

  histo2 += histo;

  HistoND<2> histo3 = histo2 + histo;

  histo3 = histo - histo2;

  bool isExceptionCaptured = false;
  try {
    histoOld += histo3;
  }
  catch (const YODA::BinningError& e) {
    isExceptionCaptured = true;
  }

  // test NAN reporting
  histo.fill(5.0, std::numeric_limits<double>::quiet_NaN());
  std::cout << histo.nanCount() << " " << histo.numEntries() << std::endl;
  EstimateND<2> e1 = histo.mkEstimate();
  assert(fuzzyEquals(e1.annotation<double>("NanFraction"), 1.0/1001.));

  assert(histo._config() == "dd");

  // test useFocus
  Scatter3D s1 = histo3.mkScatter("", true, true);

  return CHECK_TEST_RES(true && isExceptionCaptured);
}

auto testHisto() {
  return CHECK_TEST_RES(
    (testHisto1D()            == EXIT_SUCCESS) &&
    (testHisto2D()            == EXIT_SUCCESS));
}

int main() {
  int rtn = EXIT_SUCCESS;

  rtn = testHisto();

  return rtn;
}
