#ifndef HISTOGRAM_HH_INCLUDED
#define HISTOGRAM_HH_INCLUDED

#include "Computable.hh"
using stringutil::print;
using stringutil::exec;
using stringutil::strip_comments;


/*! a generic wrapper around the root 1d, 2d, and 3d histogram classes */

struct Histogram
{
  string name;
  string analysis_name;   // something that ends up in the name.
  string flavor;

  string vars;      // e.g. Enu:cos_theta (refer to Observable names)
  vector< Observable* > _obs_ptrs; // the quantities on the axes
  int dim;          // dimesion
  TH1* H;           // The actual root histogram

  string selectionname;
  string weightname;
  string variantname;
  vector<string> options; // for post-processing etc

  Variant*   variant;   //!
  Selection* selection; //!
  Weight*    weight;    //!

  string draw_option;

  /*! colon separated list of all observable names */
  string observables() const
  {
    return join(":",  _obs_ptrs, [] (Observable * p) { return p->name ; } );
  }

  string key() const {
    auto vars = observables();
    return analysis_name + "_" + flavor + "_" + vars + variantname + selectionname + weightname;
  }

  string title()
  {
    key();
    return analysis_name + " " + flavor + " " + variantname + " " + selectionname + " " + weightname;
  }

  Histogram(): H(0) { }  // initializing pointers to zero is super-important for ROOT io (prevent horrible segfaults)

  virtual ~Histogram() 
  {
    // print("Histrogram dtor", key() );
  }

  Histogram( string analysis_name_,
             string flavor_,
             const vector<Observable*>& observables,
             Variant&   variant_,
             Selection& selection_,
             Weight&    weight_ )
  {
    analysis_name   = analysis_name_;
    flavor     = flavor_;
    selection       = &selection_;
    weight          = &weight_;
    variant         = &variant_;

    selectionname = selection->name;
    weightname    = weight->name;
    variantname   = variant->name;

    _obs_ptrs = observables;

    dim = _obs_ptrs.size();

    vars = join(":", observables, [] (Observable * p) { return p->name ; } );

    name = "H_" + analysis_name + "_" + flavor + "_" +
    join( "_v_", _obs_ptrs, [](Observable * ptr) { return ptr->name; } ) + "_" +
    variant->name + "_" + selection->name + "_" + weight->name;

    string title = "";
    if ( dim == 1 )
    {
      H = new TH1D( name.c_str(), title.c_str(),
                    _obs_ptrs[0]->nbins  ,
                    _obs_ptrs[0]->minval ,
                    _obs_ptrs[0]->maxval );
      draw_option = "hist";
      H->SetXTitle( observables[0]->axis_label().c_str() );
    }

    if ( dim == 2 )
    {
      H = new TH2D( name.c_str(), title.c_str(),
                    _obs_ptrs[0]->nbins  ,
                    _obs_ptrs[0]->minval ,
                    _obs_ptrs[0]->maxval ,
                    _obs_ptrs[1]->nbins  ,
                    _obs_ptrs[1]->minval ,
                    _obs_ptrs[1]->maxval );

      draw_option = "colz";

      H->SetXTitle( observables[0]->axis_label().c_str() );
      H->SetYTitle( observables[1]->axis_label().c_str() );
    }


    if ( dim == 3 )
    {
      H = new TH3D( name.c_str(), title.c_str(),
                    _obs_ptrs[0]->nbins  ,
                    _obs_ptrs[0]->minval ,
                    _obs_ptrs[0]->maxval ,
                    _obs_ptrs[1]->nbins  ,
                    _obs_ptrs[1]->minval ,
                    _obs_ptrs[1]->maxval ,
                    _obs_ptrs[2]->nbins  ,
                    _obs_ptrs[2]->minval ,
                    _obs_ptrs[2]->maxval );

      draw_option = "BOX";
      H->SetXTitle( observables[0]->axis_label().c_str() );
      H->SetYTitle( observables[1]->axis_label().c_str() );
      H->SetZTitle( observables[2]->axis_label().c_str() );

    }

  }

  /*! Fill the histogram if selection is true, and using weight */
  void fill ( Evt& evt )
  {
    if ( selection->eval( evt ) == 0 ) return;
    double w = weight->eval(evt);
    slurp( evt, w );
  }


  void slurp( Evt& evt, double weight = 1  )
  {
    if ( dim == 1 ) H->Fill ( _obs_ptrs[0] -> eval(evt),
                                weight );

    if ( dim == 2 ) ( (TH2D*) H) ->Fill ( _obs_ptrs[0] -> eval(evt),
                                          _obs_ptrs[1] -> eval(evt),
                                          weight );

    if ( dim == 3 ) ( (TH3D*) H ) ->Fill ( _obs_ptrs[0] -> eval(evt),
                                           _obs_ptrs[1] -> eval(evt),
                                           _obs_ptrs[2] -> eval(evt),
                                           weight );
  }

  void slurp( Evt& evt, double weight  , int chache_id )
  {
    if ( dim == 1 ) H->Fill ( _obs_ptrs[0] -> eval(evt, chache_id),
                                weight );

    if ( dim == 2 ) ( (TH2D*) H) ->Fill ( _obs_ptrs[0] -> eval(evt, chache_id),
                                          _obs_ptrs[1] -> eval(evt, chache_id),
                                          weight );

    if ( dim == 3 ) ( (TH3D*) H ) ->Fill ( _obs_ptrs[0] -> eval(evt, chache_id),
                                           _obs_ptrs[1] -> eval(evt, chache_id),
                                           _obs_ptrs[2] -> eval(evt, chache_id),
                                           weight );
  }

  string info()
  {
    ostringstream s;
    auto write = stringutil::coolprinter::printer(s);

    write("histogram     : ", H -> GetName()   );
    write("title         : ", H -> GetTitle()  );
    write("analysis_name : ", analysis_name          );
    write("flavor        : ", flavor );
    write("variant       : ", variant->info()  );
    write("selection     : ", selection->info()      );
    write("weight        : ", weight->info()   );
    write("draw_option   : ", draw_option );

    for ( auto o : _obs_ptrs )
    {
      write( "observable: ", o->info() );
    }

    return s.str();
  }


  // why oh why is this not just in root (or: why can't I find it ;-)
  void set_style( TH1& donor )
  {
    H->TAttFill::operator=( donor );
    H->TAttLine::operator=( donor );
    H->TAttMarker::operator=( donor );
  }

};

inline Table tabulate( const vector<Histogram>& histograms )
{
    Table T( split("histogram-name key type flavor selection weight entries integral mean over under dir ")) ;

    T.title = "Histogram Table";

    for ( auto& o :  histograms )
    {
      string n = o.H->GetDirectory()? o.H->GetDirectory()->GetName() : "null";

      T << o.H->GetName()
        << o.key()
        << o.H->IsA()->GetName()
        << o.flavor 
        << o.selectionname
        << o.weightname
        << o.H->GetEntries()
        << o.H->Integral()
        << o.H->GetMean() 
        << o.H->GetBinContent(0)
        << o.H->GetBinContent( o.H->GetNbinsX() + 1 )
        << n ;
    }
    return T;
}

#endif