//===-- rosa/agent/SignalState.hpp ------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/SignalState.hpp
///
/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *signal state* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_SIGNALSTATE_HPP
#define ROSA_AGENT_SIGNALSTATE_HPP

#include "rosa/agent/FunctionAbstractions.hpp"
#include "rosa/agent/Functionality.h"
#include "rosa/agent/History.hpp"
#include "rosa/agent/State.hpp"

#include "rosa/support/math.hpp"

namespace rosa {
namespace agent {

/// Signal properties defining the properties of the signal which is monitored
/// by \c rosa::agent::SignalStateDetector and is saved in \c
/// rosa::agent::SignalStateInformation.
enum SignalProperties : uint8_t {
  INPUT = 0, ///< The signal is an input signal
  OUTPUT = 1 ///< The signal is an output signal
};

/// TODO: write description
template <typename CONFDATATYPE>
struct SignalStateInformation : StateInformation<CONFDATATYPE> {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<CONFDATATYPE>::value),
                "confidence type is not to arithmetic");

  /// TODO: describe
  CONFDATATYPE ConfidenceOfMatchingState;
  CONFDATATYPE ConfidenceOfMismatchingState;

  /// The SignalProperty saves whether the monitored signal is an input our
  /// output signal.
  SignalProperties SignalProperty;

  /// The SignalStateIsValid saves the number of samples which have been
  /// inserted into the state after entering it.
  uint32_t NumberOfInsertedSamplesAfterEntrance;

public:
  SignalStateInformation(unsigned int SignalStateID,
                         SignalProperties _SignalProperty) {
    this->StateID = SignalStateID;
    this->SignalProperty = _SignalProperty;
    this->StateCondition = StateConditions::UNKNOWN;
    this->StateIsValid = false;
    this->StateJustGotValid = false;
    this->StateIsValidAfterReentrance = false;
    this->ConfidenceStateIsValid = 0;
    this->ConfidenceStateIsInvalid = 0;
    this->ConfidenceStateIsStable = 0;
    this->ConfidenceStateIsDrifting = 0;
  }

  SignalStateInformation() {}
};

/// \tparam INDATATYPE type of input data, \tparam CONFDATATYPE type of
/// data in that the confidence values are given, \tparam PROCDATATYPE type of
/// the relative distance and the type of data in which DABs are saved.
template <typename INDATATYPE, typename CONFDATATYPE, typename PROCDATATYPE>
class SignalState : public Functionality {

  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<INDATATYPE>::value),
                "input data type not arithmetic");
  STATIC_ASSERT((std::is_arithmetic<CONFDATATYPE>::value),
                "confidence data type is not to arithmetic");
  STATIC_ASSERT(
      (std::is_arithmetic<PROCDATATYPE>::value),
      "process data type (DAB and Relative Distance) is not to arithmetic");

public:
  // For the convinience to write a shorter data type name
  using PartFuncReference = PartialFunction<INDATATYPE, CONFDATATYPE> &;
  using StepFuncReference = StepFunction<INDATATYPE, CONFDATATYPE> &;

private:
  /// SignalStateInfo is a struct of SignalStateInformation that contains
  /// information about the current signal state.
  SignalStateInformation<CONFDATATYPE> SignalStateInfo;

  /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the
  /// confidence how good the new sample matches another sample in the sample
  /// history.
  PartFuncReference FuzzyFunctionSampleMatches;

  /// The FuzzyFunctionSampleMismatches is the fuzzy function that gives the
  /// confidence how bad the new sample matches another sample in the sample
  /// history.
  PartFuncReference FuzzyFunctionSampleMismatches;

  /// The FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history match the new sample.
  StepFuncReference FuzzyFunctionNumOfSamplesMatches;

  /// The FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives
  /// the confidence how many samples from the sampe history mismatch the new
  /// sample.
  StepFuncReference FuzzyFunctionNumOfSamplesMismatches;

  /// The FuzzyFunctionSignalIsDrifting is the fuzzy function that gives the
  /// confidence how likely it is that the signal (resp. the state of a signal)
  /// is drifting.
  PartFuncReference FuzzyFunctionSignalIsDrifting;

  /// The FuzzyFunctionSignalIsStable is the fuzzy function that gives the
  /// confidence how likely it is that the signal (resp. the state of a signal)
  /// is stable (not drifting).
  PartFuncReference FuzzyFunctionSignalIsStable;

  /// SampleHistory is a history in that the last sample values are stored.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::FIFO> SampleHistory;
  /// DAB is a (usually) small history of the last sample values of which a
  /// average is calculated if the DAB is full.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::SRWF> DAB;
  /// DABHistory is a history in that the last DABs (to be exact, the averages
  /// of the last DABs) are stored.
  DynamicLengthHistory<PROCDATATYPE, HistoryPolicy::LIFO> DABHistory;

  /// LowestConfidenceMatchingHistory is a history in that the lowest confidence
  /// for the current sample matches all history samples are saved.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::FIFO>
      LowestConfidenceMatchingHistory;
  /// HighestConfidenceMatchingHistory is a history in that the highest
  /// confidence for the current sample matches all history samples are saved.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::FIFO>
      HighestConfidenceMismatchingHistory;

  //@benedikt: neu (passt das so?)
  CONFDATATYPE TempConfidenceMatching = 0;
  CONFDATATYPE TempConfidenceMismatching = 0;

public:
  /// Creates an instance by setting all parameters
  /// \param SignalStateID The Id of the SignalStateinfo \c
  /// SignalStateInformation.
  ///
  /// \param FuzzyFunctionSampleMatches The FuzzyFunctionSampleMatches is the
  /// fuzzy function that gives the confidence how good the new sample matches
  /// another sample in the sample history.
  ///
  /// \param FuzzyFunctionSampleMismatches The FuzzyFunctionSampleMismatches is
  /// the fuzzy function that gives the confidence how bad the new sample
  /// matches another sample in the sample history.
  ///
  /// \param FuzzyFunctionNumOfSamplesMatches The
  /// FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history match the new sample.
  ///
  /// \param FuzzyFunctionNumOfSamplesMismatches The
  /// FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history mismatch the new
  /// sample.
  ///
  /// \param FuzzyFunctionSignalIsDrifting The FuzzyFunctionSignalIsDrifting is
  /// the fuzzy function that gives the confidence how likely it is that the
  /// signal (resp. the state of a signal) is drifting.
  ///
  /// \param FuzzyFunctionSignalIsStable The FuzzyFunctionSignalIsStable is the
  /// fuzzy function that gives the confidence how likely it is that the signal
  /// (resp. the state of a signal) is stable (not drifting).
  ///
  /// \param SampleHistorySize Size of the Sample History \c
  /// DynamicLengthHistory . SampleHistory is a history in that the last sample
  /// values are stored.
  ///
  /// \param DABSize Size of DAB \c DynamicLengthHistory . DAB is a (usually)
  /// small history of the last sample values of which a average is calculated
  /// if the DAB is full.
  ///
  /// \param DABHistorySize Size of the DABHistory \c DynamicLengthHistory .
  /// DABHistory is a history in that the last DABs (to be exact, the averages
  /// of the last DABs) are stored.
  ///
  SignalState(uint32_t SignalStateID, SignalProperties SignalProperty,
              uint32_t SampleHistorySize, uint32_t DABSize,
              uint32_t DABHistorySize,
              PartFuncReference FuzzyFunctionSampleMatches,
              PartFuncReference FuzzyFunctionSampleMismatches,
              StepFuncReference FuzzyFunctionNumOfSamplesMatches,
              StepFuncReference FuzzyFunctionNumOfSamplesMismatches,
              PartFuncReference FuzzyFunctionSignalIsDrifting,
              PartFuncReference FuzzyFunctionSignalIsStable) noexcept
      : //@benedikt/@david: I don't know if i am allowed to initialize it like
        // that because the struct is a derivate of another struct
        SignalStateInfo{SignalStateID, SignalProperty},
        FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches),
        FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches),
        FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches),
        FuzzyFunctionNumOfSamplesMismatches(
            FuzzyFunctionNumOfSamplesMismatches),
        FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting),
        FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable),
        SampleHistory(SampleHistorySize), DAB(DABSize),
        DABHistory(DABHistorySize),
        LowestConfidenceMatchingHistory(SampleHistorySize),
        HighestConfidenceMismatchingHistory(SampleHistorySize) {}

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

  void leaveSignalState(void) noexcept {
    DAB.clear();
    SignalStateInfo.NumberOfInsertedSamplesAfterEntrance = 0;
    SignalStateInfo.StateIsValidAfterReentrance = false;
  }

  SignalStateInformation<CONFDATATYPE>
  insertSample(INDATATYPE Sample) noexcept {

    SignalStateInfo.NumberOfInsertedSamplesAfterEntrance++;

    validateSignalState(Sample);

    SampleHistory.addEntry(Sample);

    DAB.addEntry(Sample);
    if (DAB.full()) {
      PROCDATATYPE AvgOfDAB = DAB.template average<PROCDATATYPE>();
      DABHistory.addEntry(AvgOfDAB);
      DAB.clear();
    }

    FuzzyFunctionNumOfSamplesMatches.setRightLimit(
        static_cast<INDATATYPE>(SampleHistory.numberOfEntries()));
    FuzzyFunctionNumOfSamplesMismatches.setRightLimit(
        static_cast<INDATATYPE>(SampleHistory.numberOfEntries()));

    checkSignalStability();

    //@benedikt (das gehört dazu)
    SignalStateInfo.ConfidenceOfMatchingState = TempConfidenceMatching;
    SignalStateInfo.ConfidenceOfMismatchingState = TempConfidenceMismatching;

    return SignalStateInfo;
  }

  /// Gives the confidence how likely the new sample matches the signal state.
  ///
  /// \param Sample is the actual sample of the observed signal.
  ///
  /// \return the confidence of the new sample is matching the signal state.
  CONFDATATYPE
  confidenceSampleMatchesSignalState(INDATATYPE Sample) noexcept {

    CONFDATATYPE ConfidenceOfBestCase = 0;

    DynamicLengthHistory<PROCDATATYPE, HistoryPolicy::FIFO>
        RelativeDistanceHistory(SampleHistory.maxLength());

    // calculate distances to all history samples
    for (auto &HistorySample : SampleHistory) {
      PROCDATATYPE RelativeDistance =
          relativeDistance<INDATATYPE, PROCDATATYPE>(Sample, HistorySample);
      RelativeDistanceHistory.addEntry(RelativeDistance);
    }

    // sort all calculated distances so that the lowest distance (will get the
    // highest confidence) is at the beginning.
    RelativeDistanceHistory.sortAscending();

    CONFDATATYPE ConfidenceOfWorstFittingSample = 1;

    // Case 1 means that one (the best fitting) sample of the history is
    // compared with the new sample. Case 2 means the two best history samples
    // are compared with the new sample. And so on.
    // TODO (future): to accelerate . don't start with 1 start with some higher
    // number because a low number (i guess lower than 5) will definetely lead
    // to a low confidence. except the history is not full.
    //
    // Case 1 means that one (the best fitting) sample of the history is
    // compared with the new sample. Case 2 means the two best history samples
    // are compared with the new sample. And so on.
    for (uint32_t Case = 0; Case < RelativeDistanceHistory.numberOfEntries();
         Case++) {

      CONFDATATYPE ConfidenceFromRelativeDistance;

      if (std::isinf(RelativeDistanceHistory[Case])) {
        // TODO (future) if fuzzy is defined in a way that infinity is not 0 it
        // would be a problem
        ConfidenceFromRelativeDistance = 0;
      } else {
        ConfidenceFromRelativeDistance =
            FuzzyFunctionSampleMatches(RelativeDistanceHistory[Case]);
      }
      // printf("AAAAAAAAAAAAAAAA 1\n");
      ConfidenceOfWorstFittingSample = fuzzyAND(ConfidenceOfWorstFittingSample,
                                                ConfidenceFromRelativeDistance);

      // printf("AAAAAAAAAAAAAAAA 2\n");
      ConfidenceOfBestCase =
          fuzzyOR(ConfidenceOfBestCase,
                  fuzzyAND(ConfidenceOfWorstFittingSample,
                           FuzzyFunctionNumOfSamplesMatches(
                               static_cast<CONFDATATYPE>(Case) + 1)));
    }

    //@benedikt (das gehört dazu)
    TempConfidenceMatching = ConfidenceOfBestCase;

    return ConfidenceOfBestCase;
  }

  /// Gives the confidence how likely the new sample mismatches the signal
  /// state.
  ///
  /// \param Sample is the actual sample of the observed signal.
  ///
  /// \return the confidence of the new sample is mismatching the signal state.
  CONFDATATYPE
  confidenceSampleMismatchesSignalState(INDATATYPE Sample) noexcept {

    float ConfidenceOfWorstCase = 1;

    DynamicLengthHistory<PROCDATATYPE, HistoryPolicy::FIFO>
        RelativeDistanceHistory(SampleHistory.maxLength());

    // calculate distances to all history samples
    for (auto &HistorySample : SampleHistory) {
      RelativeDistanceHistory.addEntry(
          relativeDistance<INDATATYPE, PROCDATATYPE>(Sample, HistorySample));
    }

    // sort all calculated distances so that the highest distance (will get the
    // lowest confidence) is at the beginning.
    RelativeDistanceHistory.sortDescending();

    CONFDATATYPE ConfidenceOfBestFittingSample = 0;

    // TODO (future): to accelerate -> don't go until end. Confidences will only
    // get higher. See comment in "CONFDATATYPE
    // confidenceSampleMatchesSignalState(INDATATYPE Sample)".
    //
    // Case 1 means that one (the worst fitting) sample of the history is
    // compared with the new sample. Case 2 means the two worst history samples
    // are compared with the new sample. And so on.
    for (uint32_t Case = 0; Case < RelativeDistanceHistory.numberOfEntries();
         Case++) {

      CONFDATATYPE ConfidenceFromRelativeDistance;

      if (std::isinf(RelativeDistanceHistory[Case])) {
        ConfidenceFromRelativeDistance = 1;
      } else {
        ConfidenceFromRelativeDistance =
            FuzzyFunctionSampleMismatches(RelativeDistanceHistory[Case]);
      }

      ConfidenceOfBestFittingSample = fuzzyOR(ConfidenceOfBestFittingSample,
                                              ConfidenceFromRelativeDistance);

      // printf("AAAAAAAAAAAAAAAA 3\n");
      ConfidenceOfWorstCase =
          fuzzyAND(ConfidenceOfWorstCase,
                   fuzzyOR(ConfidenceOfBestFittingSample,
                           FuzzyFunctionNumOfSamplesMismatches(
                               static_cast<CONFDATATYPE>(Case) + 1)));
    }

    //@benedikt (das gehört dazu)
    TempConfidenceMismatching = ConfidenceOfWorstCase;

    return ConfidenceOfWorstCase;
  }

  /// Gives information about the current signal state.
  ///
  /// \return a struct SignalStateInformation that contains information about
  /// the current signal state.
  SignalStateInformation<CONFDATATYPE> signalStateInformation(void) noexcept {
    return SignalStateInfo;
  }

private:
  void validateSignalState(INDATATYPE Sample) {
    // TODO (future): WorstConfidenceDistance and BestConfidenceDistance could
    // be set already in "CONFDATATYPE
    // confidenceSampleMatchesSignalState(INDATATYPE Sample)" and "CONFDATATYPE
    // confidenceSampleMismatchesSignalState(INDATATYPE Sample)" when the new
    // sample is compared to all history samples. This would save a lot time
    // because the comparisons are done only once. However, it has to be asured
    // that the these two functions are called before the insertation, and the
    // FuzzyFunctions for validation and matching have to be the same!
    CONFDATATYPE LowestConfidenceMatching = 1;
    CONFDATATYPE HighestConfidenceMismatching = 0;
    for (auto &HistorySample : SampleHistory) {
      // TODO (future): think about using different fuzzy functions for
      // validation and matching.
      // printf("AAAAAAAAAAAAAAAA 4\n");
      LowestConfidenceMatching = fuzzyAND(
          LowestConfidenceMatching,
          FuzzyFunctionSampleMatches(relativeDistance<INDATATYPE, PROCDATATYPE>(
              Sample, HistorySample)));

      HighestConfidenceMismatching =
          fuzzyOR(HighestConfidenceMismatching,
                  FuzzyFunctionSampleMismatches(
                      relativeDistance<INDATATYPE, PROCDATATYPE>(
                          Sample, HistorySample)));
    }
    LowestConfidenceMatchingHistory.addEntry(LowestConfidenceMatching);
    HighestConfidenceMismatchingHistory.addEntry(HighestConfidenceMismatching);

    LowestConfidenceMatching = LowestConfidenceMatchingHistory.lowestEntry();
    HighestConfidenceMismatching =
        HighestConfidenceMismatchingHistory.highestEntry();

    // printf("AAAAAAAAAAAAAAAA 5\n");
    SignalStateInfo.ConfidenceStateIsValid =
        fuzzyAND(LowestConfidenceMatching,
                 FuzzyFunctionNumOfSamplesMatches(static_cast<INDATATYPE>(
                     SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));

    SignalStateInfo.ConfidenceStateIsInvalid =
        fuzzyOR(HighestConfidenceMismatching,
                FuzzyFunctionNumOfSamplesMismatches(static_cast<INDATATYPE>(
                    SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));

    if (SignalStateInfo.ConfidenceStateIsValid >
        SignalStateInfo.ConfidenceStateIsInvalid) {
      if (SignalStateInfo.StateIsValid) {
        SignalStateInfo.StateJustGotValid = false;
      } else {
        SignalStateInfo.StateJustGotValid = true;
      }
      SignalStateInfo.StateIsValid = true;
      SignalStateInfo.StateIsValidAfterReentrance = true;
    }
  }

  void checkSignalStability(void) {

    if (DABHistory.numberOfEntries() >= 2) {
      SignalStateInfo.ConfidenceStateIsStable = FuzzyFunctionSignalIsStable(
          relativeDistance<INDATATYPE, PROCDATATYPE>(
              DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0]));
      SignalStateInfo.ConfidenceStateIsDrifting = FuzzyFunctionSignalIsDrifting(
          relativeDistance<INDATATYPE, PROCDATATYPE>(
              DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0]));
    } else {
      // @benedikt: I do not know if this "initializing" is the best, but I
      // think it makes sense because we do not know if it is stable or
      // drifting.
      SignalStateInfo.ConfidenceStateIsStable = 0;
      SignalStateInfo.ConfidenceStateIsDrifting = 0;
    }

    //@benedikt: before it was "ConfidenceSignalIsStable >=
    // ConfidenceSignalIsDrifting" -> stable. However, I think like that it
    // makes
    // more sense. What do you mean?
    if (SignalStateInfo.ConfidenceStateIsStable >
        SignalStateInfo.ConfidenceStateIsDrifting) {
      SignalStateInfo.StateCondition = StateConditions::STABLE;
    } else if (SignalStateInfo.ConfidenceStateIsStable <
               SignalStateInfo.ConfidenceStateIsDrifting) {
      SignalStateInfo.StateCondition = StateConditions::DRIFTING;
    } else {
      SignalStateInfo.StateCondition = StateConditions::UNKNOWN;
    }
  }
};

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

#endif // ROSA_AGENT_SIGNALSTATE_HPP
