//===-- rosa/agent/Abstraction.hpp ------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/Abstraction.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Definition of *abstraction* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_ABSTRACTION_HPP
#define ROSA_AGENT_ABSTRACTION_HPP

#include "rosa/agent/Functionality.h"

#include "rosa/support/debug.hpp"

#include <algorithm>
#include <map>

namespace rosa {
namespace agent {

/// Abstracts values from a type to another one.
///
/// \tparam T type to abstract from
/// \tparam A type to abstract to
template <typename T, typename A> class Abstraction : public Functionality {
protected:
  /// Value to abstract to by default.
  const A Default;

public:
  /// Creates an instance.
  ///
  /// \param Default value to abstract to by default
  Abstraction(const A Default) noexcept : Default(Default) {}

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

  /// Abstracts a value from type \p T to type \p A.
  ///
  /// \note The default implementation always returns
  /// \c rosa::agent::Abstraction::Default, hence the actual argument is
  /// ignored.
  ///
  /// \return the abstracted value
  virtual A operator()(const T &) const noexcept { return Default; }
};

/// Implements \c rosa::agent::Abstraction as a \c std::map from a type to
/// another one.
///
/// \note This implementation is supposed to be used to abstract between
/// enumeration types, which is statically enforced.
///
/// \tparam T type to abstract from
/// \tparam A type to abstract to
template <typename T, typename A>
class MapAbstraction : public Abstraction<T, A>, private std::map<T, A> {
  // Make sure the actual type arguments are enumerations.
  STATIC_ASSERT((std::is_enum<T>::value && std::is_enum<A>::value),
                "mapping not enumerations");

  // Bringing into scope inherited members.
  using Abstraction<T, A>::Default;
  using std::map<T, A>::end;
  using std::map<T, A>::find;

public:
  /// Creates an instance by initializing the underlying \c std::map.
  ///
  /// \param Map the mapping to do abstraction according to
  /// \param Default value to abstract to by default
  MapAbstraction(const std::map<T, A> &Map, const A Default) noexcept
      : Abstraction<T, A>(Default),
        std::map<T, A>(Map) {}

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

  /// Abstracts a value 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::MapAbstraction::Default if the actual argument is not
  /// associated with anything by 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 {
    const auto I = find(V);
    return I == end() ? Default : *I;
  }
};

/// Implements \c rosa::agent::Abstraction as a \c std::map from ranges of a
/// type to values of another type.
///
/// \note This implementation is supposed to be used to abstract ranges of
/// arithmetic types into enumerations, 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 RangeAbstraction : public Abstraction<T, A>,
                         private std::map<std::pair<T, T>, A> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<T>::value), "abstracting not arithmetic");
  /// \todo check if this compiles with the definition of abstractions as
  /// self-aware properties
  //STATIC_ASSERT((std::is_enum<A>::value), "abstracting not to enumeration");

  // Bringing into scope inherited members.
  using Abstraction<T, A>::Default;
  using std::map<std::pair<T, T>, A>::begin;
  using std::map<std::pair<T, T>, A>::end;
  using std::map<std::pair<T, T>, A>::find;

public:
  /// Creates an instance by Initializing the unserlying \c std::map.
  ///
  /// \param Map the mapping to do abstraction according to
  /// \param Default value 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.
  RangeAbstraction(const std::map<std::pair<T, T>, A> &Map, const A &Default)
      : Abstraction<T, A>(Default), std::map<std::pair<T, T>, A>(Map) {
    // Sanity check.
    ASSERT(std::all_of(
        begin(), end(), [this](const std::pair<std::pair<T, T>, A> &P) {
          return P.first.first <= P.first.second &&
                 std::all_of(++find(P.first), end(),
                             [&P](const std::pair<std::pair<T, T>, A> &R) {
                               // \note Values in \c Map are sorted.
                               return P.first.first < P.first.second &&
                                          P.first.second <= R.first.first ||
                                      P.first.first == P.first.second &&
                                          P.first.second < R.first.first;
                             });
        }));
  }

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

  /// Abstracts a value 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 {
    auto I = begin();
    bool Found = false;  // Indicates if \c I refers to a matching range.
    bool Failed = false; // Indicates if it is pointless to continue searching.
    while (!Found && !Failed && I != end()) {
      if (V < I->first.first) {
        // No match so far and \p V is below the next range, never will match.
        // \note Keys are sorted in the map.
        Failed = true;
      } else if (I->first.first <= V && V < I->first.second) {
        // Matching range found.
        Found = true;
      } else {
        // Cannot conclude in this step, move to the next range.
        ++I;
      }
    }
    ASSERT(!Found || I != end());
    return Found ? I->second : Default;
  }
};

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

#endif // ROSA_AGENT_ABSTRACTION_HPP
