#include <iostream>
#include <vector>
#include <functional>
#include <string>
#include <math.h>

#include "Data.hh"

// generic function comparing two values of some type T
template <class T>
bool testEqual(const std::string& name, T expected, T real) {
  if (expected != real) {
    std::cout << "(" << name << ": <" << expected << ">"
              << ", " << real << ") ";
    return false;
  }
  return true;
}

bool testReadingSize() {
  std::cout << "testReadingSize...";
  Data datA("testA");
  return testEqual<int>("size", 1, datA.size());
}

bool testReadingMeasurement() {
  std::cout << "testReadingMeasurement...";
  Data datA("testA");
  return testEqual("measurement", 10., datA.measurement(0));
}

bool testReadingBinEdges() {
  std::cout << "testReadingBinEdges...";
  Data datA("testA");
  return testEqual("bin low", 0., datA.binLow(0)) &&
         testEqual("bin high", 1., datA.binHigh(0));
}

bool testReadingErrors() {
  std::cout << "testReadingErrors...";
  Data datA("testA");
  return testEqual("error", 2., datA.error(0));
}

bool testCopyConstructor() {
  std::cout << "testCopyConstructor...";
  Data a("testA");
  Data b(a);
  Data c = a;  // equivalent to Data c(a)
  return testEqual("measurement", 10., b.measurement(0)) &&
         testEqual("measurement", 10., c.measurement(0));
}

void runTests() {
  std::cout << "running tests...\n";
  std::vector<std::function<bool()> > tests(
      {testReadingSize, testReadingMeasurement, testReadingBinEdges,
       testReadingErrors, testCopyConstructor});
  for (auto test : tests)
    std::cout << (test() ? " ok" : " FAILED!") << std::endl;
}

double background(double x) {
  return 0.005 - 0.00001 * x + 0.08 * exp(-0.015 * x);
}

int main() {
  using namespace std;

  cout << "******************************************************" << endl;
  runTests();
  cout << "******************************************************" << endl;
  // create an object which holds data of experiment A
  Data datA("exp_A");
  Data datB("exp_B");
  Data datC("exp_C");
  Data datD("exp_D");
  vector<Data> datas;
  datas.push_back(datA);
  datas.push_back(datB);
  datas.push_back(datC);
  datas.push_back(datD);

  // here is the data from experiment A
  cout << "bin 0: from " << datA.binLow(0) << " to " << datA.binHigh(1) << endl;
  cout << "measurement of experiment A in bin 0: " << datA.measurement(0)
       << endl;

  for(auto data : datas) {
    cout << "measurement of " << data.name() << " in bin 27: "
      << data.measurement(27) << " +- " << data.error(27) << endl;
    cout << "Chisquare of " << data.name() << ": " << data.chisquare(background) << endl;
    for (auto data2 : datas) {
      if (abs(data.measurement(27) - data2.measurement(27)) >
          data.error(27) + data2.error(27)) {
        cout << "Data point doesnt agree" << endl;
      }
      if (data.name() != data2.name()) {
        int fails = data.checkCompatibility(&data2, 3);
        cout << "Fails between " << data.name() << " and " << data2.name() <<
          ": " << fails << endl;
      }
    }
  }
  Data dat_avg = datA + datB + datC + datD;
  cout << "Combined data: " << dat_avg.measurement(27) << endl
    << "Combined error: " << dat_avg.error(27) << endl
    << "Combined chisquare: " << dat_avg.chisquare(background) << endl;

  return 0;
}
