#ifndef YODA_BINESTIMATORS_H
#define YODA_BINESTIMATORS_H

#include "YODA/Utils/fastlog.h"
#include "YODA/Utils/MathUtils.h"
#include <cstdlib>

namespace YODA {

    /// @brief Bin estimator
    ///
    /// Base class for guessing the right bin index for a given value. The
    /// better the guess, the less time spent looking.
    struct BinEstimator {

      /// Virtual destructor needed for inheritance
      virtual ~BinEstimator() {}

      /// Return offset bin index estimate, with 0 = underflow and Nbins+1 = overflow
      size_t estindex(double x) const {
        const int i = _est(x);
        if (i < 0) return 0;
        const size_t i2 = (size_t) i;
        if (i2 >= _N) return _N+1;
        return i2 + 1;
      }

      /// Return offset bin index estimate, with 0 = underflow and Nbins+1 = overflow
      size_t operator() (double x) const {
        return estindex(x);
      }

    protected:

      /// Make an int-valued estimate of bin index
      /// @note No range checking or underflow offset
      virtual int _est(double x) const = 0;

      /// Number of bins
      size_t _N;
    };


    /// @brief Linear bin estimator
    ///
    /// This class handles guessing a index bin with a hypothesis of uniformly
    /// spaced bins on a linear scale.
    struct LinBinEstimator : public BinEstimator {

      /// Constructor
      LinBinEstimator(size_t nbins, double xlow, double xhigh) {
        _N = nbins;
        _c = xlow;
        _m = (double) nbins / (xhigh - xlow);
      }

      /// Copy constructor
      LinBinEstimator(const LinBinEstimator& other) {
        _N = other._N;
        _c = other._c;
        _m = other._m;
      }

      /// Call operator returns estimated bin index (offset so 0 == underflow)
      int _est(double x) const {
        int res =(int) floor(_m * (x - _c));
        return res; 
      }

    protected:
      double _c, _m;
    };


    /// @brief Logarithmic bin estimator
    ///
    /// This class handles guessing a bin index with a hypothesis of uniformly
    /// spaced bins on a logarithmic scale.
    ///
    /// @todo Make a generalised version of this with a transform function
    class LogBinEstimator : public BinEstimator {
    public:

      /// Constructor
      LogBinEstimator(size_t nbins, double xlow, double xhigh) {
        _N = nbins;
        _c = log2(xlow);
        _m = nbins / (log2(xhigh) - _c);
      }

      /// Copy constructor
      LogBinEstimator(const LogBinEstimator& other) {
        _N = other._N;
        _c = other._c;
        _m = other._m;
      }

      /// Call operator returns estimated bin index (offset so 0 == underflow)
      int _est(double x) const {
        int res =(int) floor(_m * (Utils::fastlog2(x) - _c));
        return res;

      }

    protected:
      double _c, _m;
    };
}

#endif
