// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_histo_axis
#define tools_histo_axis

#include <string>
#include <vector>

namespace tools {
namespace histo {

enum { axis_UNDERFLOW_BIN = -2, axis_OVERFLOW_BIN = -1 }; //AIDA casing.

//TC is for a coordinate.
//TO is for an offset used to identify a bin.

template <class TC,class TO>
class axis {
public:
  typedef unsigned int bn_t;
public:
  enum { UNDERFLOW_BIN = axis_UNDERFLOW_BIN, OVERFLOW_BIN = axis_OVERFLOW_BIN };
public:
  bool is_fixed_binning() const {return m_fixed;}
  TC lower_edge() const {return m_minimum_value;}
  TC upper_edge() const {return m_maximum_value;}
  bn_t bins() const {return m_number_of_bins;}
  const std::vector<TC>& edges() const {return m_edges;}

  TC bin_width(int a_bin) const {
    if(a_bin==UNDERFLOW_BIN) {
      return 0; //FIXME return DBL_MAX;
    } else if(a_bin==OVERFLOW_BIN) {
      return 0; //FIXME return DBL_MAX;
    } else if((a_bin<0) ||(a_bin>=(int)m_number_of_bins)) {
      return 0;
    } else {
      if(m_fixed) {
        return m_bin_width;
      } else {
        return (m_edges[a_bin+1]-m_edges[a_bin]);
      }
    }
  }

  TC bin_lower_edge(int a_bin) const {
    if(a_bin==UNDERFLOW_BIN) {
      return 0; //FIXME return -DBL_MAX;
    } else if(a_bin==OVERFLOW_BIN) {
      return 0; //FIXME return bin_upper_edge(m_number_of_bins-1);
    } else if((a_bin<0) ||(a_bin>=(int)m_number_of_bins)) {
      return 0;
    } else {
      if(m_fixed) {
        return (m_minimum_value + a_bin * m_bin_width);
      } else {
        return m_edges[a_bin];
      }
    }
  }

  TC bin_upper_edge(int a_bin) const {
    if(a_bin==UNDERFLOW_BIN) {
      return 0; //FIXME bin_lower_edge(0)
    } else if(a_bin==OVERFLOW_BIN) {
      return 0; //FIXME return DBL_MAX;
    } else if((a_bin<0) ||(a_bin>=(int)m_number_of_bins)) {
      return 0;
    } else {
      if(m_fixed) {
        return (m_minimum_value + (a_bin + 1) * m_bin_width);
      } else {
        return m_edges[a_bin+1];
      }
    }
  }

  TC bin_center(int a_bin) const {
    if(a_bin==UNDERFLOW_BIN) {
      return 0; //FIXME : -INF
    } else if(a_bin==OVERFLOW_BIN) {
      return 0; //FIXME : +INF
    } else if(a_bin<0) {
      return 0; //FIXME : -INF
    } else if(a_bin>=(int)m_number_of_bins) {
      return 0; //FIXME : +INF
    } else {
      if(m_fixed) {
        return (m_minimum_value + (a_bin + 0.5) * m_bin_width);
      } else {
        return (m_edges[a_bin] + m_edges[a_bin+1])/2.;
      }
    }
  }

  int coord_to_index(TC a_value) const {
    if( a_value <  m_minimum_value) {
      return UNDERFLOW_BIN;
    } else if( a_value >= m_maximum_value) {
      return OVERFLOW_BIN;
    } else {
      if(m_fixed) {
        return (int)((a_value - m_minimum_value)/m_bin_width);
      } else {
        for(bn_t index=0;index<m_number_of_bins;index++) {
          if((m_edges[index]<=a_value)&&(a_value<m_edges[index+1])) {
            return index;
          }
        }
        // Should never pass here...
        return UNDERFLOW_BIN;
      }
    }
  }

  bool coord_to_absolute_index(TC a_value,bn_t& a_index) const {
    if( a_value <  m_minimum_value) {
      a_index = 0;
      return true;
    } else if( a_value >= m_maximum_value) {
      a_index = m_number_of_bins+1;
      return true;
    } else {
      if(m_fixed) {
        a_index = (bn_t)((a_value - m_minimum_value)/m_bin_width)+1;
        return true;
      } else {
        for(bn_t index=0;index<m_number_of_bins;index++) {
          if((m_edges[index]<=a_value)&&(a_value<m_edges[index+1])) {
            a_index = index+1;
            return true;
          }
        }
        // Should never pass here...
        a_index = 0;
        return false;
      }
    }
  }

  bool in_range_to_absolute_index(int a_in,bn_t& a_out) const {
    // a_in is given in in-range indexing :
    //  - [0,n-1] for in-range bins
    //  - UNDERFLOW_BIN for the iaxis underflow bin
    //  - OVERFLOW_BIN for the iaxis overflow bin
    // Return the absolute indexing in [0,n+1].
    if(a_in==UNDERFLOW_BIN) {
      a_out = 0;
      return true;
    } else if(a_in==OVERFLOW_BIN) {
      a_out = m_number_of_bins+1;
      return true;
    } else if((a_in>=0)&&(a_in<(int)m_number_of_bins)){
      a_out = a_in + 1;
      return true;
    } else {
      return false;
    }
  }

public:
  // Partition :
  bool configure(const std::vector<TC>& a_edges) {
    // init :
    m_number_of_bins = 0;
    m_minimum_value = 0;
    m_maximum_value = 0;
    m_fixed = true;
    m_bin_width = 0;
    m_edges.clear();
    // setup :
    if(a_edges.size()<=1) return false;
    bn_t number = (bn_t)a_edges.size()-1;
    for(bn_t index=0;index<number;index++) {
      if((a_edges[index]>=a_edges[index+1])) {
        return false;
      }
    }
    m_edges = a_edges;
    m_number_of_bins = number;
    m_minimum_value = a_edges[0];
    m_maximum_value = a_edges[m_number_of_bins];
    m_fixed = false;
    return true;
  }

  bool configure(bn_t aNumber,TC aMin,TC aMax) {
    // init :
    m_number_of_bins = 0;
    m_minimum_value = 0;
    m_maximum_value = 0;
    m_fixed = true;
    m_bin_width = 0;
    m_edges.clear();
    // setup :
    if(aNumber<=0) return false;
    if(aMax<=aMin) return false;
    m_number_of_bins = aNumber;
    m_minimum_value = aMin;
    m_maximum_value = aMax;
    m_bin_width = (aMax - aMin)/ aNumber;
    m_fixed = true;
    return true;
  }

  bool is_compatible(const axis& a_axis) const {
    if(m_number_of_bins!=a_axis.m_number_of_bins) return false;
    if(m_minimum_value!=a_axis.m_minimum_value) return false;
    if(m_maximum_value!=a_axis.m_maximum_value) return false;
    return true;
  }

public:
  axis()
  :m_offset(0)
  ,m_number_of_bins(0)
  ,m_minimum_value(0)
  ,m_maximum_value(0)
  ,m_fixed(true)
  ,m_bin_width(0)
  {}

  virtual ~axis(){}
public:
  axis(const axis& a_from)
  :m_offset(a_from.m_offset)
  ,m_number_of_bins(a_from.m_number_of_bins)
  ,m_minimum_value(a_from.m_minimum_value)
  ,m_maximum_value(a_from.m_maximum_value)
  ,m_fixed(a_from.m_fixed)
  ,m_bin_width(a_from.m_bin_width)
  ,m_edges(a_from.m_edges)
  {}

  axis& operator=(const axis& a_from) {
    if(&a_from==this) return *this;
    m_offset = a_from.m_offset;
    m_number_of_bins = a_from.m_number_of_bins;
    m_minimum_value = a_from.m_minimum_value;
    m_maximum_value = a_from.m_maximum_value;
    m_fixed = a_from.m_fixed;
    m_bin_width = a_from.m_bin_width;
    m_edges = a_from.m_edges;
    return *this;
  }
public:
  bool equals(const axis& a_from) const {
    if(&a_from==this) return true;
    if(m_offset!=a_from.m_offset) return false;
    if(m_number_of_bins!=a_from.m_number_of_bins) return false;
    if(m_minimum_value!=a_from.m_minimum_value) return false;
    if(m_maximum_value!=a_from.m_maximum_value) return false;
    if(m_fixed!=a_from.m_fixed) return false;
    if(m_bin_width!=a_from.m_bin_width) return false;
    if(m_edges!=a_from.m_edges) return false;
    return true;
  }

  bool operator==(const axis& a_from) const {return equals(a_from);}
  bool operator!=(const axis& a_from) const {return !equals(a_from);}

public:
  TO m_offset;
  bn_t m_number_of_bins;
  TC m_minimum_value;
  TC m_maximum_value;
  bool m_fixed;
  // Fixed size bins :
  TC m_bin_width;
  // Variable size bins :
  std::vector<TC> m_edges;
};

}}

#endif




