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

#include <type_traits>

namespace YODA {

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

  /// @brief CRTP mixin introducing convenience aliases along X axis.
  template <class Derived>
  struct XDirectionMixin {

    /// @name Point accessors
    /// @{

    /// Get x value
    double x() const {
      return static_cast<const Derived*>(this)->vals()[0];
    }

    /// Set the x value
    void setX(double x) {
      static_cast<Derived*>(this)->setVal(0, x);
    }

    /// @}

    /// @name x error accessors
    /// @{

    /// Get x error pair
    std::pair<double,double> xErrs() const {
      return static_cast<const Derived*>(this)->errs(0);
    }

    /// Get minus x error
    double xErrMinus() const {
      return static_cast<const Derived*>(this)->errMinus(0);
    }

    /// Get plus x error
    double xErrPlus() const {
      return static_cast<const Derived*>(this)->errPlus(0);
    }

    // Get the average x error
    double xErrAvg() const {
      return static_cast<const Derived*>(this)->errAvg(0);
    }

    /// Set the minus x errors
    void setXErrMinus(double err) {
      static_cast<Derived*>(this)->setErrMinus(0, err);
    }

    /// Set the plus x errors
    void setXErrPlus(double err) {
      static_cast<Derived*>(this)->setErrPlus(0, err);
    }

    /// Set symmetric x error
    void setXErr(double ex) {
      setXErrMinus(ex);
      setXErrPlus(ex);
    }

    /// Set symmetric x error (alias)
    void setXErrs(double ex) {
      setXErr(ex);
    }

    /// Set the x errors
    void setXErrs(double errminus, double errplus) {
      static_cast<Derived*>(this)->setErrs(0, {errminus, errplus});
    }

    /// Set the x errors
    void setXErrs(std::pair<double,double> errs) {
      static_cast<Derived*>(this)->setErrs(0, errs);
    }

    /// Get value minus negative x-error
    double xMin() const {
      return x() - xErrMinus();
      return static_cast<const Derived*>(this)->min(0);
    }

    /// Get value plus positive x-error
    double xMax() const {
      return static_cast<const Derived*>(this)->max(0);
    }

    /// @}

    /// @name Combined x value and error setters
    /// @{

    /// Set x value and symmetric error
    void setX(double x, double ex) {
      setX(x);
      setXErr(ex);
    }

    /// Set x value and asymmetric error
    void setX(double x, double exminus, double explus) {
      setX(x);
      setXErrs(exminus, explus);
    }

    /// Set x value and asymmetric error
    void setX(double x, std::pair<double,double>& ex) {
      setX(x);
      setXErrs(ex);
    }

    /// @}

    // @name Manipulations
    /// @{

    /// Scaling of x axis
    void scaleX(double scalex) {
      setX(x()*scalex);
      setXErrs(xErrMinus()*scalex, xErrPlus()*scalex);
    }

    /// @}


  };


  /// @brief CRTP mixin introducing convenience aliases along Y axis.
  template <class Derived>
  struct YDirectionMixin {

    /// @name Point accessors
    /// @{

    /// Get y value
    double y() const {
      return static_cast<const Derived*>(this)->vals()[1];
    }

    /// Set the y value
    void setY(double y) {
      static_cast<Derived*>(this)->setVal(1, y);
    }

    std::pair<double,double> xy() const {
      const auto& vals = static_cast<const Derived*>(this)->vals();
      return {vals[0], vals[1]};
    }

    /// Set x and y values
    void setXY(double x, double y) {
      static_cast<Derived*>(this)->setVal(0, x);
      static_cast<Derived*>(this)->setVal(1, y);
    }

    /// Set x and y values
    void setXY(const std::pair<double,double>& xy) {
      setXY(xy.first, xy.second);
    }

    /// @}

    /// @name x error accessors
    /// @{

    /// Get y error pair
    std::pair<double,double> yErrs() const {
      return static_cast<const Derived*>(this)->errs(1);
    }

    /// Get minus y error
    double yErrMinus() const {
      return static_cast<const Derived*>(this)->errMinus(1);
    }

    /// Get plus y error
    double yErrPlus() const {
      return static_cast<const Derived*>(this)->errPlus(1);
    }

    // Get the average y error
    double yErrAvg() const {
      return static_cast<const Derived*>(this)->errAvg(1);
    }

    /// Set the minus y errors
    void setYErrMinus(double err) {
      static_cast<Derived*>(this)->setErrMinus(1, err);
    }

    /// Set the plus y errors
    void setYErrPlus(double err) {
      static_cast<Derived*>(this)->setErrPlus(1, err);
    }

    /// Set symmetric y error
    void setYErr(double ey) {
      setYErrMinus(ey);
      setYErrPlus(ey);
    }

    /// Set symmetric y error (alias)
    void setYErrs(double ey) {
      setYErr(ey);
    }

    /// Set the y errors
    void setYErrs(double errminus, double errplus) {
      static_cast<Derived*>(this)->setErrs(1, {errminus, errplus});
    }

    /// Set the y errors
    void setYErrs(std::pair<double,double> errs) {
      static_cast<Derived*>(this)->setErrs(1, errs);
    }

    /// Get value minus negative y-error
    double yMin() const {
      return static_cast<const Derived*>(this)->min(1);
    }

    /// Get value plus positive y-error
    double yMax() const {
      return static_cast<const Derived*>(this)->max(1);
    }

    /// @}

    /// @name Combined y value and error setters
    /// @{

    /// Set y value and symmetric error
    void setY(double y, double ey) {
      setY(y);
      setYErr(ey);
    }

    /// Set y value and asymmetric error
    void setY(double y, double eyminus, double eyplus) {
      setY(y);
      setYErrs(eyminus, eyplus);
    }

    /// Set y value and asymmetric error
    void setY(double y, std::pair<double,double>& ey) {
      setY(y);
      setYErrs(ey);
    }

    /// @}

    // @name Manipulations
    /// @{

    /// Scaling of y axis
    void scaleY(double scaley) {
      setY(y()*scaley);
      setYErrs(yErrMinus()*scaley, yErrPlus()*scaley);
    }

    /// @}


  };


  /// @brief CRTP mixin introducing convenience aliases along Z axis.
  template <class Derived>
  struct ZDirectionMixin {

    /// @name Point accessors
    /// @{

    /// Get z value
    double z() const {
      return static_cast<const Derived*>(this)->vals()[2];
    }

    /// Set the y value
    void setZ(double z) {
      static_cast<Derived*>(this)->setVal(2, z);
    }

    /// Set x and y values
    void setXYZ(double x, double y, double z) {
      static_cast<Derived*>(this)->setVal(0, x);
      static_cast<Derived*>(this)->setVal(1, y);
      static_cast<Derived*>(this)->setVal(2, z);
    }

    /// @}

    /// @name x error accessors
    /// @{

    /// Get y error pair
    std::pair<double,double> zErrs() const {
      return static_cast<const Derived*>(this)->errs(2);
    }

    /// Get minus z error
    double zErrMinus() const {
      return static_cast<const Derived*>(this)->errMinus(2);
    }

    /// Get plus z error
    double zErrPlus() const {
      return static_cast<const Derived*>(this)->errPlus(2);
    }

    // Get the average z error
    double zErrAvg() const {
      return static_cast<const Derived*>(this)->errAvg(2);
    }

    /// Set the minus z errors
    void setZErrMinus(double err) {
      static_cast<Derived*>(this)->setErrMinus(2, err);
    }

    /// Set the plus y errors
    void setZErrPlus(double err) {
      static_cast<Derived*>(this)->setErrPlus(2, err);
    }

    /// Set symmetric z error
    void setZErr(double ez) {
      setZErrMinus(ez);
      setZErrPlus(ez);
    }

    /// Set symmetric z error (alias)
    void setZErrs(double ez) {
      setZErr(ez);
    }

    /// Set the z errors
    void setZErrs(double errminus, double errplus) {
      static_cast<Derived*>(this)->setErrs(2, {errminus, errplus});
    }

    /// Set the z errors
    void setZErrs(std::pair<double,double> errs) {
      static_cast<Derived*>(this)->setErrs(2, errs);
    }

    /// Get value minus negative y-error
    double zMin() const {
      return static_cast<const Derived*>(this)->min(2);
    }

    /// Get value plus positive y-error
    double zMax() const {
      return static_cast<const Derived*>(this)->max(2);
    }

    /// @}

    /// @name Combined z value and error setters
    /// @{

    /// Set z value and symmetric error
    void setZ(double z, double ez) {
      setZ(z);
      setZErr(ez);
    }

    /// Set y value and asymmetric error
    void setZ(double z, double ezminus, double ezplus) {
      setZ(z);
      setZErrs(ezminus, ezplus);
    }

    /// Set z value and asymmetric error
    void setZ(double z, std::pair<double,double>& ez) {
      setZ(z);
      setZErrs(ez);
    }

    /// @}

    // @name Manipulations
    /// @{

    /// Scaling of z axis
    void scaleZ(double scalez) {
      setZ(z()*scalez);
      setZErrs(zErrMinus()*scalez, zErrPlus()*scalez);
    }

    /// @}


  };

  /// @}

}

#endif
