//===-- rosa/agent/FunctionAbstractions.hpp ---------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/FunctionAbstractions.hpp
///
/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *FunctionAbstractions* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP
#define ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP

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

#include "rosa/support/debug.hpp"

#include <algorithm>
#include <vector>
#include <cmath>
#include <memory>

namespace rosa {
namespace agent {

/// Evaluates a linear function at a given value.
///
/// \tparam T type of the functions domain
/// \tparam A type of the functions range
template <typename T, typename A> class LinearFunction :
	public Abstraction<T, A>{
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<T>::value),
    "LinearFunction not arithmetic T");
  STATIC_ASSERT((std::is_arithmetic<A>::value),
    "LinearFunction not to arithmetic");
protected:
  const T Intercept;
  const T Coefficient;

public:
  /// Creates an instance.
  ///
  /// \param Intercept the intercept of the linear function
  /// \param Coefficient the coefficient of the linear function
  /// domain
  LinearFunction(T Intercept, T Coefficient) noexcept
      : Abstraction<T, A>(Intercept),
        Intercept(Intercept),
        Coefficient(Coefficient) {}

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

  /// Evaluates the linear function
  ///
  /// \param X the value at which to evaluate the function
  /// \return the result
  virtual A operator()(const T &X) const noexcept override {
    return Intercept + X*Coefficient;
  }
};

/// Evaluates a sine function at a given value.
///
/// \tparam T type of the functions domain
/// \tparam A type of the functions range
template <typename T, typename A> class SineFunction :
	public Abstraction<T, A>{
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<T>::value),
    "SineFunction not arithmetic T");
  STATIC_ASSERT((std::is_arithmetic<A>::value),
    "SineFunction not to arithmetic");
protected:
  const T Frequency;
  const T Amplitude;
  const T Phase;
  const T Average;

public:
  /// Creates an instance.
  ///
  /// \param Frequency the frequency of the sine wave
  /// \param Amplitude the amplitude of the sine wave
  /// \param Phase the phase of the sine wave
  /// \param Average the average of the sine wave
  /// domain
  SineFunction(T Frequency, T Amplitude, T Phase, T Average) noexcept
      : Abstraction<T, A>(Average),
        Frequency(Frequency),
        Amplitude(Amplitude),
        Phase(Phase),
        Average(Average) {}

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

  /// Evaluates the linear function
  ///
  /// \param X the value at which to evaluate the function
  /// \return the result
  virtual A operator()(const T &X) const noexcept override {
    return Amplitude*sin(Frequency * X + Phase) + Average;
  }
};

/// Implements \c rosa::agent::RangeAbstraction as an abstraction from
/// \c std::map from ranges of a type to abstractions of that type to another
/// type. The resulting abstractions are evaluated for the given values.
///
/// \note This implementation is supposed to be used to abstract ranges of
/// arithmetic types into abstractions from that type to another arithmetic
/// type, which is statically enforced.
///
/// \invariant The keys in the underlying \c std::map define valid ranges
/// such that `first <= second` and there are no overlapping ranges defined by
/// the keys.
///
/// \tparam T type to abstract from
/// \tparam A type to abstract to
template <typename T, typename A>
class PartialFunction : private Abstraction<T, A> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<T>::value), "abstracting not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<A>::value),
      "abstracting not to arithmetic");

private:
  RangeAbstraction<T, std::shared_ptr<Abstraction<T, A>>> RA;

public:
  /// Creates an instance by Initializing the underlying \c RangeAbstraction.
  ///
  /// \param Map the mapping to do abstraction according to
  /// \param Default abstraction to abstract to by default
  ///
  /// \pre Each key defines a valid range such that `first <= second` and
  /// there are no overlapping ranges defined by the keys.
  PartialFunction(const std::map<std::pair<T, T>,
      std::shared_ptr<Abstraction<T, A>>> &Map,
    const A Default)
      : Abstraction<T, A>(Default),
        RA(Map, std::shared_ptr<Abstraction<T, A>>
          (new Abstraction<T, A>(Default))) {
  }

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

  /// Evaluates an Abstraction from type \p T to type \p A based on the set
  /// mapping.
  ///
  /// Results in the value associated by the set mapping to the argument, or
  /// \c rosa::agent::RangeAbstraction::Default if the actual argument is not
  /// included in any of the ranges in the set mapping.
  ///
  /// \param V value to abstract
  ///
  /// \return the abstracted value based on the set mapping
  A operator()(const T &V) const noexcept override {
    return RA(V)->operator()(V);
  }
};
} // End namespace agent
} // End namespace rosa

#endif // ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP
