#ifndef __TQ_GRIDSCANNER__
#define __TQ_GRIDSCANNER__

#include <vector>

#include "TString.h"
#include "TMVA/Timer.h"

#include "QFramework/TQGridScanPoint.h"
#include "QFramework/TQGridScanResults.h"
#include "QFramework/TQFolder.h"
#include "QFramework/TQSignificanceEvaluator.h"
#include "QFramework/TQTaggable.h"

class TQGridScanObservable;

class TQGridScanner : public TQTaggable, public TNamed {
public:
  using ObsVec = std::vector<TQGridScanObservable*>;
  using CutBounds = std::vector<std::pair<int, int>>; // bin numbers
  using SplitCutVals = std::vector<double>;
  using SplitCutBins = std::vector<int>;
  using BoundDirection = TQGridScanBound::Direction;

  // This default constructor is for compatibility with TBufferFile::WriteObject
  TQGridScanner() {}

  TQGridScanner(const TString& name, TQSignificanceEvaluator* evaluator);
  TQGridScanner(const TString& name, TQSignificanceEvaluator* evaluator, TList* obsToScan);
  ~TQGridScanner();

  // Adds a multidimensional histogram to be used as signal(bkg) in significance computation
  // This is done nominally by TQSignificanceEvaluator, for both signal and background
  // Returns false if there exists already a signal(bkg) hist, otherwise true
  bool addSignalHist(THnBase* hist);
  // See addSignalHist
  bool addBkgHist(THnBase* hist);

  // Accesses an observable by name
  TQGridScanObservable* getObs(const TString& obsName);

  // Acess observable names that are scanned
  std::vector<TString>  obsNamesToScan() {return m_obsNamesToScan;}

  // Add names for FOMs that are being evaluated
  void addFOMDefinitions(const std::vector <TString>& definitions);

  // Processes the user-defined bounds and creates some intermediate data structures, then
  // prepares the grid scan
  int prepare();
  // starts the grid scan
  void run(std::vector<int> chunk = {});
  TQGridScanResults* results() { return &m_results; }

  void plotAndSaveAllSignificanceProfiles(int topNumber, const TString& options);
  void plotAndSaveSignificanceProfile(BoundDirection direction, int i, int topNumber, const TString& options);
  std::unique_ptr<TH1F> getSignificanceProfile(BoundDirection direction, int i, int topNumber);

  // TODO: implement verbose logging
  void setVerbose(bool v = true) { m_verbose = v; }
  std::vector<TQGridScanPoint>& points() { return m_points; }
  const TString& nDimHistName() const { return m_nDimHistName; }

  void extractInputHistogramProjections();
  void dumpInputHistogramProjections(TQTaggable& tags);
  
  bool hasSplitObs = false;

  int getNumberOfPoints() { return m_nPoints; }

protected:
  void init();
  // This function skips over points not fulfilling some user-defined criteria when plotting and
  // printing points
  // TODO: implement this
  bool isAcceptedPoint(const TQGridScanPoint& point);

  // Scans over normal bounds (see TQGridScanObservable) recursively, then passes to split bounds
  // if they exist, otherwise creates a scan point (TQGridScanPoint)
  void scan(CutBounds::iterator obsValsIter, ObsVec::iterator obsIter, std::vector<int> chunk);
  // Calculates the significance for the set of all split observables (scan and fixed) by
  // recursively applying either side of each split cut 
  double splitSignif2(SplitCutBins::iterator obsBinsIter, ObsVec::iterator obsIter, bool isScanSplit);
  // Scans over the scan (as opposed to fixed) split observables, in preparation to call splitSignif2 
  void scanSplit(SplitCutBins::iterator obsBinsIter, ObsVec::iterator obsIter, std::vector<int> chunk);

  // This is simply a helper function used by splitSignif2
  double splitSignifHandleRecursion(
      bool isFinalLevel,
      bool isScanSplit,
      SplitCutBins::iterator obsBinsIter,
      ObsVec::iterator obsIter
  );

  // Updates the heartbeat
  bool updateHeartbeat();

  // print progress of scan to console
  void updateProgress();

  // check if the current point is in specified chunk to process
  bool evaluatePoint(std::vector<int> chunk);

  // The map of observables is generated automatically in `init`
  std::vector<TString> m_obsNamesToScan;
  std::map< TString, TQGridScanObservable*> m_observables;
  std::vector< THnBase*> m_signalHists;
  std::vector< THnBase*> m_bkgHists;
  TQSignificanceEvaluator* m_evaluator = nullptr;
  std::vector<TQGridScanPoint> m_points;

  // These hold pointers to the observables of normal bounds and split vals, respectively
  ObsVec m_normalObs;
  ObsVec m_splitObs;
  ObsVec m_splitScanObs;

  // Vectors that hold current values for bins/normal bounds/split vals as the scan iterates through them
  // Design chosen for performance
  CutBounds m_normalBounds;
  // These hold the bin numbers and bin edge values for the non-scan split observables
  SplitCutBins m_splitBins;
  // These hold the bin numbers and bin edge values for the scan split observables
  SplitCutBins m_splitScanBins;

  TString m_heartBeatCommand;
  unsigned long m_heartBeatInterval;
  unsigned long m_heartbeat;
  bool m_sorted = false;

  bool m_verbose = false;

  TQGridScanResults m_results;
  TMVA::Timer m_runTimer;
  int m_nPoints = 1;
  int m_nPointsProcessed = 0;
  TString m_nDimHistName;

  ClassDefOverride(TQGridScanner,3) // helper class to facilitate cut optimization scans
};

#endif
 TQGridScanner.h:1
 TQGridScanner.h:2
 TQGridScanner.h:3
 TQGridScanner.h:4
 TQGridScanner.h:5
 TQGridScanner.h:6
 TQGridScanner.h:7
 TQGridScanner.h:8
 TQGridScanner.h:9
 TQGridScanner.h:10
 TQGridScanner.h:11
 TQGridScanner.h:12
 TQGridScanner.h:13
 TQGridScanner.h:14
 TQGridScanner.h:15
 TQGridScanner.h:16
 TQGridScanner.h:17
 TQGridScanner.h:18
 TQGridScanner.h:19
 TQGridScanner.h:20
 TQGridScanner.h:21
 TQGridScanner.h:22
 TQGridScanner.h:23
 TQGridScanner.h:24
 TQGridScanner.h:25
 TQGridScanner.h:26
 TQGridScanner.h:27
 TQGridScanner.h:28
 TQGridScanner.h:29
 TQGridScanner.h:30
 TQGridScanner.h:31
 TQGridScanner.h:32
 TQGridScanner.h:33
 TQGridScanner.h:34
 TQGridScanner.h:35
 TQGridScanner.h:36
 TQGridScanner.h:37
 TQGridScanner.h:38
 TQGridScanner.h:39
 TQGridScanner.h:40
 TQGridScanner.h:41
 TQGridScanner.h:42
 TQGridScanner.h:43
 TQGridScanner.h:44
 TQGridScanner.h:45
 TQGridScanner.h:46
 TQGridScanner.h:47
 TQGridScanner.h:48
 TQGridScanner.h:49
 TQGridScanner.h:50
 TQGridScanner.h:51
 TQGridScanner.h:52
 TQGridScanner.h:53
 TQGridScanner.h:54
 TQGridScanner.h:55
 TQGridScanner.h:56
 TQGridScanner.h:57
 TQGridScanner.h:58
 TQGridScanner.h:59
 TQGridScanner.h:60
 TQGridScanner.h:61
 TQGridScanner.h:62
 TQGridScanner.h:63
 TQGridScanner.h:64
 TQGridScanner.h:65
 TQGridScanner.h:66
 TQGridScanner.h:67
 TQGridScanner.h:68
 TQGridScanner.h:69
 TQGridScanner.h:70
 TQGridScanner.h:71
 TQGridScanner.h:72
 TQGridScanner.h:73
 TQGridScanner.h:74
 TQGridScanner.h:75
 TQGridScanner.h:76
 TQGridScanner.h:77
 TQGridScanner.h:78
 TQGridScanner.h:79
 TQGridScanner.h:80
 TQGridScanner.h:81
 TQGridScanner.h:82
 TQGridScanner.h:83
 TQGridScanner.h:84
 TQGridScanner.h:85
 TQGridScanner.h:86
 TQGridScanner.h:87
 TQGridScanner.h:88
 TQGridScanner.h:89
 TQGridScanner.h:90
 TQGridScanner.h:91
 TQGridScanner.h:92
 TQGridScanner.h:93
 TQGridScanner.h:94
 TQGridScanner.h:95
 TQGridScanner.h:96
 TQGridScanner.h:97
 TQGridScanner.h:98
 TQGridScanner.h:99
 TQGridScanner.h:100
 TQGridScanner.h:101
 TQGridScanner.h:102
 TQGridScanner.h:103
 TQGridScanner.h:104
 TQGridScanner.h:105
 TQGridScanner.h:106
 TQGridScanner.h:107
 TQGridScanner.h:108
 TQGridScanner.h:109
 TQGridScanner.h:110
 TQGridScanner.h:111
 TQGridScanner.h:112
 TQGridScanner.h:113
 TQGridScanner.h:114
 TQGridScanner.h:115
 TQGridScanner.h:116
 TQGridScanner.h:117
 TQGridScanner.h:118
 TQGridScanner.h:119
 TQGridScanner.h:120
 TQGridScanner.h:121
 TQGridScanner.h:122
 TQGridScanner.h:123
 TQGridScanner.h:124
 TQGridScanner.h:125
 TQGridScanner.h:126
 TQGridScanner.h:127
 TQGridScanner.h:128
 TQGridScanner.h:129
 TQGridScanner.h:130
 TQGridScanner.h:131
 TQGridScanner.h:132
 TQGridScanner.h:133
 TQGridScanner.h:134
 TQGridScanner.h:135
 TQGridScanner.h:136
 TQGridScanner.h:137
 TQGridScanner.h:138
 TQGridScanner.h:139
 TQGridScanner.h:140
 TQGridScanner.h:141