#ifndef HstArray2_HH
#define HstArray2_HH

#include <cstring>

#include "TH1F.h"
#include "TH2F.h"


template <unsigned NumberOfXBins, unsigned NumberOfYBins> class HstArray2 {

public:
  HstArray2() {
    reset();
  }

  void reset() {
    memset(_array,0,(NumberOfXBins+2)*(NumberOfYBins+2)*sizeof(int));
  }

  unsigned numberOfXBins() const {
    return NumberOfXBins;
  }

  unsigned numberOfYBins() const {
    return NumberOfYBins;
  }

  int xBinToArray(int xBin) const {
    if(xBin<0) return 0;
    else if(xBin>=(int)NumberOfXBins) return NumberOfXBins+1;
    else return xBin+1;
  }

  int yBinToArray(int yBin) const {
    if(yBin<0) return 0;
    else if(yBin>=(int)NumberOfYBins) return NumberOfYBins+1;
    else return yBin+1;
  }

  int contents(int xBin, int yBin) const {
    return _array[xBinToArray(xBin)][yBinToArray(yBin)];
  }

  int xUnderflow(int yBin) const {
    return _array[0][yBinToArray(yBin)];
  }

  int xOverflow(int yBin) const {
    return _array[NumberOfXBins+1][yBinToArray(yBin)];
  }

  int yUnderflow(int xBin) const {
    return _array[xBinToArray(xBin)][0];
  }

  int yOverflow(int xBin) const {
    return _array[xBinToArray(xBin)][NumberOfYBins+1];
  }

  int entries() const {
    int sum(0);
    for(unsigned i(0);i<NumberOfXBins+2;i++) {
      for(unsigned j(0);j<NumberOfYBins+2;j++) {
	sum+=_array[i][j];
      }
    }
    return sum;
  }

  void fill(int xBin, int yBin, int weight=1) {
    _array[xBinToArray(xBin)][yBinToArray(yBin)]+=weight;
    return;
  }

  void fillXUnderflow(int yBin, int weight=1) {
    _array[0][yBinToArray(yBin)]+=weight;
  }

  void fillXOverflow(int yBin, int weight=1) {
    _array[NumberOfXBins+1][yBinToArray(yBin)]+=weight;
  }

  void fillYUnderflow(int xBin, int weight=1) {
    _array[xBinToArray(xBin)][0]+=weight;
  }

  void fillYOverflow(int xBin, int weight=1) {
    _array[xBinToArray(xBin)][NumberOfYBins+1]+=weight;
  }

  // ROOT interface
  TH2F* makeTH2F(const std::string &label, const std::string &title,
		 unsigned xGranularity=1, unsigned yGranularity=1) {

    assert(xGranularity>0);
    assert(yGranularity>0);

    unsigned xBins((NumberOfXBins+xGranularity-1)/xGranularity);
    unsigned yBins((NumberOfYBins+yGranularity-1)/yGranularity);

    return new TH2F(label.c_str(),title.c_str(),
		    xBins,0.0,xBins*xGranularity,
		    yBins,0.0,yBins*yGranularity);
  }

  TH1F* xMakeTH1F(const std::string &label, const std::string &title,
		  unsigned granularity=1, 
		  double xLo=0.0, double xHi=NumberOfXBins) {

    assert(granularity>0);

    unsigned xBins((NumberOfXBins+granularity-1)/granularity);

    return new TH1F(label.c_str(),title.c_str(),
		    xBins,xLo,xHi);
  }

  TH1F* yMakeTH1F(const std::string &label, const std::string &title,
		  unsigned granularity=1,
		  double yLo=0.0, double yHi=NumberOfXBins) {

    assert(granularity>0);

    unsigned yBins((NumberOfYBins+granularity-1)/granularity);

    return new TH1F(label.c_str(),title.c_str(),
		    yBins,yLo,yHi);
  }

  void fillTH2F(TH2F *hst) {
    unsigned xGranularity((NumberOfXBins+hst->GetNbinsX()-1)/hst->GetNbinsX());
    unsigned yGranularity((NumberOfYBins+hst->GetNbinsY()-1)/hst->GetNbinsY());
 
    for(unsigned i(0);i<(unsigned)hst->GetNbinsX();i++) {
      for(unsigned j(0);j<(unsigned)hst->GetNbinsY();j++) {
	int sum(0);
	for(unsigned k(xGranularity*i);k<xGranularity*(i+1) && k<NumberOfXBins;k++) {
	  for(unsigned l(yGranularity*j);l<yGranularity*(j+1) && l<NumberOfYBins;l++) {
	    sum+=contents(k,l);
	  }
	}
	hst->SetBinContent(i+1,j+1,sum);
      }
    }
    
    for(unsigned j(0);j<(unsigned)hst->GetNbinsY();j++) {
      hst->SetBinContent(0,j,xUnderflow(j));
      hst->SetBinContent(hst->GetNbinsX()+1,j,xOverflow(j));
    }

    for(unsigned i(0);i<(unsigned)hst->GetNbinsX();i++) {
      hst->SetBinContent(i,0,yUnderflow(i));
      hst->SetBinContent(i,hst->GetNbinsY()+1,yOverflow(i));
    }

    hst->SetEntries(entries());
  }

  void xFillTH1F(TH1F *hst) {
    std::vector<int> v;
    for(int i(-1);i<(int)(NumberOfYBins+1);i++) v.push_back(i);
    xFillTH1F(hst,v);
  }

  void xFillTH1F(TH1F *hst, int yBin) {
    std::vector<int> v;
    v.push_back(yBin);
    xFillTH1F(hst,v);
  }

  void xFillTH1F(TH1F *hst, int yBinLo, int yBinHi) {
    std::vector<int> v;
    for(int i(yBinLo);i<=yBinHi;i++) v.push_back(i);
    xFillTH1F(hst,v);
  }

  void xFillTH1F(TH1F *hst, std::vector<int> yBins) {
    unsigned granularity((NumberOfXBins+hst->GetNbinsX()-1)/hst->GetNbinsX());
 
    int entries(0);
    for(unsigned i(0);i<(unsigned)hst->GetNbinsX();i++) {
      int sum(0);
      for(unsigned j(granularity*i);j<granularity*(i+1) && j<NumberOfXBins;j++) {
	for(unsigned k(0);k<yBins.size();k++) {
	  sum+=contents(j,yBins[k]);
	}
      }
      hst->SetBinContent(i+1,sum);
      entries+=sum;
    }
 
    int sumU(0),sumO(0);
    for(unsigned j(0);j<yBins.size();j++) {
      sumU+=xUnderflow(yBins[j]);
      sumO+=xOverflow(yBins[j]);
    }
    entries+=sumO;
    entries+=sumU;

    hst->SetBinContent(0,sumU);
    hst->SetBinContent(hst->GetNbinsX()+1,sumO);

    hst->SetEntries(entries);
  }

  std::ostream& print(std::ostream &o, std::string s="") const {
    o << s << "HstArray2<" << NumberOfXBins << ">::print()" << std::endl;

    o << s << " Entries   = " << std::setw(6) << entries() << std::endl;
    o << s << " Underflow = " << std::setw(6) << xUnderflow(5) << std::endl;
    for(int i(0);i<(int)NumberOfXBins;i++) {
      o << s << " XBin " << std::setw(5) << i << " = " 
	<< std::setw(6) << contents(i,5) << std::endl;
    }
    o << s << " Overflow  = " << std::setw(6) << xOverflow(5) << std::endl;

    return o;
  }


private:
  int _array[NumberOfXBins+2][NumberOfYBins+2];
};

#endif
