//===-- rosa/agent/DistanceMetrics.hpp ---------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/DistanceMetrics.hpp
///
/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at)
///
/// \date 2020
///
/// \brief Definition of *DistanceMetrics* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_DISTANCEMETRICS_HPP
#define ROSA_AGENT_DISTANCEMETRICS_HPP

#include "rosa/agent/Abstraction.hpp"
#include "rosa/agent/Functionality.h"

#include "rosa/support/debug.hpp"

#include <cmath>


namespace rosa {
namespace agent {

/// Implements \c rosa::agent::Abstraction as the absolute difference between
/// two values
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class AbsoluteDistance : public Abstraction<std::pair<D, D>, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  AbsoluteDistance(void) : Abstraction<std::pair<D, D>, R>(0) { }

  /// Destroys \p this object.
  ~AbsoluteDistance(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    // @NOTE reached >98% using 1_0.0001_0.005_1_5_absolute
    // inner 0.0001
    // outer 0.005
    return (V.first - V.second);
  }
};

/// Implements \c rosa::agent::Abstraction as the relative difference between
/// two values
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class RelativeDistance : public Abstraction<std::pair<D, D>, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  RelativeDistance(void) : Abstraction<std::pair<D, D>, R>(0) { }

  /// Destroys \p this object.
  ~RelativeDistance(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    R Dist = ((R)V.second) - V.first;
    if (Dist == 0) {
        return 0;
    } else {
	Dist = Dist / V.first;
    }
    return Dist;
  }
};

template <typename D, typename R>
class NormalizedDistanceMetric : public Abstraction<std::pair<D, D>, R> {

  protected:
    D Norm = 1;

  NormalizedDistanceMetric() : Abstraction<std::pair<D, D>, R>(0) {}
  ~NormalizedDistanceMetric(void) = default;

  public:
    void setNorm(D _Norm) {
      Norm = _Norm;
	}
	
    R operator()(const std::pair<D, D> &V, D _Norm) const noexcept {
		setNorm(_Norm);
		return operator()(V);
	}
};

/// Implements \c rosa::agent::Abstraction
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class DanielsDistance1 : public NormalizedDistanceMetric<D, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  DanielsDistance1(void) : NormalizedDistanceMetric<D, R>() { }

  /// Destroys \p this object.
  ~DanielsDistance1(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    // Daniels 1st proposal
    return (((R)V.second-V.first)*(std::abs(V.first)+this->Norm))/(std::pow(V.first,2)+this->Norm);
  }
};

/// Implements \c rosa::agent::Abstraction
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class DanielsDistance2 : public NormalizedDistanceMetric<D, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  DanielsDistance2(void) : NormalizedDistanceMetric<D, R>() { }

  /// Destroys \p this object.
  ~DanielsDistance2(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    // Daniels 2nd proposal
    return (((R)V.first-V.second)*(std::abs(V.first-V.second)+this->Norm))/(std::pow(V.first-V.second,2)+this->Norm);
  }
};

/// Implements \c rosa::agent::Abstraction
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class DanielsDistance3 : public NormalizedDistanceMetric<D, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  DanielsDistance3(void) : NormalizedDistanceMetric<D, R>() { }

  /// Destroys \p this object.
  ~DanielsDistance3(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    // Daniels 3rd proposal
    return ((R)V.first-V.second)*std::abs((R)V.first-V.second)/(this->Norm+std::abs((R)V.first-V.second));
  }
};

/// Implements \c rosa::agent::Abstraction
///
/// \note This implementation is supposed to be used to represent a difference-metric
/// function from an arithmetic domain to an arithmetic range. This is enforced
/// statically.
///
/// \tparam D type of the input values
/// \tparam R type of the difference
template <typename D, typename R>
class MaxisDistance1 : public NormalizedDistanceMetric<D, R> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<D>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<R>::value),
                "abstracting not to arithmetic");

public:
  /// Creates an instance by Initializing the underlying \c Abstraction.
  MaxisDistance1(void) : NormalizedDistanceMetric<D, R>() { }

  /// Destroys \p this object.
  ~MaxisDistance1(void) = default;

  /// Checks wether the Abstraction evaluates to default at the given position
  ///
  /// \param V the value at which to check if the function falls back to it's
  /// default value.
  ///
  /// \return false if the value falls into a defined range and the Abstraction
  /// defined for that range does not fall back to it's default value.
  bool isDefaultAt(const std::pair<D, D> &V) const noexcept override {
    (void)(V);
    return false;
  }

  /// Calculates the distance-metric for the given value. If this is the first
  /// value, the Default-Value is returned
  ///
  /// \param V value to abstract
  ///
  /// \return the absolute distanct
  R operator()(const std::pair<D, D> &V) const noexcept override {
    // Maxis proposal
    return ((R)V.first-V.second)/(this->Norm+std::abs(V.first-V.second));
  }
};

} // End namespace agent
} // End namespace rosa

#endif // ROSA_AGENT_DISTANCEMETRICS_HPP
