//===-- rosa/agent/Confidence.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/Confidence.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Definition of *confidence* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_CONFIDENCE_HPP
#define ROSA_AGENT_CONFIDENCE_HPP

#include "rosa/agent/History.hpp"

#include "rosa/support/debug.hpp"

#include <limits>

namespace rosa {
namespace agent {

/// Confidence validator.
///
/// Checks the plausibility of given values by validating if a valid region
/// contains them. It also capable of checking consistency by validating the
/// rate of change recorded by a \c rosa::agent::History object against a
/// maximal absolute valid rate of change.
///
/// \tparam T type of values to validate
///
/// \note The template is defined only for arithmetic types.
///
/// \note The lower bound is inclusive and the upper bound is exclusive.
///
/// \invariant The bounds are defined in a meaningful way:\code
/// LowerBound <= UpperBound
/// \endcode
template <typename T> class Confidence : public Functionality {
  // Make sure the actual type argument is an arithmetic type.
  STATIC_ASSERT(std::is_arithmetic<T>::value, "not arithmetic Confidence");

public:
  /// Unsigned type corresponding to \p T.
  using UT = unsigned_t<T>;

  /// The minimal value of type \p T.
  /// \note Not exist \c V of type \p T such that `V < Min`.
  static constexpr T Min = std::is_integral<T>::value
                               ? std::numeric_limits<T>::min()
                               : std::numeric_limits<T>::lowest();

  /// The maximal value of type \p T.
  /// \note Not exist \c V of type \p T such that `V > Max`.
  static constexpr T Max =
      (std::is_integral<T>::value || !std::numeric_limits<T>::has_infinity)
          ? std::numeric_limits<T>::max()
          : std::numeric_limits<T>::infinity();

  /// The maximal value of type \c UT.
  /// \note Not exist \c V of type \c UT such that `V > UnsignedMax`.
  static constexpr UT UnsignedMax =
      (std::is_integral<UT>::value || !std::numeric_limits<UT>::has_infinity)
          ? std::numeric_limits<UT>::max()
          : std::numeric_limits<UT>::infinity();

private:
  /// The inclusive lower bound for plausibility check.
  T LowerBound;

  /// The exclusive upper bound for plausibility check.
  T UpperBound;

  /// The maximal absolute rate of change for consistency check.
  UT ChangeRate;

public:
  /// Creates an instance by setting the validator variables.
  ///
  /// \param LowerBound the lower bound for plausability check
  /// \param UpperBound the upper bound for plausability check
  /// \param ChangeRate maximal absolute rate of change for consistency check
  ///
  /// \pre The bounds are defined in a meaningful way:\code
  /// LowerBound <= UpperBound
  /// \endcode
  Confidence(const T LowerBound = Min, const T UpperBound = Max,
             const UT ChangeRate = UnsignedMax) noexcept
      : LowerBound(LowerBound), UpperBound(UpperBound), ChangeRate(ChangeRate) {
    // Make sure Confidence is created in a valid state.
    if (LowerBound > UpperBound) {
      ROSA_CRITICAL("Confidence with LowerBound higher than UpperBound");
    }
  }

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

  /// Gives a snapshot of the current state of the validator variables.
  ///
  /// \param [out] LowerBound to copy \c rosa::agent::Confidence::LowerBound
  /// into
  /// \param [out] UpperBound to copy \c rosa::agent::Confidence::UpperBound
  /// into
  /// \param [out] ChangeRate to copy \c rosa::agent::Confidence::ChangeRate
  /// into
  void getParameters(T &LowerBound, T &UpperBound, UT &ChangeRate) const
      noexcept {
    // Copy members to the given references.
    LowerBound = this->LowerBound;
    UpperBound = this->UpperBound;
    ChangeRate = this->ChangeRate;
  }

  /// Sets the lower bound for plausability check.
  ///
  /// Beyond setting the lower bound, the function also adjusts the upper bound
  /// to the given lower bound if the new lower bound would be higher than the
  /// upper bound.
  ///
  /// \param LowerBound the new lower bound to set
  void setLowerBound(const T LowerBound) noexcept {
    // Adjust UpperBound if necessary, then set LowerBound.
    if (UpperBound < LowerBound) {
      UpperBound = LowerBound;
    }
    this->LowerBound = LowerBound;
  }

  /// Sets the upper bound for plausability check.
  ///
  /// Beyond setting the upper bound, the function also adjusts the lower bound
  /// to the given upper bound if the new upper bound would be lower than the
  /// lower bound.
  ///
  /// \param UpperBound the new upper bound to set
  void setUpperBound(const T UpperBound) noexcept {
    // Adjust LowerBound if necessary, then set UpperBound.
    if (UpperBound < LowerBound) {
      LowerBound = UpperBound;
    }
    this->UpperBound = UpperBound;
  }

  /// Sets the maximal rate of change for consistency check.
  ///
  /// \param ChangeRate the new rate of change to set
  void setChangeRate(const UT ChangeRate) noexcept {
    // Set ChangeRate.
    this->ChangeRate = ChangeRate;
  }

  /// Tells the binary confidence on the plausibility of a value.
  ///
  /// \param V value to check
  ///
  /// \return whether \c V is within the range defined by
  /// \c rosa::agent::Confidence::LowerBound and
  /// \c rosa::agent::Confidence::UpperBound.
  bool operator()(const T V) const noexcept {
    // Return if \c V is plausible.
    return LowerBound <= V && V < UpperBound;
  }

  /// Tells the binary confidence on the plausibility and consistency of the
  /// last value recorded by a \c rosa::agent::History instance.
  ///
  /// Consistency of the last value is checked by validating the difference with
  /// its preceding entry.
  ///
  /// \note The \c rosa::agent::History instance needs to store values of type
  /// \p T.
  ///
  /// \note An empty \c rosa::agent::History instance results in full
  /// confidence.
  ///
  /// \tparam N number of values \p H is able to store
  /// \tparam P retention policy followed by \p H when capacity is reached
  ///
  /// \param H *history* whose last entry to check
  template <size_t N, HistoryPolicy P>
  bool operator()(const StaticLengthHistory<T, N, P> &H) const noexcept {
    if (H.empty()) {
      // No entry to validate.
      return true;
    } else {
      // Validate the last entry and the one step average absolute difference.
      return (*this)(H.entry()) && H.averageAbsDiff(1) <= ChangeRate;
    }
  }
};

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

#endif // ROSA_AGENT_CONFIDENCE_HPP
