// -*- C++ -*-
//
// This file is part of YODA -- Yet more Objects for Data Analysis
// Copyright (C) 2008-2025 The YODA collaboration (see AUTHORS for details)
//
#ifndef YODA_BinnedUtils_h
#define YODA_BinnedUtils_h

#include "YODA/Utils/BinningUtils.h"

namespace YODA {

  /// @name Mixin of convenience methods using CRTP
  /// @{

  /// @brief CRTP mixin introducing convenience aliases along X axis.
  template <class Derived, typename EdgeT = double>
  struct XAxisMixin {

    /// @name Bin accessors
    /// @{

    /// @brief Number of bins along the X axis
    size_t numBinsX(const bool includeOverflows=false) const {
      return static_cast<const Derived*>(this)->numBinsAt(0, includeOverflows);
    }

    /// @brief Low edge of first histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> xMin() const { return static_cast<const Derived*>(this)->template min<0>(); }

    /// @brief High edge of first histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> xMax() const { return static_cast<const Derived*>(this)->template max<0>(); }

    /// @brief All bin edges on X axis. +-inf edges are included.
    std::vector<EdgeT> xEdges(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template edges<0>(includeOverflows);
    }

    /// @brief All widths on X axis
    ///
    /// @note Only supported for continuous axes
    template <class T = EdgeT>
    std::enable_if_t<std::is_floating_point<T>::value, std::vector<T>>
    xWidths(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template widths<0>(includeOverflows);
    }

    void rebinXBy(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<0>(n, begin, end);
    }

    void rebinXTo(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<0>(newedges);
    }

    void rebinX(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<0>(n, begin, end);
    }

    void rebinX(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<0>(newedges);
    }

    /// @}

  };


  /// @brief CRTP mixin introducing convenience aliases to access statistics along X axis.
  template <class Derived>
  struct XStatsMixin {

    /// @name Whole histo data
    /// @{

    /// @brief Calculate the mean on X axis
    double xMean(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->mean(0, includeOverflows);
    }

    /// @brief Calculate the variance on X axis
    double xVariance(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->variance(0, includeOverflows);
    }

    /// @brief Calculate the standard deviation on X axis
    double xStdDev(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdDev(0, includeOverflows);
    }

    /// @brief Calculate the standard error on X axis
    double xStdErr(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdErr(0, includeOverflows);
    }

    /// @brief Calculate the RMS on X axis
    double xRMS(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->rms(0, includeOverflows);
    }

    /// @}
  };


  /// @brief CRTP mixin introducing convenience aliases along Y axis.
  template <class Derived, typename EdgeT = double>
  struct YAxisMixin {

    /// @name Bin accessors
    /// @{

    /// @brief Number of bins along the Y axis
    size_t numBinsY(const bool includeOverflows=false) const {
      return static_cast<const Derived*>(this)->numBinsAt(1, includeOverflows);
    }

    /// @brief Low edge of second histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> yMin() const { return static_cast<const Derived*>(this)->template min<1>(); }

    /// @brief High edge of second histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> yMax() const { return static_cast<const Derived*>(this)->template max<1>(); }

    /// @brief All bin edges on Y axis. +-inf edges are included.
    std::vector<EdgeT> yEdges(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template edges<1>(includeOverflows);
    }

    /// @brief All widths on Y axis
    ///
    /// @note Only supported for continuous axes
    template <class T = EdgeT>
    std::enable_if_t<std::is_floating_point<T>::value, std::vector<T>>
    yWidths(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template widths<1>(includeOverflows);
    }

    void rebinYBy(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<1>(n, begin, end);
    }

    void rebinYTo(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<1>(newedges);
    }

    void rebinY(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<1>(n, begin, end);
    }

    void rebinY(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<1>(newedges);
    }

    /// @}

  };

  /// @brief CRTP mixin introducing convenience aliases to access statistics along Y axis.
  template <class Derived>
  struct YStatsMixin {

    /// @name Whole histo data
    /// @{

    /// @brief Calculate the mean on Y axis
    double yMean(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->mean(1, includeOverflows);
    }

    /// @brief Calculate the variance on Y axis
    double yVariance(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->variance(1, includeOverflows);
    }

    /// @brief Calculate the standard deviation on Y axis
    double yStdDev(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdDev(1, includeOverflows);
    }

    /// @brief Calculate the standard error on Y axis
    double yStdErr(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdErr(1, includeOverflows);
    }

    /// @brief Calculate the RMS on Y axis
    double yRMS(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->rms(1, includeOverflows);
    }

  };


  /// @brief CRTP mixin introducing convenience aliases along Z axis.
  template <class Derived, typename EdgeT = double>
  struct ZAxisMixin {

    /// @name Bin accessors
    /// @{

    /// @brief Number of bins along the Z axis
    size_t numBinsZ(const bool includeOverflows=false) const {
      return static_cast<const Derived*>(this)->numBinsAt(2, includeOverflows);
    }

    /// @brief Low edge of second histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> zMin() const { return static_cast<const Derived*>(this)->template min<2>(); }

    /// @brief High edge of second histo's axis
    template <class T = EdgeT>
    enable_if_CAxisT<T> zMax() const { return static_cast<const Derived*>(this)->template max<2>(); }

    /// @brief All bin edges on Z axis. +-inf edges are included.
    std::vector<EdgeT> zEdges(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template edges<2>(includeOverflows);
    }

    /// @brief All widths on Z axis
    ///
    /// @note Only supported for continuous axes
    template <class T = EdgeT>
    std::enable_if_t<std::is_floating_point<T>::value, std::vector<T>>
    zWidths(const bool includeOverflows = false) const {
      return static_cast<const Derived*>(this)->template widths<2>(includeOverflows);
    }

    void rebinZBy(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<2>(n, begin, end);
    }

    void rebinZTo(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<2>(newedges);
    }

    void rebinZ(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
      static_cast<Derived*>(this)->template rebinBy<2>(n, begin, end);
    }

    void rebinZ(const std::vector<double>& newedges) {
      static_cast<Derived*>(this)->template rebinTo<2>(newedges);
    }

    /// @}

  };

  /// @brief CRTP mixin introducing convenience aliases to access statistics along Z axis.
  template <class Derived>
  struct ZStatsMixin {

    /// @name Whole histo data
    /// @{

    /// @brief Calculate the mean on Z axis
    double zMean(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->mean(2, includeOverflows);
    }

    /// @brief Calculate the variance on Z axis
    double zVariance(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->variance(2, includeOverflows);
    }

    /// @brief Calculate the standard deviation on Z axis
    double zStdDev(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdDev(2, includeOverflows);
    }

    /// @brief Calculate the standard error on Z axis
    double zStdErr(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->stdErr(2, includeOverflows);
    }

    /// @brief Calculate the RMS on Z axis
    double zRMS(const bool includeOverflows=true) const noexcept {
      return static_cast<const Derived*>(this)->rms(2, includeOverflows);
    }

  };

  /// @}

}

#endif
