#include "QFramework/TQGridScanObservable.h"

using Bound = TQGridScanBound;
using Bounds = TQGridScanBounds;
using BoundsType = TQGridScanBound::Type;
using BoundRange = TQGridScanBound::Range;
using BoundRangeUser = TQGridScanObservable::RangeUser;
using BinExtrema = TQGridScanBound::BinExtrema;

//using std::cout;
//using std::endl;
using std::vector;
using std::pair;
using std::tie;
using std::move;

ClassImp(TQGridScanBound)
ClassImp(TQGridScanBounds)
ClassImp(TQGridScanObservable)

namespace {

// These functions transform user-space coords to bin numbers for some histgram axis
// Bound range in user coords is _exclusive_ on the upper end, so subtract 1 from the bin
// for upper bounds
int coordToBin(TAxis* axis, double coord) {
  return axis->FindFixBin(coord);
}

int coordToBinLower(TAxis* axis, double coord) {
  return coordToBin(axis, coord);
}

int coordToBinUpper(TAxis* axis, double coord) {
  return coordToBin(axis, coord) - 1;
}

int coordToBinSplit(TAxis* axis, double coord) {
  return coordToBin(axis, coord);
}

BoundRange coordsToBins(
    TAxis* axis,
    BoundRangeUser boundRangeUser,
    const std::function<int (TAxis*, double)>& coordToBinFunc
) {
  double lowerCoord, upperCoord;
  int nSteps;
  // TODO C++17: destructuring
  tie(lowerCoord, upperCoord, nSteps) = boundRangeUser;
  auto lowerBin = coordToBinFunc(axis, lowerCoord);
  auto upperBin = coordToBinFunc(axis, upperCoord);
  int stepSize = 1; // in bins
  if (nSteps > 0) {
    stepSize = (upperBin - lowerBin) / nSteps;
    if (stepSize < 1) stepSize = 1;
  }
  return {lowerBin, upperBin, stepSize};
}

BoundRange coordsToBinsLower(TAxis* axis, BoundRangeUser boundRangeUser) {
  return coordsToBins(axis, boundRangeUser, coordToBinLower);
}

BoundRange coordsToBinsUpper(TAxis* axis, BoundRangeUser boundRangeUser) {
  return coordsToBins(axis, boundRangeUser, coordToBinUpper);
}

BoundRange coordsToBinsSplit(TAxis* axis, BoundRangeUser boundRangeUser) {
  return coordsToBins(axis, boundRangeUser, coordToBinSplit);
}

}

Bounds* TQGridScanObservable::normalBounds() {
  if (not m_bounds or m_bounds->which() == BoundsType::split) {
    // If any the lower or upper  bounds are not set, they will just be single cuts below or above
    // the under- or overflow bins, respectively
    m_bounds = Bounds{};
  }
  return &boost::get<Bounds>(*m_bounds);
}

Bound* TQGridScanObservable::splitBounds() {
  if (not m_bounds or m_bounds->which() == BoundsType::normal) {
    // This is just a dummy split point
    m_bounds = Bound{1, 1, 1};
  }
  return &boost::get<Bound>(*m_bounds);
}

void TQGridScanObservable::setAxesRange(int first, int last) {
  // Set axes ranges for all regions and all systematic variations!
  for (auto a : m_axes) a->SetRange(first, last); // sets range for all variations and samples!
}

void TQGridScanObservable::setRangeCutLower(BoundRangeUser boundRangeUser) {
  auto binRange = coordsToBinsLower(axis(), boundRangeUser);
  normalBounds()->lower()->range(move(binRange));
}
void TQGridScanObservable::setRangeCutUpper(BoundRangeUser boundRangeUser) {
  auto binRange = coordsToBinsUpper(axis(), boundRangeUser);
  normalBounds()->upper()->range(move(binRange));
}
void TQGridScanObservable::setFixedCutLower(double cutVal) {
  int lowerBin = coordToBinLower(axis(), cutVal);
  normalBounds()->lower()->range(lowerBin);
}
void TQGridScanObservable::setFixedCutUpper(double cutVal) {
  int upperBin = coordToBinUpper(axis(), cutVal);
  normalBounds()->upper()->range(upperBin);
}
void TQGridScanObservable::setSwitchCutLower(double cutVal) {
  int lowerBin = coordToBinLower(axis(), cutVal);
  normalBounds()->lower()->range(0, lowerBin);
}
void TQGridScanObservable::setSwitchCutUpper(double cutVal) {
  int upperBin = coordToBinUpper(axis(), cutVal);
  normalBounds()->upper()->range(upperBin, axis()->GetNbins()+1);
}
void TQGridScanObservable::setRangeCutSplit(BoundRangeUser boundRangeUser) {
  auto binRange = coordsToBinsSplit(axis(), boundRangeUser);
  splitBounds()->range(move(binRange));
}
void TQGridScanObservable::setFixedCutSplit(double cutVal) {
  int bin = coordToBinSplit(axis(), cutVal);
  splitBounds()->range(bin);
}
void TQGridScanObservable::setRangeCutLower(double lower, double upper, int nSteps) {
  setRangeCutLower(BoundRangeUser{lower, upper, nSteps});
}
void TQGridScanObservable::setRangeCutUpper(double lower, double upper, int nSteps) {
  setRangeCutUpper(BoundRangeUser{lower, upper, nSteps});
}
void TQGridScanObservable::setRangeCutSplit(double lower, double upper, int nSteps) {
  setRangeCutSplit(BoundRangeUser{lower, upper, nSteps});
}
 TQGridScanObservable.cxx:1
 TQGridScanObservable.cxx:2
 TQGridScanObservable.cxx:3
 TQGridScanObservable.cxx:4
 TQGridScanObservable.cxx:5
 TQGridScanObservable.cxx:6
 TQGridScanObservable.cxx:7
 TQGridScanObservable.cxx:8
 TQGridScanObservable.cxx:9
 TQGridScanObservable.cxx:10
 TQGridScanObservable.cxx:11
 TQGridScanObservable.cxx:12
 TQGridScanObservable.cxx:13
 TQGridScanObservable.cxx:14
 TQGridScanObservable.cxx:15
 TQGridScanObservable.cxx:16
 TQGridScanObservable.cxx:17
 TQGridScanObservable.cxx:18
 TQGridScanObservable.cxx:19
 TQGridScanObservable.cxx:20
 TQGridScanObservable.cxx:21
 TQGridScanObservable.cxx:22
 TQGridScanObservable.cxx:23
 TQGridScanObservable.cxx:24
 TQGridScanObservable.cxx:25
 TQGridScanObservable.cxx:26
 TQGridScanObservable.cxx:27
 TQGridScanObservable.cxx:28
 TQGridScanObservable.cxx:29
 TQGridScanObservable.cxx:30
 TQGridScanObservable.cxx:31
 TQGridScanObservable.cxx:32
 TQGridScanObservable.cxx:33
 TQGridScanObservable.cxx:34
 TQGridScanObservable.cxx:35
 TQGridScanObservable.cxx:36
 TQGridScanObservable.cxx:37
 TQGridScanObservable.cxx:38
 TQGridScanObservable.cxx:39
 TQGridScanObservable.cxx:40
 TQGridScanObservable.cxx:41
 TQGridScanObservable.cxx:42
 TQGridScanObservable.cxx:43
 TQGridScanObservable.cxx:44
 TQGridScanObservable.cxx:45
 TQGridScanObservable.cxx:46
 TQGridScanObservable.cxx:47
 TQGridScanObservable.cxx:48
 TQGridScanObservable.cxx:49
 TQGridScanObservable.cxx:50
 TQGridScanObservable.cxx:51
 TQGridScanObservable.cxx:52
 TQGridScanObservable.cxx:53
 TQGridScanObservable.cxx:54
 TQGridScanObservable.cxx:55
 TQGridScanObservable.cxx:56
 TQGridScanObservable.cxx:57
 TQGridScanObservable.cxx:58
 TQGridScanObservable.cxx:59
 TQGridScanObservable.cxx:60
 TQGridScanObservable.cxx:61
 TQGridScanObservable.cxx:62
 TQGridScanObservable.cxx:63
 TQGridScanObservable.cxx:64
 TQGridScanObservable.cxx:65
 TQGridScanObservable.cxx:66
 TQGridScanObservable.cxx:67
 TQGridScanObservable.cxx:68
 TQGridScanObservable.cxx:69
 TQGridScanObservable.cxx:70
 TQGridScanObservable.cxx:71
 TQGridScanObservable.cxx:72
 TQGridScanObservable.cxx:73
 TQGridScanObservable.cxx:74
 TQGridScanObservable.cxx:75
 TQGridScanObservable.cxx:76
 TQGridScanObservable.cxx:77
 TQGridScanObservable.cxx:78
 TQGridScanObservable.cxx:79
 TQGridScanObservable.cxx:80
 TQGridScanObservable.cxx:81
 TQGridScanObservable.cxx:82
 TQGridScanObservable.cxx:83
 TQGridScanObservable.cxx:84
 TQGridScanObservable.cxx:85
 TQGridScanObservable.cxx:86
 TQGridScanObservable.cxx:87
 TQGridScanObservable.cxx:88
 TQGridScanObservable.cxx:89
 TQGridScanObservable.cxx:90
 TQGridScanObservable.cxx:91
 TQGridScanObservable.cxx:92
 TQGridScanObservable.cxx:93
 TQGridScanObservable.cxx:94
 TQGridScanObservable.cxx:95
 TQGridScanObservable.cxx:96
 TQGridScanObservable.cxx:97
 TQGridScanObservable.cxx:98
 TQGridScanObservable.cxx:99
 TQGridScanObservable.cxx:100
 TQGridScanObservable.cxx:101
 TQGridScanObservable.cxx:102
 TQGridScanObservable.cxx:103
 TQGridScanObservable.cxx:104
 TQGridScanObservable.cxx:105
 TQGridScanObservable.cxx:106
 TQGridScanObservable.cxx:107
 TQGridScanObservable.cxx:108
 TQGridScanObservable.cxx:109
 TQGridScanObservable.cxx:110
 TQGridScanObservable.cxx:111
 TQGridScanObservable.cxx:112
 TQGridScanObservable.cxx:113
 TQGridScanObservable.cxx:114
 TQGridScanObservable.cxx:115
 TQGridScanObservable.cxx:116
 TQGridScanObservable.cxx:117
 TQGridScanObservable.cxx:118
 TQGridScanObservable.cxx:119
 TQGridScanObservable.cxx:120
 TQGridScanObservable.cxx:121
 TQGridScanObservable.cxx:122
 TQGridScanObservable.cxx:123
 TQGridScanObservable.cxx:124
 TQGridScanObservable.cxx:125
 TQGridScanObservable.cxx:126
 TQGridScanObservable.cxx:127
 TQGridScanObservable.cxx:128
 TQGridScanObservable.cxx:129
 TQGridScanObservable.cxx:130
 TQGridScanObservable.cxx:131
 TQGridScanObservable.cxx:132
 TQGridScanObservable.cxx:133
 TQGridScanObservable.cxx:134
 TQGridScanObservable.cxx:135
 TQGridScanObservable.cxx:136
 TQGridScanObservable.cxx:137