/*
 * Histo2DAction.h
 *
 *  Created on: 01/set/2009
 *      Author: Nicola Mori
 */

/*! @file Histo2DAction.h The Histo2DAction class declaration file. */

#ifndef HISTO2DACTION_H_
#define HISTO2DACTION_H_

#include "../../CollectionAction/CollectionAction.h"

#include <TH2I.h>
#include <TH2F.h>
#include <TH2D.h>

/*! @brief An abstract action that fills a 2-dimensional histogram.
 *
 * This abstract class provides all the common methods needed to produce 2-dimensional histograms,
 * like reading the bins from a file, setting up the root histogram and so on. It handles histograms
 * both as a ROOT histogram and as a matrix. The OnGood() method has to be implemented in children
 * classes, with the specific physical variables to fill the histogram. Eg., a dE/dx vs. rigidity
 * histogram class derived from Histo2DAction could have this OnGood() implementation:
 *
 * Fill(event->GetTrack(0)->GetTrkTrack()->GetRigidity(), event->GetTrack(0)->GetTrkTrack()->GetDEDX());
 *
 * Fill() automatically fills the ROOT and the matrix histogram.
 * The template argument HistoType is the type of histogram (Int_t, Double_t,...). In current
 * implementation, ROOT histograms are only supported for Int_t, Float_t and Double_t. For all the
 * other types, only the the matrix histogram with text file output will be produced. If you want to
 * implement some new types of ROOT histograms, please specialize the template definitions of _CreateHisto()
 * and ~Histo2DAction() (see the specializations in the .cpp file).
 *
 * */
template<class HistoType>
class Histo2DAction: public CollectionAction {
public:

  /*! @brief Constructor.
   *
   * The histogram binning is defined by the content of the bins vector.
   *
   * @param actionName The action's name.
   * @param title The ROOT histogram title.
   * @param outFileBase The file base name for the histogram output. If "", no output file will be produced.
   * @param mode The mode of ROOT file creation (see documentation of TFile constructor
   *             in ROOT's reference guide).
   * @param outRoot If true, an output ROOT file named outFileBase + ".root" will be produced.
   * @param outText If true, an output text file named outFileBase + ".txt" will be produced. It will overwrite an
   *                eventually existing file with the same name.
   */
  Histo2DAction(const char *actionName, TString title = "", TString outFileBase = "", TString mode = "UPDATE",
      bool outRoot = false, bool outText = false);

  /*! @brief Destructor
   *
   * This has to be specialized for each kind of ROOT histogram.
   *
   * */
  ~Histo2DAction();

  /*! @brief Sets the X axis using a binning read from a a vector.
   *
   * @param label The axis' label.
   * @param bins A vector containing the histogram binning (with sign, eventually; from lowest to highest).
   */
  void SetXAxis(TString label, vector<float> &bins);
  /*! @brief Sets the X axis reading the binning from a file.
   *
   * @param label The axis' label.
   * @param binsFile The file containing the bins (with sign, eventually; from lowest to highest).
   */
  void SetXAxis(TString label, TString binsFile);
  /*! @brief Sets the X axis specifying the binning parameters.
   *
   * @param label The axis' label.
   * @param nBins The number of bins.
   * @param min The lower limit of the axis.
   * @param max The upper limit of the axis.
   * @param logBinning If true, bins will be logarithmically spaced.
   */
  void SetXAxis(TString label, unsigned int nBins, float min, float max, bool logBinning = false);

  /*! @brief Sets the Y axis using a binning read from a a vector.
   *
   * @param label The axis' label.
   * @param bins A vector containing the histogram binning (with sign, eventually; from lowest to highest).
   */
  void SetYAxis(TString label, vector<float> &bins);
  /*! @brief Sets the Y axis reading the binning from a file.
   *
   * @param label The axis' label.
   * @param binsFile The file containing the bins (with sign, eventually; from lowest to highest).
   */
  void SetYAxis(TString label, TString binsFile);

  /*! @brief Sets the Y axis specifying the binning parameters.
   *
   * @param label The axis' label.
   * @param nBins The number of bins.
   * @param min The lower limit of the axis.
   * @param max The upper limit of the axis.
   * @param logBinning If true, bins will be logarithmically spaced.
   */
  void SetYAxis(TString label, unsigned int nBins, float min, float max, bool logBinning = false);

  /*! @brief Sets the ROOT histogram's title.
   *
   * @param title The histogram title as it will appear on the histogram itself.
   */
  void SetTitle(TString &title) {
    _title = title;
  }
  /*! @brief Sets up the histogram
   *
   * This routine effectively prepares the histogram, after the desired parameters has been set by #SetXAxis() and #SetYAxis().
   *
   * @param events Pointer to PamLevel2 events (unused).
   */
  void Setup(PamLevel2 *events) {
    CollectionAction::Setup(events);
    _InitHistos();

  }

  /*! @ brief Post-analysis tasks.
   *
   * This method writes the output on a ROOT file if the proper option was set in the constructor.
   */
  void Finalize();

  /*! @brief Returns the histogram.
   *
   * Element [i][j] is the value of the i-th bin on the Y axis and on the j-th bin on the X axis. This sort
   * of index inversion is due to logically maintain the Y values on the vertical axis of the matrix, which
   * is indexed by the row number (which conventionally is the first index of a matrix element).
   *
   * @return A reference to a matrix containing the values of the bins of the histogram.
   */
  SimpleMatrix<HistoType> &GetHisto() {
    return _histo;
  }

  /*! @brief Returns a pointer to the ROOT histogram.
   *
   * @return A pointer to the root histogram
   */
  TH2 *GetRootHisto() {
    return _rootHisto;
  }

  /*! Fills the ROOT and the vector histogram.
   *
   * @param xValue The value of the X coordinate associated to the event.
   * @param yValue The value of the Y coordinate associated to the event.
   * @param weight The weight which will be applied to the event.
   */
  void Fill(double xValue, double yValue, double weight = 1.);

  /*! @brief Gets the X overflow histogram.
   *
   * The returned vector contains the histogram filled with the X overflow events, eg.,
   * those events whose X variable falls above the upper limit of the X axis. This histogram
   * is binned like the Y axis.
   *
   * @return The X overflow histogram.
   */
  vector<HistoType>& GetXOverflow() {
    return _xOverflow;
  }

  /*! @brief Gets the X underflow histogram.
   *
   * The returned vector contains the histogram filled with the X underflow events, eg.,
   * those events whose X variable falls below the lower limit of the X axis. This histogram
   * is binned like the Y axis.
   *
   * @return The X underflow histogram.
   */
  vector<HistoType>& GetXUnderflow() {
    return _xUnderflow;
  }

  /*! @brief Gets the Y overflow histogram.
   *
   * The returned vector contains the histogram filled with the Y overflow events, eg.,
   * those events whose Y variable falls above the upper limit of the Y axis. This histogram
   * is binned like the X axis.
   *
   * @return The Y overflow histogram.
   */
  vector<HistoType>& GetYOverflow() {
    return _yOverflow;
  }

  /*! @brief Gets the Y underflow histogram.
   *
   * The returned vector contains the histogram filled with the Y underflow events, eg.,
   * those events whose Y variable falls below the lower limit of the Y axis. This histogram
   * is binned like the X axis.
   *
   * @return The Y underflow histogram.
   */
  vector<HistoType>& GetYUnderflow() {
    return _yUnderflow;
  }

  /*! @brief Gets the X-Y overflow histogram.
   *
   * This single-bin histogram is built with events which fall above the lower limits of both the
   * X and Y axis.
   *
   * @return The X-Y overflow histogram.
   */
  HistoType GetXOverYOverflow() {
    return _xOverYOverflow;
  }

  /*! @brief Gets the X-Y underflow histogram.
   *
   * This single-bin histogram is built with events fall below the lower limits of both the X and Y axis.
   *
   * @return The X-Y overflow histogram.
   */
  HistoType GetXUnderYUnderflow() {
    return _xUnderYUnderflow;
  }
  /*! @brief Gets the (X over - Y under)flow histogram.
   *
   * This single-bin histogram is built with events fall above the lower limit of the Xaxis  and below
   * that of the Y axis.
   *
   * @return The (X over -Y under)flow histogram.
   */
  HistoType GetXOverYUnderflow() {
    return _xOverYUnderflow;
  }

  /*! @brief Gets the (X under - Y over)flow histogram.
   *
   * This single-bin histogram is built with events fall below the lower limit of the X axis and above
   * that of the Y axis.
   *
   * @return The (X under -Y over)flow histogram.
   */
  HistoType GetXUnderYOverflow() {
    return _xUnderYOverflow;
  }

protected:

  /*! @brief The vector containing the limits of the X bins(from lower to higher). */
  std::vector<float> _xBins;
  /*! @brief The vector containing the limits of the Y bins(from lower to higher). */
  std::vector<float> _yBins;
  /*! @brief A matrix containing the value of the histogram for each X-Y bin. */
  SimpleMatrix<HistoType> _histo;
  /*! @brief The ROOT histogram. */
  TH2 *_rootHisto;

  /*! @brief Base name of the output file. */
  TString _outFileBase;
  /*! @brief Output file open mode (UPDATE or RECREATE, see documentation of TFile). */
  TString _mode;
  /*! @brief Title for the ROOT histogram. */
  TString _title;
  /*! @brief X axis label for the ROOT histogram. */
  TString _xLabel;
  /*! @brief Y axis label for the ROOT histogram. */
  TString _yLabel;

private:

  vector<HistoType> _xUnderflow, _xOverflow, _yUnderflow, _yOverflow;
  HistoType _xUnderYUnderflow, _xOverYOverflow, _xUnderYOverflow, _xOverYUnderflow;
  bool _outRoot;
  bool _outText;
  void _CreateHisto();
  void _InitHistos();
};

template<class HistoType>
void Histo2DAction<HistoType>::_CreateHisto() {

  // No ROOT histogram for generic type; see template specializations in .cpp file.
  _rootHisto = NULL;
}

// Specializations for _CreateHistos(). See Histo2DAction.cpp
template<>
void Histo2DAction<Int_t>::_CreateHisto();

template<>
void Histo2DAction<Float_t>::_CreateHisto();

template<>
void Histo2DAction<Double_t>::_CreateHisto();

template<class HistoType>
void Histo2DAction<HistoType>::_InitHistos() {

  _CreateHisto();
  if (_xBins.size() < 2) // SetXAxis not called by the main program, or wrongly filled (only 1 bin limit)
    SetXAxis("Default X", 10, 0., 1.);
  if (_yBins.size() < 2) // SetYAxis not called by the main program, or wrongly filled (only 1 bin limit)
    SetYAxis("Default Y", 10, 0., 1.);

  if (_rootHisto) {
    Double_t *auxXArray = new Double_t[_xBins.size()];

    for (unsigned int i = 0; i < _xBins.size(); i++) {
      auxXArray[i] = _xBins[i];
    }

    Double_t *auxYArray = new Double_t[_yBins.size()];

    for (unsigned int i = 0; i < _yBins.size(); i++) {
      auxYArray[i] = _yBins[i];
    }

    _rootHisto->SetBins(_xBins.size() - 1, auxXArray, _yBins.size() - 1, auxYArray);
    _rootHisto->SetName(GetName());
    _rootHisto->SetTitle(_title);
    _rootHisto->SetXTitle(_xLabel);
    _rootHisto->SetYTitle(_yLabel);

    delete[] auxXArray;
    delete[] auxYArray;
  }

  /* The row index (first) corresponds to the position on the vertical (Y) axis. */
  _histo.Resize(_yBins.size() - 1, _xBins.size() - 1);

  _xUnderflow.resize(_yBins.size() - 1);
  _xOverflow.resize(_yBins.size() - 1);
  _yUnderflow.resize(_xBins.size() - 1);
  _yOverflow.resize(_xBins.size() - 1);

}

template<class HistoType>
Histo2DAction<HistoType>::~Histo2DAction() {

  delete _rootHisto;
  _rootHisto = NULL;
}

template<class HistoType>
Histo2DAction<HistoType>::Histo2DAction(const char *actionName, TString title, TString outFileBase, TString mode,
    bool outRoot, bool outText) :
  CollectionAction(actionName), _xBins(0), _yBins(0), _histo(0, 0), _rootHisto(NULL), _outFileBase(outFileBase), _mode(
      mode), _title(title), _xLabel(""), _yLabel(""), _xUnderflow(0), _xOverflow(0), _yUnderflow(0), _yOverflow(0),
      _xUnderYUnderflow((HistoType) 0), _xOverYOverflow((HistoType) 0), _xUnderYOverflow((HistoType) 0),
      _xOverYUnderflow((HistoType) 0), _outRoot(outRoot), _outText(outText) {

}

template<class HistoType>
void Histo2DAction<HistoType>::SetXAxis(TString label, vector<float> &bins) {

  _xBins = bins;
  _xLabel = label;

}

template<class HistoType>
void Histo2DAction<HistoType>::SetYAxis(TString label, vector<float> &bins) {

  _yBins = bins;
  _yLabel = label;

}

template<class HistoType>
void Histo2DAction<HistoType>::SetXAxis(TString label, TString binsFile) {

  // Reading the bins from file
  ifstream binListFile;
  binListFile.open(binsFile);

  TString auxString;
  _xBins.resize(0);
  while (!binListFile.eof()) {
    binListFile >> auxString;
    if (auxString != "") {
      _xBins.push_back(auxString.Atof());
    }
  }
  binListFile.close();

  _xLabel = label;

}

template<class HistoType>
void Histo2DAction<HistoType>::SetYAxis(TString label, TString binsFile) {

  // Reading the bins from file
  ifstream binListFile;
  binListFile.open(binsFile);

  TString auxString;
  _yBins.resize(0);
  while (!binListFile.eof()) {
    binListFile >> auxString;
    if (auxString != "") {
      _yBins.push_back(auxString.Atof());
    }
  }
  binListFile.close();

  _yLabel = label;

}

template<class HistoType>
void Histo2DAction<HistoType>::SetXAxis(TString label, unsigned int nBins, float min, float max, bool logBinning) {

  _xBins.resize(nBins + 1);

  if (!logBinning || (logBinning && min <= 0.)) {

#ifdef DEBUGPAMCUT
    if (logbinning && rigMin <= 0.)
    cout << "Warning: logarithmic binning was chosen for X axis but min <= 0. Using linear binning."
#endif

    float step = (max - min) / nBins;
    for (unsigned int i = 0; i < nBins + 1; i++) {
      _xBins[i] = min + i * step;
    }

  }
  else {

    double maxExp = log10(max / min);
    for (unsigned int i = 0; i < nBins + 1; i++) {
      _xBins[i] = min * pow(10., (double) i / ((double) nBins) * maxExp);
    }

  }

  _xLabel = label;
}

template<class HistoType>
void Histo2DAction<HistoType>::SetYAxis(TString label, unsigned int nBins, float min, float max, bool logBinning) {

  _yBins.resize(nBins + 1);

  if (!logBinning || (logBinning && min <= 0.)) {

#ifdef DEBUGPAMCUT
    if (logbinning && rigMin <= 0.)
    cout << "Warning: logarithmic binning was chosen for Y axis but min <= 0. Using linear binning."
#endif

    float step = (max - min) / nBins;
    for (unsigned int i = 0; i < nBins + 1; i++) {
      _yBins[i] = min + i * step;
    }

  }
  else {

    double maxExp = log10(max / min);
    for (unsigned int i = 0; i < nBins + 1; i++) {
      _yBins[i] = min * pow(10., (double) i / ((double) nBins) * maxExp);
    }

  }

  _yLabel = label;
}

template<class HistoType>
void Histo2DAction<HistoType>::Fill(double xValue, double yValue, double weight) {

  _rootHisto->Fill(xValue, yValue, weight);

  int xBin = 0, yBin = 0;

  bool UOflow = false;

  if (xValue < _xBins.front()) {
    xBin = -1;
    UOflow = true;
  }

  if (xValue >= _xBins.back()) {
    xBin = _xBins.size();
    UOflow = true;
  }

  if (yValue < _yBins.front()) {
    yBin = -1;
    UOflow = true;
  }

  if (yValue >= _yBins.back()) {
    yBin = _yBins.size();
    UOflow = true;
  }

  if (xBin == 0) {

    while (xValue >= _xBins[xBin]) {
      xBin++;
    }
    xBin--;
  }

  if (yBin == 0) {

    while (yValue >= _yBins[yBin]) {
      yBin++;
    }
    yBin--;
  }

  if (!UOflow) {
    // No under or over flow.
    _histo[yBin][xBin] += (HistoType) weight;
    return;
  }
  else {
    // An under or over flow has happened.
    if (xBin == -1) {
      if (yBin == -1) {
        _xUnderYUnderflow += (HistoType) weight;
        return;
      }
      else {
        if (yBin == (int) _yBins.size()) {
          _xUnderYOverflow += (HistoType) weight;
          return;
        }
        else {
          _xUnderflow[yBin] += (HistoType) weight;
          return;
        }
      }
    }

    if (xBin == (int) _xBins.size()) {
      if (yBin == -1) {
        _xOverYUnderflow += (HistoType) weight;
        return;
      }
      else {
        if (yBin == (int) _yBins.size()) {
          _xOverYOverflow += (HistoType) weight;
          return;
        }
        else {
          _xOverflow[yBin] += (HistoType) weight;
          return;
        }
      }
    }

    if (yBin == -1) {
      _yUnderflow[xBin] += (HistoType) weight;
      return;
    }

    if (yBin == (int) _yBins.size()) {
      _yOverflow[xBin] += (HistoType) weight;
      return;
    }

  }
}

template<class HistoType>
void Histo2DAction<HistoType>::Finalize() {

  if (_outFileBase != "") {
    // Write the ROOT file
    if (_rootHisto && _outRoot) {
      TFile outRootFile((_outFileBase + ".root"), _mode);
      outRootFile.cd();
      _rootHisto->Write();
      outRootFile.Close();
    }

    //Write the text file
    if (_outText) {
      ofstream outTextFile((_outFileBase + ".txt").Data(), ios_base::out);
      for (unsigned int i = 0; i < _histo.GetNRows(); i++) {
        for (unsigned int j = 0; j < _histo.GetNCols(); j++) {
          outTextFile << setw(7) << _histo[i][j] << "  ";
        }
        outTextFile << "\n";
      }
      outTextFile.close();
    }
  }

}
#endif /* HISTO2DACTION_H_ */
