//===-- rosa/agent/RangeConfidence.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/RangeConfidence.hpp
///
/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *RangeConfidence* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_RANGECONFIDENCE_HPP
#define ROSA_AGENT_RANGECONFIDENCE_HPP

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

#include "rosa/support/debug.hpp"

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

namespace rosa {
namespace agent {

/// Evaluates a map of ID's to Abstractions at a given value and returns the
/// results as a map from ID's to results of the corresponding Abstraction
///
/// \note This implementation is supposed to be used to abstract ranges of
/// arithmetic types into maps whose values are of another arithmetic type,
/// which is statically enforced.
///
/// \tparam D type to abstract from
/// \tparam I type the type of the ID's
/// \tparam R type of the range
template <typename D, typename I, typename R>
class RangeConfidence : protected Abstraction<D, std::map<I, R>>,
                        private std::map<I, PartialFunction<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");

private:
  /// Wether to include default results in the result-map or not
  bool IgnoreDefaults;

public:
  /// Creates an instance by Initializing the underlying \c Abstraction and
  /// \c std::map.
  ///
  /// \param Abstractions the Abstractions to be evaluated
  /// \param IgnoreDefaults wether to include default results in the result-map
  /// or not (defaults to false).
  RangeConfidence(const std::map<I, PartialFunction<D, R>> &Abstractions,
                  bool IgnoreDefaults = false)
      : Abstraction<D, std::map<I, R>>({}), std::map<I, PartialFunction<D, R>>(
                                                Abstractions),
        IgnoreDefaults(IgnoreDefaults) {}

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

  /// Checks wether all Abstractions evaluate to default at the given position
  ///
  /// \param V the value at which to check if the functions falls back to it's
  /// default value.
  ///
  /// \return true, if all Abstractions evaluate to default
  bool isDefaultAt(const D &V) const noexcept override {
    for (auto const &P : ((std::map<I, PartialFunction<D, R>>)*this)) {
      if (!P.second.isDefaultAt(V))
        return false;
    }
    return true;
  }

  /// All Abstractions stored in the underlying \c std::map are evaluated for
  /// the given value. Their results are stored in another map, with
  /// corresponding keys.
  /// If IgnoreDefaults is set, Abstractions that default for that value are not
  /// evaluated and inserted into the resulting \c std::map
  ///
  /// \param V value to abstract
  ///
  /// \return a \c std::map containing the results of the stored Abstractions,
  /// indexable by the key's the Abstractions are associated with
  std::map<I, R> operator()(const D &V) const noexcept override {
    std::map<I, R> Ret;
    for (auto const &P : ((std::map<I, PartialFunction<D, R>>)*this)) {
      if (!IgnoreDefaults || !P.second.isDefaultAt(V))
        Ret.insert(std::pair<I, R>(P.first, P.second(V)));
    }
    return Ret;
  }
};
} // End namespace agent
} // End namespace rosa

#endif // ROSA_AGENT_RANGECONFIDENCE_HPP
