//===-- rosa/agent/Reliability.h --------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/Reliability.h
///
/// \author Daniel Schnoell (danielschnoell@tuwien.ac.at)
///
/// \date 2019
///
/// \brief  Definition of *reliability* *functionality*.
///
/// \note All classes throw runtime errors if not all things are set
///
//===----------------------------------------------------------------------===//

#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 Reliability more
/// readable \tparam StateType The datatype of the States \tparam
/// ReliabilityType The datatype of the Reliability
template <typename StateType, typename ReliabilityType> struct ConfOrRel {
  typedef StateType _StateType;
  typedef ReliabilityType _ReliabilityType;

  StateType score;
  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;
  }

  typedef std::map<StateType, ReliabilityType>
      map; // needed or it throws an clang diagnosic erroor
  /// 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 adds the Reliabilities of the same Scores
  /// \param me The vector to wich is written to
  /// \param other The other data vector
  friend std::vector<ConfOrRel> operator+=(std::vector<ConfOrRel> &me,
                                           std::vector<ConfOrRel> other) {
    static_assert(std::is_arithmetic<ReliabilityType>::value);
    for (auto tmp_me : me)
      for (auto tmp_other : other) {
        if (tmp_me.score == tmp_other.score) {
          tmp_me.Reliability = tmp_me.Reliability + tmp_other.Reliability;
        }
      }
    return me;
  }

  /// This is to push the data inside a vector in a humanreadable 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 Reliabilites
/// \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 is the Reliability Functionality for a low level Agent
/// \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)
///
/// use the () operator to get the reliability and feed the information from the
/// master back to this \note all pointer for the functionalities will be
/// deleted when this is object ist destroyed
template <typename SensorValueType, typename StateType,
          typename ReliabilityType>
class LowLevel {
public:
  typedef ConfOrRel<StateType, ReliabilityType> ConfOrRel;

  /// Calculates the Conf/ Reliability
  /// \param SensorValue The current Values of the Sensor
  ///
  /// \return Reliability of the current Value
  ConfOrRel operator()(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 =
        getRelibility(SensorValue, previousSensorValue, valueSetCounter);

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

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

    possibleScores = min(possibleScores, inputReliability);

    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 {
                static_assert(std::is_arithmetic<ReliabilityType>::value);
                return A.Reliability > B.Reliability;
              });

    previousSensorValue = SensorValue;
    PreviousSensorValueExists = true;

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

  /// Needed feedback from the Master
  /// \param ValuesFromMaster The Scores + Reliability from the Master for this
  /// Agent
  void feedback(std::vector<ConfOrRel> ValuesFromMaster) {
    this->ValuesFromMaster = ValuesFromMaster;
  }

  /// This is the setter for Confidence Function
  /// \param Confidence A pointer to the Functional for the Confidence
  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
  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
  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
  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 vertor for all states
  void setStates(std::vector<StateType> states) { this->States = states; }

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

  /// This sets the Value set Counter
  /// \param ValueSetCounter the new Value
  void setValueSetCounter(unsigned int ValueSetCounter) {
    this->valueSetCounter = ValueSetCounter;
  }

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;

  /*--------------------------------- needed Funktions
   * -----------------------------------------------------*/

  /// 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
  ReliabilityType getRelibility(SensorValueType actualValue,
                                SensorValueType lastValue,
                                unsigned int valueSetCounter) {
    static_assert(std::is_arithmetic<ReliabilityType>::value);
    ReliabilityType relAbs = Reliability->operator()(actualValue);
    if (PreviousSensorValueExists) {
      ReliabilityType relSlo = ReliabilitySlope->operator()(
          (lastValue - actualValue) / (SensorValueType)valueSetCounter);

      // calculate signal input reliability
      // NOTE: options would be multiply, average, AND (best to worst:
      // average = AND > multiply) rel = relAbs * relSlo; rel = (relAbs +
      // relSlo)/2;

      return std::min(relAbs, relSlo);
    } else
      return relAbs;
  }

  /// adabts the possible Scores by checking the History and combines those
  /// values currently with max
  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;

        // combine each history score with the confidence of time
        // NOTE: multiplication, AND, or average would be alternatives (best to
        // worst: multiplication = AND = average)
        historyConf = historyConf * TimeConfidence->operator()(posInHistory);
        // historyConf = (historyConf + TimeConfidence(posInHistory)) / 2;
        // historyConf = std::min(historyConf, TimeConfidence(posInHistory));

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

          if (pS.score == historyScore) {

            // calculate confidence for score
            // NOTE: multiplication, AND, or average would be alternatives (best
            // to worst: AND >> average = multiplication ) pS->confOrRel =
            // pS->confOrRel * historyConf; pS->confOrRel = (pS->confOrRel +
            // historyConf) / 2;
            pS.Reliability = std::max(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
  /// \param actualPossibleScores The Scores which should be saved
  ///
  /// \note Does the History realy make sence if the values are to smal it only
  /// stores something if its empty and not if it isn't completly 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, savo oldest element
      while (History.size() > HistoryMaxSize) {
        // delete possibleScoreHistory.back();
        History.pop_back();
      }
    }
  }
};

/// This is the Reliability Functionality for the Highlevel Agent
/// \tparam StateType		Datatype of the State			( Typically
/// double	or float) \tparam ReliabilityType	Datatype of the
/// Reliability		( Typically	long	or int)
///
/// use the () operator to calculate the Reliability and all cross confidences
/// for all slaves \note all pointer to Funcionalities get deleted upon deletion
/// of the object
template <typename StateType, typename ReliabilityType> class HighLevel {
public:
  typedef ConfOrRel<StateType, ReliabilityType> ConfOrRel;

  struct returnType {
    ReliabilityType CrossReliability;
    std::map<id_t, std::vector<ConfOrRel>> CrossConfidence;
  };

  returnType operator()(
      std::vector<std::tuple<id_t, StateType, ReliabilityType>> &Values) {

    StateType EWS = 0;
    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);

      EWS = EWS + sc;

      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 >> )

      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({std::get<0>(Value), output_temporary});
      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;
    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
  void setFunction(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 CrossConfidence
  void setFunction(std::unique_ptr<CrossConfidence<StateType, ReliabilityType>>
                       &CrossConfidence) {
    this->CrossConfidence = std::move(CrossConfidence);
  }

  /// This the adder for the states
  /// \param States id spezific states this will be copied
  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
