#ifndef __JROOT__JROOTTOOLKIT__
#define __JROOT__JROOTTOOLKIT__

#include <string>
#include <istream>
#include <ostream>
#include <limits>
#include <cctype>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#include "TString.h"
#include "TObjString.h"
#include "TRegexp.h"
#include "TPRegexp.h"
#include "TFormula.h"
#include "TFile.h"
#include "TStreamerInfo.h"
#include "TList.h"
#include "TIterator.h"
#include "TF1.h"
#include "TH1.h"
#include "TH2.h"
#include "TGraph.h"
#include "TGraphErrors.h"
#include "TGraph2D.h"
#include "TGraph2DErrors.h"
#include "TMultiGraph.h"
#include "TNtuple.h"
#pragma GCC diagnostic pop

#include "JLang/JType.hh"
#include "JLang/JLangToolkit.hh"

#include "Jeep/JPrint.hh"

#include "JROOT/JRootFile.hh"
#include "JROOT/JRootDictionary.hh"
#include "JROOT/JRootPrinter.hh"

/**
 * \author mdejong, mlincett
 */

namespace JROOT {}
namespace JPP { using namespace JROOT; }

namespace JROOT {

  using JLANG::JType;


  /**
   * Get ROOT name of given data type.
   *
   * \return            name of object to be read
   */
  template<class T>
  inline const char* getName()
  {
    return getName(JType<T>());
  }


  /**
   * Get ROOT name of given data type.
   *
   * \param  type       data type
   * \return            name of object to be read
   */
  template<class T>
  inline const char* getName(const JType<T>& type)
  {
    return T::Class_Name();
  }


  /**
   * Detach TH1 object and optionally reset contents.
   *
   * \param  object                      ROOT TH1 object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TH1* object, const bool reset = false)
  {
    if (object != NULL) {
      
      object->SetDirectory(0);

      if (reset) {
	object->Reset();
      }

      return true;
    }
    
    return false;
  }

  
  /**
   * Detach TGraph object and optionally reset contents.
   *
   * \param  object                      ROOT TGraph object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TGraph* object, const bool reset = false)
  {
    if (object != NULL) {

      if (reset) { 
	for (int i = 0; i != object->GetN(); ++i) {
	  object->SetPoint(i, 0.0, 0.0);
	}
      }
	
      return true;
    }
    
    return false;
  }

  
  /**
   * Detach TGraphErrors object and optionally reset contents.
   *
   * \param  object                      ROOT TGraphErrors object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TGraphErrors* object, const bool reset = false)
  {
    if (object != NULL) {

      if (reset) {

	for (int i = 0; i != object->GetN(); ++i) {
	  object->SetPoint     (i, 0.0, 0.0);
	  object->SetPointError(i, 0.0, 0.0);
	}
      }
	
      return true;
    }
    
    return false;
  }

  
  /**
   * Detach TGraph2D object and optionally reset contents.
   *
   * \param  object                      ROOT TGraph2D object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TGraph2D* object, const bool reset = false)
  {
    if (object != NULL) {

      if (reset) { 
	for (int i = 0; i != object->GetN(); ++i) {
	  object->SetPoint(i, 0.0, 0.0, 0.0);
	}
      }
	
      return true;
    }
    
    return false;
  }

  
  /**
   * Detach TGraph2DErrors object and optionally reset contents.
   *
   * \param  object                      ROOT TGraph2DErrors object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TGraph2DErrors* object, const bool reset = false)
  {
    if (object != NULL) {

      if (reset) {
	for (int i = 0; i != object->GetN(); ++i) {
	  object->SetPoint     (i, 0.0, 0.0, 0.0);
	  object->SetPointError(i, 0.0, 0.0, 0.0);
	}
      }
	
      return true;
    }
    
    return false;
  }

  
  /**
   * Detach TMultiGraph object and optionally reset contents.
   *
   * \param  object                      ROOT TMultiGraph object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TMultiGraph* object, const bool reset = false)
  {
    if (object != NULL) {

      bool status = true;
      
      if (reset) {

	TIter next(object->GetListOfGraphs());

	while (TGraph* graph = (TGraph*)next()) {

	  if (!(resetObject(dynamic_cast<TGraph2DErrors*>(graph), reset) ||
		resetObject(dynamic_cast<TGraph2D*>      (graph), reset)) &&
	      !(resetObject(dynamic_cast<TGraphErrors*>  (graph), reset) ||
		resetObject                              (graph,  reset)) ) {
	    
	    status = false;
	  }
	}
      }
	
      return status;
    }
    
    return false;
  }


  /**
   * Detach TTree object and optionally reset contents.
   *
   * \param  object                      ROOT TTree object
   * \param  reset                       reset contents if true
   * \return                             true if successful; else false
   */
  inline bool resetObject(TTree* object, const bool reset = false)
  {
    if (object != NULL) {
      
      object->SetDirectory(0);

      if (reset) {
	object->Reset();
      }

      return true;
    }
    
    return false;
  }


  /**
   * Add point to TGraph.
   *
   * \param  g1                          pointer to valid ROOT TGraph object
   * \param  x                           x value
   * \param  y                           y value
   */
  inline void AddPoint(TGraph*        g1,
		       const Double_t x,
		       const Double_t y)
  {
    const Int_t n = g1->GetN();

    g1->Set(n + 1);
    g1->SetPoint(n, x, y);
  }


  /**
   * Add point to TGraph2D.
   *
   * \param  g1                          pointer to valid ROOT TGraph object
   * \param  x                           x value
   * \param  y                           y value
   * \param  z                           z value
   */
  inline void AddPoint(TGraph2D*      g1,
		       const Double_t x,
		       const Double_t y,
		       const Double_t z)
  {
    const Int_t n = g1->GetN();

    g1->Set(n + 1);
    g1->SetPoint(n, x, y, z);
  }


  /**
   * Add point to TGraphErrors.
   *
   * \param  g1                          pointer to valid ROOT TGraph object
   * \param  x                           x value
   * \param  y                           y value
   * \param  ex                          x error
   * \param  ey                          y error
   */
  inline void AddPoint(TGraphErrors*  g1,
		       const Double_t x,
		       const Double_t y,
		       const Double_t ex,
		       const Double_t ey)
  {
    const Int_t n = g1->GetN();

    g1->Set(n + 1);
    g1->SetPoint(n, x, y);
    g1->SetPointError(n, ex, ey);
  }


  /**
   * Add point to TGraph2DErrors.
   *
   * \param  g1                          pointer to valid ROOT TGraph object
   * \param  x                           x value
   * \param  y                           y value
   * \param  z                           z value
   * \param  ex                          x error
   * \param  ey                          y error
   * \param  ez                          z error
   */
  inline void AddPoint(TGraph2DErrors* g1,
		       const Double_t  x,
		       const Double_t  y,
		       const Double_t  z,
		       const Double_t  ex,
		       const Double_t  ey,
		       const Double_t  ez)
  {
    const Int_t n = g1->GetN();

    g1->Set(n + 1);
    g1->SetPoint(n, x, y, z);
    g1->SetPointError(n, ex, ey, ez);
  }
  

  /**
   * Write object to ROOT file.
   *
   * \param  file         ROOT file
   * \param  object       ROOT object
   * \return              ROOT file
   */
  inline TFile& operator<<(TFile& file, const TObject& object)
  {
    file.WriteTObject(&object);

    return file;
  }
  

  /**
   * Get ROOT streamer information of class with given name.
   * Note that the class name should include the name space, if any.
   *
   * \param  file         pointer to ROOT file
   * \param  name         class name
   * \return              pointer to TStreamerInfo (NULL in case of error)
   */
  inline const TStreamerInfo* getStreamerInfo(TFile* file, const char* const name) 
  {
    if (file != NULL && file->IsOpen()) {

      const TString buffer(name);
	
      TIter iter(file->GetStreamerInfoList());

      for (const TStreamerInfo* pStreamerInfo; (pStreamerInfo = (TStreamerInfo*) iter.Next()) != NULL; ) {
	if (buffer == TString(pStreamerInfo->GetName())) {
	  return pStreamerInfo;
	}
      }
    }

    return NULL;
  }


  /**
   * Get ROOT streamer information of class with given name.
   * Note that the class name should include the name space, if any.
   *
   * \param  file_name    file name
   * \param  name         class name
   * \return              pointer to TStreamerInfo (NULL in case of error)
   */
  inline const TStreamerInfo* getStreamerInfo(const char* const file_name, const char* const name) 
  {
    JRootInputFile file(file_name);

    return getStreamerInfo(file.getFile(), name);
  }


  /**
   * Get ROOT streamer version of class with given name.
   * Note that the class name should include the name space, if any.
   *
   * \param  file         pointer to ROOT file
   * \param  name         class name
   * \return              streamer version; (-1 in case of error)
   */
  inline int getStreamerVersion(TFile* file, const char* const name) 
  {
    const TStreamerInfo* pStreamerInfo = getStreamerInfo(file, name);
    
    if (pStreamerInfo != NULL)
      return pStreamerInfo->GetClassVersion();
    else
      return -1;
  }


  /**
   * Get ROOT streamer version of class with given name.
   * Note that the class name should include the name space, if any.
   *
   * \param  file_name    file name
   * \param  name         class name
   * \return              streamer version; (-1 in case of error)
   */
  inline int getStreamerVersion(const char* const file_name, const char* const name) 
  {
    JRootInputFile file(file_name);
    
    return getStreamerVersion(file.getFile(), name);
  }


  /**
   * Match a regular expression with given string and return the specified matched parts.
   *
   * If no matches are found corresponding to the specified index, the original string is returned.
   *
   * \param  regexp       regular expression
   * \param  string       input string
   * \param  index        index of matched parts (starting at 1)
   * \return              matched part of string
   */
  inline TString parse(const TPRegexp& regexp, const TString& string, const int index = 1) 
  {
    TPRegexp   buffer = regexp;
    TObjArray* array  = buffer.MatchS(string);
    TString    result = string;

    if (index - 1 < array->GetLast()) {
      result = ((TObjString*) array->At(index))->GetName();
    }

    delete array;

    return result;
  }


  /**
   * Auxiliary data structure for a parameter index and its value.
   */
  struct JFitParameter_t {
    /**
     * Default constructor.
     */
    JFitParameter_t() :
      index(0),
      value(0.0)
    {}


    /**
     * Constructor.
     *
     * \param  index        parameter index
     * \param  value        parameter value
     */
    JFitParameter_t(const Int_t    index,
		    const Double_t value) :
      index(index),
      value(value)
    {}


    /**
     * Type conversion operator
     *
     *
     * \return              index
     */
    inline operator Int_t() const
    { 
      return index;
    }

     
    Int_t    index;
    Double_t value;
  };
  

  /**
   * Set fit parameter.
   *
   * \param  f1           fit function
   * \param  parameter    parameter index and value
   */
  inline bool setParameter(TF1& f1, const JFitParameter_t& parameter)
  {
    if (parameter.index >= 0 && parameter.index < f1.GetNpar()) {

      f1.SetParameter(parameter.index, parameter.value);

      return true;

    } else {

      return false;
    }
  }


  /**
   * Fix fit parameter.
   *
   * \param  f1           fit function
   * \param  parameter    parameter index and value
   */
  inline bool fixParameter(TF1& f1, const JFitParameter_t& parameter)
  {
    if (parameter.index >= 0 && parameter.index < f1.GetNpar()) {

      f1.FixParameter(parameter.index, parameter.value);

      return true;

    } else {

      return false;
    }
  }


  /**
   * Release fit parameter.
   *
   * \param  f1           fit function
   * \param  index        parameter index
   */
  inline bool releaseParameter(TF1& f1, const Int_t index)
  {
    if (index >= 0 && index < f1.GetNpar()) {

      f1.ReleaseParameter(index);

      return true;

    } else {

      return false;
    }
  }


  /**
   * Set fit parameter limits.
   *
   * \param  f1           fit function
   * \param  index        parameter index
   * \param  xmin         lower limit
   * \param  xmax         upper limit
   */
  inline bool setParLimits(TF1& f1, const Int_t index, Double_t xmin, Double_t xmax)
  {
    using namespace std;

    if (index >= 0 && index < f1.GetNpar()) {

      if (xmin == 0.0) { xmin = -numeric_limits<Double_t>::min(); }
      if (xmax == 0.0) { xmax = +numeric_limits<Double_t>::min(); }

      f1.SetParLimits(index, xmin, xmax);

      return true;

    } else {

      return false;
    }
  }


  /**
   * Check if fit parameter is fixed.
   *
   * \param  f1           fit function
   * \param  index        parameter index
   */
  inline bool isParameterFixed(const TF1& f1, const Int_t index)
  {
    if (index >= 0 && index < f1.GetNpar()) {

      Double_t xmin;
      Double_t xmax;
    
      f1.GetParLimits(index, xmin, xmax);
    
      return (xmin != 0.0 && xmax != 0.0 && xmin >= xmax);

    } else {
      
      return false;
    }
  }


  /**
   * Get result of given textual formula.
   *
   * The formula may contain names of data members of the given object.
   * For example:
   * <pre>
   *         getResult("a / b.value", object, ..);
   * </pre>
   * In this, the corresponding data type should have (at least) data members <tt>a</tt> and <tt>b</tt>
   * and <tt>b</tt> should have (at least) data member <tt>value</tt>.
   *
   * \param  text           text
   * \param  object         object
   * \param  dictionary     dictionary
   * \return                value
   */
  template<class T>
  inline Double_t getResult(const TString&           text,
			    const T&                 object,
			    const JRootDictionary_t& dictionary = JRootDictionary::getInstance())
  {
    using namespace std;
    using namespace JPP;

    const TRegexp DOUBLE("[0-9.][eE][+-0-9]");

    string buffer = (const char*) text;

    for (string::size_type pos = 0; pos != buffer.size(); ) {

      if (isalpha(buffer[pos]) || buffer[pos] == '_') {

	string::size_type len = 1;

	while (pos + len != buffer.size() && (isalnum(buffer[pos+len]) || buffer[pos+len] == '_' || buffer[pos+len] == '.')) {
	  ++len;
	}

	if (len != 1 || pos == 0 || TString(buffer.substr(pos-1).c_str()).Index(DOUBLE) != 0) {

	  ostringstream os;

	  os.setf(ios::fixed);
	  os.precision(10);

	  JRootPrinter::print(os, object, buffer.substr(pos,len), dictionary);

	  buffer.replace(pos, len, os.str());

	  pos += os.str().size();

	  continue;
	}
      }

      pos += 1;
    }

    return TFormula("/tmp", buffer.c_str()).Eval(0.0);
  }
  

  /**
   * Helper method to convert a 1D histogram to a graph.
   *
   * The graph consists of:
   *   - bin centers  as x values;
   *   - bin contents as y values.
   *
   * Note that underflow and overflow bins are ignored.
   *
   * \param  h1            1D histogram
   * \return               pointer to newly create graph
   */
  inline TGraph* histogramToGraph(const TH1& h1)
  {
    const int N = h1.GetNbinsX();

    std::vector<double> x(N), y(N);

    for (int i = 0; i < N; i++) {
      x[i] = h1.GetBinCenter (i + 1);
      y[i] = h1.GetBinContent(i + 1);
    }

    return new TGraph(N, &x[0], &y[0]);
  }

  
  /**
   * Helper method for ROOT histogram projections.
   *
   * \param  h2            2D histogram
   * \param  xmin          lower limit
   * \param  xmax          upper limit
   * \param  projection    projection (x|X|y|Y)
   * \return               pointer to newly created 1D histogram
   */
  inline TH1* projectHistogram(const TH2& h2, const Double_t xmin, const Double_t xmax, const char projection)
  {
    switch (projection) {

    case 'x':
    case 'X':
      return h2.ProjectionX("_px", h2.GetYaxis()->FindBin(xmin), h2.GetYaxis()->FindBin(xmax));

    case 'y':
    case 'Y': 
      return h2.ProjectionY("_py", h2.GetXaxis()->FindBin(xmin), h2.GetXaxis()->FindBin(xmax));

    default:
      return NULL;
    }
  }
}


/**
 * Read regular expression from input stream
 *
 * \param  in           input stream
 * \param  object       regular expression
 * \return              output stream
 */
inline std::istream& operator>>(std::istream& in, TRegexp& object)
{
  std::string buffer;

  if (in >> buffer) {
    object = TRegexp(buffer.c_str());
  }

  return in;
}
 

/**
 * Write regular expression to output stream
 *
 * \param  out          output stream
 * \param  object       regular expression
 * \return              output stream
 */
inline std::ostream& operator<<(std::ostream& out, const TRegexp& object)
{
  return out;
}

#endif