//===-- rosa/agent/Reliability.h --------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/Reliability.h
///
/// \author Daniel Schnoell (daniel.schnoell@tuwien.ac.at)
///
/// \date 2019
///
/// \brief  Definition of *reliability* *functionality*.
///
/// \note based on Maximilian Goetzinger (maxgot@utu.fi) code in
/// CAM_Dirty_include SA-EWS2_Version... inside Agent.cpp
///
/// \note By defining and setting Reliability_trace_level it is possible to
/// change the level to which it should be traced. \note All classes throw
/// runtime errors if not all things are set
///
/// \note should the Reliability be capped?
///
///
//===----------------------------------------------------------------------===//

// make combination modular

#ifndef ROSA_AGENT_RELIABILITY_H
#define ROSA_AGENT_RELIABILITY_H

#include "rosa/agent/CrossReliability.h"
#include "rosa/agent/FunctionAbstractions.hpp"
#include "rosa/agent/Functionality.h"
#include "rosa/agent/RangeConfidence.hpp"

#include <algorithm>
#include <type_traits>
#include <vector>

/// 0 everything
/// 1 vectors
/// 2 outputs
#define trace_everything 0
#define trace_vectors 1
#define trace_outputs 2

#ifndef Reliability_trace_level
#define Reliability_trace_level 0
#endif
#define trace_end "\n\n\n"

namespace rosa {
namespace agent {
/// This is a struct with a few methods that make lowlevel/highlevel Reliability
/// more readable \tparam StateType The datatype of the States \tparam
/// ReliabilityType The datatype of the Reliability
template <typename StateType, typename ReliabilityType> struct ConfOrRel {
  /// making both Template Arguments readable to make a few things easier
  typedef StateType _StateType;
  /// making both Template Arguments readable to make a few things easier
  typedef ReliabilityType _ReliabilityType;

  /// The actual place where the data is stored
  StateType score;
  /// The actual place where the data is stored
  ReliabilityType Reliability;

  ConfOrRel(StateType _score, ReliabilityType _Reliability)
      : score(_score), Reliability(_Reliability){};
  ConfOrRel(){};

  /// Pushes the Data in a Human readable form
  /// \param out The stream where it is written to
  /// \param c The struct itself
  friend std::ostream &operator<<(std::ostream &out, const ConfOrRel &c) {
    out << "Score: " << c.score << "\t Reliability: " << c.Reliability << " ";
    return out;
  }

  /// needed or it throws an clang diagnosic error
  typedef std::map<StateType, ReliabilityType>
      map; // needed or it throws an clang diagnosic error
  /// Filles the vector with the data inside the map
  /// \param me The vector to be filled
  /// \param data The data wich is to be pushed into the vector
  friend std::vector<ConfOrRel> &operator<<(std::vector<ConfOrRel> &me,
                                            map &&data) {
    for (auto tmp : data) {
      me.push_back(ConfOrRel(tmp.first, tmp.second));
#if Reliability_trace_level <= trace_everything
      LOG_TRACE_STREAM << "\n" << ConfOrRel(tmp.first, tmp.second) << trace_end;
#endif
    }
    return me;
  }

  /// This is to push the data inside a vector in a human readable way into the
  /// ostream \param out The ostream \param c The vector which is read
  friend std::ostream &operator<<(std::ostream &out,
                                  const std::vector<ConfOrRel> &c) {
    std::size_t index = 0;
    for (ConfOrRel data : c) {
      out << index << " : " << data << "\n";
      index++;
    }
    return out;
  }
};

/// This calculates the minimum of the Reliabilities & the given value
/// \param me The vector with the Reliabilities
/// \param value The comparing value
template <typename Conf>
std::vector<Conf> min(std::vector<Conf> me,
                      typename Conf::_ReliabilityType value) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto tmp : me)
    tmp.Reliability = std::min(tmp.Reliability, value);
  return me;
}
/// This calculates the maximum of the Reliabilities & the given value
/// \param me The vector with the Reliabilities
/// \param value The comparing value
template <typename Conf>
std::vector<Conf> max(std::vector<Conf> me,
                      typename Conf::_ReliabilityType value) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto tmp : me)
    tmp.Reliability = std::max(tmp.Reliability, value);
  return me;
}
/// This calculates the average of the Reliabilities & the given value
/// \param me The vector with the Reliabilities
/// \param value The comparing value
template <typename Conf>
std::vector<Conf> average(std::vector<Conf> me,
                          typename Conf::_ReliabilityType value) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto tmp : me)
    tmp.Reliability = (tmp.Reliability + value) / 2;
  return me;
}

/// This calculates the average of the Reliabilities & the given value
/// \param me The vector with the Reliabilities
/// \param value The comparing value
template <typename Conf>
std::vector<Conf> mult(std::vector<Conf> me,
                       typename Conf::_ReliabilityType value) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto tmp : me)
    tmp.Reliability = tmp.Reliability * value / 2;
  return me;
}

/// This average's the Reliabilities of the same Scores
template <typename Conf>
std::vector<Conf> average(std::vector<Conf> A, std::vector<Conf> B) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto &tmp_me : A)
    for (auto &tmp_other : B) {
      if (tmp_me.score == tmp_other.score) {
        tmp_me.Reliability = (tmp_me.Reliability + tmp_other.Reliability) / 2;
      }
    }
  return A;
}
/// This min's the Reliabilities of the same Scores
template <typename Conf>
std::vector<Conf> min(std::vector<Conf> A, std::vector<Conf> B) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto &tmp_me : A)
    for (auto &tmp_other : B) {
      if (tmp_me.score == tmp_other.score) {
        tmp_me.Reliability =
            std::min(tmp_me.Reliability + tmp_other.Reliability);
      }
    }
  return A;
}
/// This max's the Reliabilities of the same Scores
template <typename Conf>
std::vector<Conf> max(std::vector<Conf> A, std::vector<Conf> B) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto &tmp_me : A)
    for (auto &tmp_other : B) {
      if (tmp_me.score == tmp_other.score) {
        tmp_me.Reliability =
            std::max(tmp_me.Reliability + tmp_other.Reliability);
      }
    }
  return A;
}
/// This mult's the Reliabilities of the same Scores
template <typename Conf>
std::vector<Conf> mult(std::vector<Conf> A, std::vector<Conf> B) {
  static_assert(std::is_arithmetic<typename Conf::_ReliabilityType>::value);
  for (auto &tmp_me : A)
    for (auto &tmp_other : B) {
      if (tmp_me.score == tmp_other.score) {
        tmp_me.Reliability = tmp_me.Reliability * tmp_other.Reliability;
      }
    }
  return A;
}

/// This is the combinator for Reliability and confidences it takes the
/// Sensor value, its "History" and feedback from \c
/// CrossCombinator to calculate different Reliabilities.
/// \tparam SensorValueType Datatype of the Sensor value	( Typically
/// double or float) \tparam StateType Datatype of the State ( Typically long or
/// int)
///	\tparam ReliabilityType Datatype of the Reliability		(
/// Typically double	or float)
///
/// \note more information about how it calculates
/// the Reliabilities
/// \verbatim
///----------------------------------------------------------------------------------
///
///
///                  ->Reliability---> getInputReliability()
///                  |             |
///                  |             V
///  Sensor Value ---|            PossibleScoreCombinationMethod -> next line
///                  |             A					|
///                  |             |					V
///                  ->Confidence---			getPossibleScores()
///
///-----------------------------------------------------------------------------------
///
///           feedback
///               |
///               V
///           ValuesFromMaster
///               |               -> History ---|
///               V               |             V
/// here -> FeedbackCombinatorMethod --------> HistoryCombinatorMethod->nextline
///                   |                                   |
///                   V                                   V
///   getpossibleScoresWithMasterFeedback()      getPossibleScoresWithHistory()
///
///----------------------------------------------------------------------------------
///
/// here -> sort -> most likely -> mostLikelySoreAndReliability()
///
/// ---------------------------------------------------------------------------------
/// \endverbatim
/// the mentioned methods are early outs so if two ore more of them are run in
/// the same step they will be interpreted as different time steps
/// <pre>
/// Default values for Combinators:
///	InputReliabilityCombinator		= combinationMin;
///	PossibleScoreCombinationMethod	= PossibleScoreCombinationMethodMin;
/// FeedbackCombinatorMethod		= FeedbackCombinatorMethodAverage;
/// HistoryCombinatorMethod			= HistoryCombinatorMethodMax;
///	</pre>
///
///
///
template <typename SensorValueType, typename StateType,
          typename ReliabilityType>
class ReliabilityAndConfidenceCombinator {
public:
  static_assert(std::is_arithmetic<SensorValueType>::value,
                "LowLevel: SensorValueType has to an arithmetic type\n");
  static_assert(std::is_arithmetic<StateType>::value,
                "LowLevel: StateType has to an arithmetic type\n");
  static_assert(std::is_arithmetic<ReliabilityType>::value,
                "LowLevel: ReliabilityType has to an arithmetic type\n");

  /// Typedef to shorten the writing.
  /// \c ConfOrRel
  typedef ConfOrRel<StateType, ReliabilityType> ConfOrRel;

  /// Calculates the input reliability by combining Reliability of the Sensor
  /// and the Slope Reliability \param SensorValue The sensor Value \note to set
  /// the combination method \c setInputReliabilityCombinator()
  auto getInputReliability(SensorValueType SensorValue) {
    auto inputReliability =
        getReliability(SensorValue, previousSensorValue, valueSetCounter);
    previousSensorValue = SensorValue;
    PreviousSensorValueExists = true;
    return inputReliability;
  }

  /// Calculates the Reliability
  /// \param SensorValue The current Values of the Sensor
  ///
  /// \return Reliability and Score of the current SensorValue
  ///
  ConfOrRel mostLikelySoreAndReliability(SensorValueType SensorValue) {
#if Reliability_trace_level <= trace_outputs
    LOG_TRACE_STREAM << "\nTrace level is set to: " << Reliability_trace_level
                     << "\n"
                     << "Will trace: "
                     << ((Reliability_trace_level == trace_outputs)
                             ? "outputs"
                             : (Reliability_trace_level == trace_vectors)
                                   ? "vectors"
                                   : (Reliability_trace_level ==
                                      trace_everything)
                                         ? "everything"
                                         : "undefined")
                     << trace_end;
#endif

    std::vector<ConfOrRel> ActuallPossibleScores;
    std::vector<ConfOrRel> possibleScores;
    ReliabilityType inputReliability = getInputReliability(SensorValue);

#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\ninput Rel: " << inputReliability << trace_end;
#endif

    possibleScores << Confidence->operator()(SensorValue);

    possibleScores =
        PossibleScoreCombinationMethod(possibleScores, inputReliability);
    possibleScores = FeedbackCombinatorMethod(possibleScores, ValuesFromMaster);

    saveInHistory(possibleScores);
#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\nActuallPossibleScores:\n"
                     << possibleScores << trace_end;
    LOG_TRACE_STREAM << "\npossibleScores:\n" << possibleScores << trace_end;
#endif
    possibleScores.clear();

    possibleScores = getAllPossibleScoresBasedOnHistory();

    std::sort(possibleScores.begin(), possibleScores.end(),
              [](ConfOrRel A, ConfOrRel B) -> bool {
                return A.Reliability > B.Reliability;
              });

#if Reliability_trace_level <= trace_outputs
    LOG_TRACE_STREAM << "\noutput lowlevel: " << possibleScores.at(0)
                     << trace_end;
#endif
    return possibleScores.at(0);
  }

  /// Calculates the possible Scores
  /// \param SensorValue the Sensor Value
  /// \brief it combines the input reliability and the confidence of the Sensor.
  /// The use combination method can be set using \c
  /// setPossibleScoreCombinationMethod()
  auto getPossibleScores(SensorValueType SensorValue) {
    std::vector<ConfOrRel> possibleScores;
    ReliabilityType inputReliability = getInputReliability(SensorValue);

    // get input rel()
#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\ninput Rel: " << inputReliability << trace_end;
#endif

    possibleScores << Confidence->operator()(SensorValue);
    possibleScores =
        PossibleScoreCombinationMethod(possibleScores, inputReliability);
    return possibleScores;
  }

  /// return the Possible Values with the feedback in mind
  /// \param SensorValue The sensor Value
  /// \brief it combines the input reliability and the confidence of the Sensor.
  /// The combines them with FeedbackCombinatorMethod and returns the result.
  auto getpossibleScoresWithMasterFeedback(SensorValueType SensorValue) {
    std::vector<ConfOrRel> possibleScores;
    ReliabilityType inputReliability = getInputReliability(SensorValue);

    // get input rel()
#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\ninput Rel: " << inputReliability << trace_end;
#endif

    possibleScores << Confidence->operator()(SensorValue);

    possibleScores =
        PossibleScoreCombinationMethod(possibleScores, inputReliability);

    // set comb method with values from master ( class :: min | max | average |
    // mult | [] ()-> {}  )
    possibleScores = FeedbackCombinatorMethod(possibleScores, ValuesFromMaster);
    return possibleScores;
  }

  /// returns all possible Scores and Reliabilities with the History in mind
  /// \param SensorValue the Sensor value
  /// how this is done is described at the class.
  auto getPossibleScoresWithHistory(SensorValueType SensorValue) {
    std::vector<ConfOrRel> ActuallPossibleScores;
    std::vector<ConfOrRel> possibleScores;
    ReliabilityType inputReliability = getInputReliability(SensorValue);

    // get input rel()
#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\ninput Rel: " << inputReliability << trace_end;
#endif

    possibleScores << Confidence->operator()(SensorValue);

    possibleScores =
        PossibleScoreCombinationMethod(possibleScores, inputReliability);
    possibleScores = FeedbackCombinatorMethod(possibleScores, ValuesFromMaster);

    saveInHistory(possibleScores);
#if Reliability_trace_level <= trace_vectors
    LOG_TRACE_STREAM << "\nActuallPossibleScores:\n"
                     << possibleScores << trace_end;
    LOG_TRACE_STREAM << "\npossibleScores:\n" << possibleScores << trace_end;
#endif
    possibleScores.clear();

    return getAllPossibleScoresBasedOnHistory();
  }

  /// feedback for this functionality most commonly it comes from a Master Agent
  /// \param ValuesFromMaster The Scores + Reliability for the feedback
  /// \brief This input kind of resembles a confidence but not
  /// directly it more or less says: compared to the other Scores inside the
  /// System these are the Scores with the Reliability that you have.
  void feedback(std::vector<ConfOrRel> ValuesFromMaster) {
    this->ValuesFromMaster = ValuesFromMaster;
  }

  //
  // ----------------------Reliability and Confidence Function setters----------
  //
  /// This is the setter for Confidence Function
  /// \param Confidence A pointer to the Functional for the \c Confidence of the
  /// Sensor value
  void setConfidenceFunction(
      std::unique_ptr<RangeConfidence<ReliabilityType, StateType,
                                      SensorValueType>> &Confidence) {
    this->Confidence = std::move(Confidence);
  }

  /// This is the setter for Reliability Function
  /// \param Reliability A pointer to the Functional for the Reliability
  /// \brief The Reliability takes the current Sensor value and return the
  /// Reliability of the value.
  void setReliabilityFunction(
      std::unique_ptr<Abstraction<SensorValueType, ReliabilityType>>
          &Reliability) {
    this->Reliability = std::move(Reliability);
  }

  /// This is the setter for ReliabilitySlope Function
  /// \param ReliabilitySlope A pointer to the Functional for the
  /// ReliabilitySlope
  /// \brief The ReliabilitySlope takes the difference of the current Sensor
  /// Value to the last one and tells you how likely the change is.
  void setReliabilitySlopeFunction(
      std::unique_ptr<Abstraction<SensorValueType, ReliabilityType>>
          &ReliabilitySlope) {
    this->ReliabilitySlope = std::move(ReliabilitySlope);
  }

  /// This is the setter for TimeConfidence Function
  /// \param TimeConfidence A pointer to the Functional for the TimeConfidence
  /// \brief The time function takes the position in the History with greater
  /// equals older and return a Reliability of how "relevant" it is.
  void setTimeConfidenceFunction(
      std::unique_ptr<Abstraction<std::size_t, ReliabilityType>>
          &TimeConfidence) {
    this->TimeConfidence = std::move(TimeConfidence);
  }

  /// This is the setter for all possible States
  /// \param states A vector containing all states
  /// \brief This exists even though \c State Type is an arithmetic Type because
  /// the states do not need to be "next" to each other ( ex. states={ 1 7 24 })
  void setStates(std::vector<StateType> states) { this->States = states; }

  /// This sets the Maximum length of the History
  /// \param length The length
  void setHistoryLength(std::size_t length) { this->HistoryMaxSize = length; }

  /// This sets the Value set Counter
  /// \param ValueSetCounter the new Value
  /// \note This might actually be only an artifact. It is only used to get the
  /// reliability from the \c ReliabilitySlope [ ReliabilitySlope->operator()(
  /// (lastValue - actualValue) / (SensorValueType)valueSetCounter) ]
  void setValueSetCounter(unsigned int ValueSetCounter) {
    this->valueSetCounter = ValueSetCounter;
  }

  //
  // ----------------combinator setters-----------------------------------------
  //

  /// This sets the combination method used by the History
  /// \param Meth the method which should be used. predefined \c
  /// HistoryCombinatorMethodMin() \c HistoryCombinatorMethodMax() \c
  /// HistoryCombinatorMethodMult() \c HistoryCombinatorMethodAverage()
  void setHistoryCombinatorMethod(ReliabilityType (*Meth)(ReliabilityType,
                                                          ReliabilityType)) {
    HistoryCombinatorMethod = Meth;
  }

  /// sets the predefined method for the combination of the possible scores and
  /// the master \param Meth the method predefined ones are
  /// \c FeedbackCombinatorMethodAverage() \c FeedbackCombinatorMethodMin() \c
  /// FeedbackCombinatorMethodMax() \c FeedbackCombinatorMethodMult()
  void setFeedbackCombinatorMethod(std::vector<ConfOrRel> (*Meth)(
      std::vector<ConfOrRel>, std::vector<ConfOrRel>)) {
    FeedbackCombinatorMethod = Meth;
  }

  /// Sets the used combination method for Possible Scores
  /// \param Meth a Pointer for the used Method. Predefined methods \c
  /// PossibleScoreCombinationMethodMin() \c PossibleScoreCombinationMethodMax()
  /// \c PossibleScoreCombinationMethodAverage()
  void setPossibleScoreCombinationMethod(
      std::vector<ConfOrRel> (*Meth)(std::vector<ConfOrRel>, ReliabilityType)) {
    PossibleScoreCombinationMethod = Meth;
  }

  /// sets the input reliability combinator method
  /// \param method the to be used method
  /// \note there are predefined methods \c combinationMin() \c combinationMax()
  /// \c combinationAverage()
  void setInputReliabilityCombinator(
      ReliabilityType (*method)(ReliabilityType, ReliabilityType)) {
    InputReliabilityCombinator = method;
  }

  //
  // ----------------predefined combinators------------------------------------
  //
  /// predefined Method
  static ReliabilityType HistoryCombinatorMethodMin(ReliabilityType A,
                                                    ReliabilityType B) {
    return std::min(A, B);
  }
  /// predefined Method
  static ReliabilityType HistoryCombinatorMethodMax(ReliabilityType A,
                                                    ReliabilityType B) {
    return std::max(A, B);
  }
  /// predefined Method
  static ReliabilityType HistoryCombinatorMethodMult(ReliabilityType A,
                                                     ReliabilityType B) {
    return A * B;
  }
  /// predefined Method
  static ReliabilityType HistoryCombinatorMethodAverage(ReliabilityType A,
                                                        ReliabilityType B) {
    return (A + B) / 2;
  }

  /// predefined method
  static std::vector<ConfOrRel>
  FeedbackCombinatorMethodAverage(std::vector<ConfOrRel> A,
                                  std::vector<ConfOrRel> B) {
    return average(A, B);
  }
  /// predefined method
  static std::vector<ConfOrRel>
  FeedbackCombinatorMethodMin(std::vector<ConfOrRel> A,
                              std::vector<ConfOrRel> B) {
    return min(A, B);
  }
  /// predefined method
  static std::vector<ConfOrRel>
  FeedbackCombinatorMethodMax(std::vector<ConfOrRel> A,
                              std::vector<ConfOrRel> B) {
    return max(A, B);
  }
  /// predefined method
  static std::vector<ConfOrRel>
  FeedbackCombinatorMethodMult(std::vector<ConfOrRel> A,
                               std::vector<ConfOrRel> B) {
    return mult(A, B);
  }

  /// Predefined combination method for possible Scores
  static std::vector<ConfOrRel>
  PossibleScoreCombinationMethodMin(std::vector<ConfOrRel> A,
                                    ReliabilityType B) {
    return min(A, B);
  }
  /// Predefined combination method for possible Scores
  static std::vector<ConfOrRel>
  PossibleScoreCombinationMethodMax(std::vector<ConfOrRel> A,
                                    ReliabilityType B) {
    return max(A, B);
  }

  /// Predefined combination method for possible Scores
  static std::vector<ConfOrRel>
  PossibleScoreCombinationMethodAverage(std::vector<ConfOrRel> A,
                                        ReliabilityType B) {
    return average(A, B);
  }

  /// Predefined combination method for possible Scores
  static std::vector<ConfOrRel>
  PossibleScoreCombinationMethodMult(std::vector<ConfOrRel> A,
                                     ReliabilityType B) {
    return mult(A, B);
  }

  /// The predefined min combinator method
  static ReliabilityType combinationMin(ReliabilityType A, ReliabilityType B) {
    return std::min(A, B);
  }

  /// The predefined max combinator method
  static ReliabilityType combinationMax(ReliabilityType A, ReliabilityType B) {
    return std::max(A, B);
  }

  /// The predefined average combinator method
  static ReliabilityType combinationAverage(ReliabilityType A,
                                            ReliabilityType B) {
    return (A + B) / 2;
  }

  /// The predefined average combinator method
  static ReliabilityType combinationMult(ReliabilityType A, ReliabilityType B) {
    return A * B;
  }

private:
  std::vector<std::vector<ConfOrRel>> History;
  std::size_t HistoryMaxSize;
  std::vector<ConfOrRel> ValuesFromMaster;

  SensorValueType previousSensorValue;
  unsigned int valueSetCounter;
  std::vector<StateType> States;
  bool PreviousSensorValueExists = false;

  std::unique_ptr<RangeConfidence<ReliabilityType, StateType, SensorValueType>>
      Confidence;
  std::unique_ptr<Abstraction<SensorValueType, ReliabilityType>> Reliability;
  std::unique_ptr<Abstraction<SensorValueType, ReliabilityType>>
      ReliabilitySlope;
  std::unique_ptr<Abstraction<std::size_t, ReliabilityType>> TimeConfidence;

  // combination functions
  ReliabilityType (*InputReliabilityCombinator)(
      ReliabilityType, ReliabilityType) = combinationMin;

  std::vector<ConfOrRel> (*PossibleScoreCombinationMethod)(
      std::vector<ConfOrRel>,
      ReliabilityType) = PossibleScoreCombinationMethodMin;

  std::vector<ConfOrRel> (*FeedbackCombinatorMethod)(std::vector<ConfOrRel>,
                                                     std::vector<ConfOrRel>) =
      FeedbackCombinatorMethodAverage;

  ReliabilityType (*HistoryCombinatorMethod)(ReliabilityType, ReliabilityType) =
      HistoryCombinatorMethodMax;

  /*--------------------------------- needed Functions
   * -----------------------------------------------------*/

  /// returns the Reliability
  /// \param actualValue The Value of the Sensor
  /// \param lastValue of the Sensor this is stored in the class
  /// \param valueSetCounter It has an effect on the difference of the current
  /// and last value This might not be needed anymore
  /// \brief it returns the combination the \c Reliability function and \c
  /// ReliabilitySlope if the previous value exists. if it doesn't it only
  /// returns the \c Reliability function value.
  ReliabilityType getReliability(SensorValueType actualValue,
                                 SensorValueType lastValue,
                                 unsigned int valueSetCounter) {
    ReliabilityType relAbs = Reliability->operator()(actualValue);
    if (PreviousSensorValueExists) {
      ReliabilityType relSlo = ReliabilitySlope->operator()(
          (lastValue - actualValue) / (SensorValueType)valueSetCounter);

      return InputReliabilityCombinator(relAbs, relSlo);
    } else
      return relAbs;
  }

  /// adapts the possible Scores by checking the History and combines those
  /// values.  currently with max
  /// \brief combines the historic values with the \c TimeConfidence function
  /// and returns the maximum Reliability for all Scores.
  std::vector<ConfOrRel> getAllPossibleScoresBasedOnHistory() {
    // iterate through all history entries
    std::size_t posInHistory = 0;
    std::vector<ConfOrRel> possibleScores;
    for (auto pShE = History.begin(); pShE < History.end();
         pShE++, posInHistory++) {

      // iterate through all possible scores of each history entry
      for (ConfOrRel &pSh : *pShE) {

        StateType historyScore = pSh.score;
        ReliabilityType historyConf = pSh.Reliability;

        historyConf = historyConf * TimeConfidence->operator()(posInHistory);

        bool foundScore = false;
        for (ConfOrRel &pS : possibleScores) {

          if (pS.score == historyScore) {

            pS.Reliability =
                HistoryCombinatorMethod(pS.Reliability, historyConf);

            foundScore = true;
          }
        }

        if (foundScore == false) {

          ConfOrRel possibleScore;
          possibleScore.score = historyScore;
          possibleScore.Reliability = historyConf;

          possibleScores.push_back(possibleScore);
        }
      }
    }

    return possibleScores;
  }

  /// saves the Scores in the History
  /// \brief It checks the incoming scores if any have a Reliability greater
  /// than 0.5 all of them get saved inside the History and then the
  /// History get shortened to the maximal length. It only saves the Value if
  /// the History is empty.
  ///
  /// \param actualPossibleScores The Scores which should be saved
  ///
  /// \note Does the History really make sense if the values are to small it
  /// only stores something if it's empty and not if it isn't completely filled
  void saveInHistory(std::vector<ConfOrRel> actualPossibleScores) {

    // check if the reliability of at least one possible score is high enough
    bool atLeastOneRelIsHigh = false;
    for (ConfOrRel pS : actualPossibleScores) {
      if (pS.Reliability > 0.5) {
        atLeastOneRelIsHigh = true;
      }
    }
    // save possible scores if at least one possible score is high enough (or if
    // the history is empty)
    if (History.size() < 1 || atLeastOneRelIsHigh == true) {
      History.insert(History.begin(), actualPossibleScores);

      // if history size is higher than allowed, save oldest element
      while (History.size() > HistoryMaxSize) {
        // delete possibleScoreHistory.back();
        History.pop_back();
      }
    }
  }
};



/// This is the Reliability Functionality for the highlevel Agent.
/// \brief It takes the scores and reliabilities of all connected lowlevel
/// Agents and calculates the Reliability of them together. Also it creates the
/// feedback that is needed by the \c ReliabilityForLowLevelAgents, which is a
/// kind of confidence.
///
/// \tparam StateType Datatype of the State ( Typically double	or float)
/// \tparam ReliabilityType	Datatype of the Reliability		(
/// Typically	long	or int)
///
/// \note A highlevel Agent is commonly in a master slave relationship with the
/// lowlevel Agents as the master. It combines the Reliability of all connected
/// Slaves and uses that as its own Reliability.
///
/// \note more information about how the Reliability and feedback is
/// created at \c operator()()
// State Type rename
// merge cross rel/conf darein
template <typename StateType, typename ReliabilityType> class CrossCombinator {
public:
  static_assert(std::is_arithmetic<StateType>::value,
                "HighLevel: StateType has to be an arithmetic type\n");
  static_assert(std::is_arithmetic<ReliabilityType>::value,
                "HighLevel: ReliabilityType has to be an arithmetic type\n");

  /// typedef To shorten the writing.
  /// \c ConfOrRel
  typedef ConfOrRel<StateType, ReliabilityType> ConfOrRel;

  /// typedef of the input type for the operator() defined explicitly to
  /// simplify interaction
  ///
  typedef std::vector<std::tuple<id_t, StateType, ReliabilityType>> InputType;

  /// The return type for the \c operator()() Method
  struct returnType {
    ReliabilityType CrossReliability;
    std::map<id_t, std::vector<ConfOrRel>> CrossConfidence;
  };

  /// Calculates the Reliability and the Cross Confidences for each lowlevel
  /// Agent for all of there states.
  ///
  /// \param Values It gets the States and Reliabilities of
  /// all connected Slaves inside a vector.
  ///
  /// \return it returns a struct \c returnType containing the CrossReliability
  /// and all CrossConfidence's
  ///
  /// \brief To calculate the Reliability it combines [\c std::min() ] the \c
  /// CrossReliability of all connected Agents. To calculate the feedback it
  /// iterates over all Agents and their states and uses the \c CrossConfidence
  /// Function to play what if with the states.
  returnType operator()(
      std::vector<std::tuple<id_t, StateType, ReliabilityType>> &Values) {

    ReliabilityType combinedInputRel = 1;
    ReliabilityType combinedCrossRel = 1;
    ReliabilityType outputReliability;

    std::vector<std::pair<id_t, StateType>> Agents;
    std::map<id_t, std::vector<ConfOrRel>> output;
    std::vector<ConfOrRel> output_temporary;

    for (auto tmp : Values) {
      std::pair<id_t, StateType> tmp2;
      tmp2.first = std::get<0>(tmp);
      tmp2.second = std::get<1>(tmp);
      Agents.push_back(tmp2);
    }

    for (auto Value : Values) {
      id_t id = std::get<0>(Value);
      StateType sc = std::get<1>(Value);
      ReliabilityType rel = std::get<2>(Value);

      // combination method ([])
      // get input reliability
      combinedInputRel = std::min(combinedInputRel, rel);

      // calculate the cross reliability for this slave agent
      ReliabilityType realCrossReliabilityOfSlaveAgent =
          CrossReliability->operator()(
              {id, sc},
              Agents); // AVERAGE, MULTIPLICATION, CONJUNCTION (best to worst:
                       // AVERAGE = CONJUNCTION > MULTIPLICATION >> )

      // get cross confidence
      output_temporary.clear();
      for (StateType thoScore : States[id]) {
        // calculate the cross reliability for this slave agent
        ConfOrRel data;
        data.score = thoScore;
        data.Reliability = CrossConfidence->operator()(id, thoScore, Agents);
        output_temporary.push_back(data);
      }

      output.insert({id, output_temporary});
      // set combination method
      // get combined cross reliability
      combinedCrossRel =
          std::min(combinedCrossRel, realCrossReliabilityOfSlaveAgent);
    }

    // combine cross reliabilites and input reliabilites of all slave agents
    // NOTE: options would be multiply, average, AND (best to worst: )
    // outputReliability = combinedInputRel * combinedCrossRel;
    // outputReliability = (combinedInputRel + combinedCrossRel) / 2;
    // set combination method
    // get output reliability
    outputReliability = std::min(combinedInputRel, combinedCrossRel);
    return {outputReliability, output};
  }

  /// This is the setter for CrossReliability Function
  /// \param CrossReliability A pointer to the Functional for the
  /// CrossReliability
  /// \brief This is needed to calculate the Reliability. It uses this on all
  /// values of all lowlevel Agnets.
  void setCrossReliability(
      std::unique_ptr<CrossReliability<StateType, ReliabilityType>>
          &CrossReliability) {
    this->CrossReliability = std::move(CrossReliability);
  }

  /// This is the setter for CrossConfidence Function
  /// \param CrossConfidence A pointer to the Functional for the \c
  /// CrossConfidence \brief This is needed for the feedback for the \c
  /// ReliabilityForLowLevelAgents.
  void setCrossConfidence(
      std::unique_ptr<CrossConfidence<StateType, ReliabilityType>>
          &CrossConfidence) {
    this->CrossConfidence = std::move(CrossConfidence);
  }

  /// This is the adder for the states
  /// \param id The id of the Agent of the states
  /// \param States id specific states. this will be copied So that if Slaves
  /// have different States they can be used correctly.
  /// \brief The States of all connected lowlevel Agents has to be known to be
  /// able to iterate over them
  void addStates(id_t id, std::vector<StateType> States) {
    this->States.insert({id, States});
  }

private:
  std::unique_ptr<CrossReliability<StateType, ReliabilityType>>
      CrossReliability;
  std::unique_ptr<CrossConfidence<StateType, ReliabilityType>> CrossConfidence;

  std::map<id_t, std::vector<StateType>> States;
};

} // namespace agent
} // namespace rosa
#endif // !ROSA_AGENT_RELIABILITY_H
