//===-- 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/support/math.hpp"

namespace rosa {
namespace agent {

/// Signal state conditions defining how the condition of a \c
/// rosa::agent::SignalState is saved in \c rosa::agent::SignalStateInformation.
enum SignalStateCondition : uint8_t {
  STABLE = 0,   ///< The signal state is stable
  DRIFTING = 1, ///< The signal state is drifting
  UNKNOWN = 2   ///< The signal state is unknown
};

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

  /// The signal state ID saved as an uint32_teger number
  uint32_t SignalStateID;
  /// The SignalStateConfidence shows the overall confidence value of the signal
  /// state.
  CONFDATATYPE SignalStateConfidence;
  /// The SignalStateCondition shows the condition of a signal state (stable,
  /// drifting, or unknown)
  SignalStateCondition SignalStateCondition;
  /// The SignalStateIsValid saves the number of samples which have been
  /// inserted into the state after entering it.
  uint32_t NumberOfInsertedSamplesAfterEntrance;
  /// The SignalStateIsValid shows whether a signal state is valid or invalid.
  /// In this context, valid means that enough samples which are in close
  /// proximitry have been inserted into the signal state.
  bool SignalStateIsValid;
  /// The SignalStateJustGotValid shows whether a signal state got valid
  /// (toggled from invalid to valid) during the current inserted sample.
  bool SignalStateJustGotValid;
  /// The SignalStateIsValidAfterReentrance shows whether a signal state is
  /// valid after the variable changed back to it again.
  bool SignalStateIsValidAfterReentrance;
  /// The SignalIsStableNotDrifting shows whether a signal is stable and not
  /// drifting.
  bool SignalIsStable;
};

/// \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");

private:
  // For the convinience to write a shorter data type name
  using PartFuncPointer =
      std::shared_ptr<PartialFunction<INDATATYPE, CONFDATATYPE>>;
  using StepFuncPointer =
      std::shared_ptr<StepFunction<INDATATYPE, CONFDATATYPE>>;

  /// SignalStateInfo is a struct SignalStateInformation that contains
  /// information about the current 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.
  PartFuncPointer FuzzyFunctionSampleMatches;

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

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

  /// The FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives
  /// the confidence how many samples from the sampe history mismatch the new
  /// sample.
  StepFuncPointer 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.
  PartFuncPointer 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).
  PartFuncPointer 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;

public:
  // @Maxi doxygen per default doesn't display private attributes of a class. So
  // I copied them to the constructor. So the user has more information.
  /// 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, uint32_t SampleHistorySize,
              uint32_t DABSize, uint32_t DABHistorySize,
              PartFuncPointer FuzzyFunctionSampleMatches,
              PartFuncPointer FuzzyFunctionSampleMismatches,
              StepFuncPointer FuzzyFunctionNumOfSamplesMatches,
              StepFuncPointer FuzzyFunctionNumOfSamplesMismatches,
              PartFuncPointer FuzzyFunctionSignalIsDrifting,
              PartFuncPointer FuzzyFunctionSignalIsStable) noexcept
      : SignalStateInfo{SignalStateID, 0,     SignalStateCondition::UNKNOWN, 0,
                        false,         false,
                        false, //@maxi added the Signal is stable bool
                        true},

        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.SignalStateIsValidAfterReentrance = false;
  }

  SignalStateInformation<CONFDATATYPE>
  insertSample(INDATATYPE Sample) noexcept {

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

    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.

    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
        //@David: because we are using these operator functions, here I have to
        // direference in a not (in my opionin) not so beautiful way
        //("(*FuzzyFunctionSampleMatches)"), or I have to write
        //"FuzzyFunctionSampleMatches->operator()(...)". Can we just write
        // functions like "->getBlabla()" or something like that?
        ConfidenceFromRelativeDistance = 0;
      } else {
        ConfidenceFromRelativeDistance =
            (*FuzzyFunctionSampleMatches)(RelativeDistanceHistory[Case]);
      }
#if false
      //@maxi why the 2?
      ConfidenceOfWorstFittingSample =
          fuzzyAND((CONFDATATYPE)2, ConfidenceOfWorstFittingSample,
                   ConfidenceFromRelativeDistance);

	  //@maxi you could also use it like this then you can define the type and the size
	   ConfidenceOfWorstFittingSample =
          fuzzyAND<CONFDATATYPE,2>({ConfidenceOfWorstFittingSample,
                   ConfidenceFromRelativeDistance});
#else
      //@maxi is this what you wanted? you don't need to define the data type
      // nor the size
      ConfidenceOfWorstFittingSample = fuzzyAND(ConfidenceOfWorstFittingSample,
                                                ConfidenceFromRelativeDistance);
#endif

      //@benedikt: same as before with "->operator()"
      //@maxi the same as before
#if false
      ConfidenceOfBestCase = fuzzyOR<CONFDATATYPE>(
          2, ConfidenceOfBestCase,
          fuzzyAND<CONFDATATYPE>(2, ConfidenceOfWorstFittingSample,
                                 FuzzyFunctionNumOfSamplesMatches->operator()(
                                     static_cast<CONFDATATYPE>(Case) + 1)));
#else
      ConfidenceOfBestCase =
          fuzzyOR(ConfidenceOfBestCase,
                  fuzzyAND(ConfidenceOfWorstFittingSample,
                           FuzzyFunctionNumOfSamplesMatches->operator()(
                               static_cast<CONFDATATYPE>(Case) + 1)));

#endif
    }

    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;

    // 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.
    // TODO (future): to accelerate -> don't go until end. Confidences will only
    // get higher. See comment in "CONFDATATYPE
    // confidenceSampleMatchesSignalState(INDATATYPE Sample)".
    for (uint32_t Case = 0; Case < RelativeDistanceHistory.numberOfEntries();
         Case++) {

      CONFDATATYPE ConfidenceFromRelativeDistance;

      if (std::isinf(RelativeDistanceHistory[Case])) {
        ConfidenceFromRelativeDistance = 1;
      } else {
        //@benedikt: I had to change the following line. The outcommented line
        // was the original one. I think it is ugly like that (new line). Do you
        // have an idea how to make it better/more beautiful?
        ConfidenceFromRelativeDistance =
            FuzzyFunctionSampleMismatches->operator()(
                RelativeDistanceHistory[Case]);
      }

//@maxi you don't have to define the data type or the amount
#if false
      ConfidenceOfBestFittingSample = fuzzyOR<CONFDATATYPE>(
          2, ConfidenceOfBestFittingSample, ConfidenceFromRelativeDistance);

      //@benedikt: same as before with "->operator()"
      ConfidenceOfWorstCase = fuzzyAND<CONFDATATYPE>(
          2, ConfidenceOfWorstCase,
          fuzzyOR<CONFDATATYPE>(2, ConfidenceOfBestFittingSample,
                                FuzzyFunctionNumOfSamplesMismatches->operator()(
                                    static_cast<CONFDATATYPE>(Case) + 1)));
#else
      ConfidenceOfBestFittingSample = fuzzyOR(ConfidenceOfBestFittingSample,
                                              ConfidenceFromRelativeDistance);

      //@benedikt: same as before with "->operator()"
      ConfidenceOfWorstCase =
          fuzzyAND(ConfidenceOfWorstCase,
                   fuzzyOR(ConfidenceOfBestFittingSample,
                           FuzzyFunctionNumOfSamplesMismatches->operator()(
                               static_cast<CONFDATATYPE>(Case) + 1)));
#endif
    }

    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.
      //@benedikt: same with "->operator()"
      //@maxi you don't have to deine the data type nor the amount
#if false 
      LowestConfidenceMatching =
          fuzzyAND<CONFDATATYPE>(2, LowestConfidenceMatching,
                                 FuzzyFunctionSampleMatches->operator()(
                                     relativeDistance<INDATATYPE, PROCDATATYPE>(
                                         Sample, HistorySample)));
      //@benedikt: same with "->operator()"
      HighestConfidenceMismatching =
          fuzzyOR<CONFDATATYPE>(2, HighestConfidenceMismatching,
                                FuzzyFunctionSampleMismatches->operator()(
                                    relativeDistance<INDATATYPE, PROCDATATYPE>(
                                        Sample, HistorySample)));
#else
      LowestConfidenceMatching =
          fuzzyAND(LowestConfidenceMatching,
                   FuzzyFunctionSampleMatches->operator()(
                       relativeDistance<INDATATYPE, PROCDATATYPE>(
                           Sample, HistorySample)));
      //@benedikt: same with "->operator()"
      HighestConfidenceMismatching =
          fuzzyOR(HighestConfidenceMismatching,
                  FuzzyFunctionSampleMismatches->operator()(
                      relativeDistance<INDATATYPE, PROCDATATYPE>(
                          Sample, HistorySample)));
#endif
    }
    LowestConfidenceMatchingHistory.addEntry(LowestConfidenceMatching);
    HighestConfidenceMismatchingHistory.addEntry(HighestConfidenceMismatching);

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

    //@maxi you don't have to define the data type or the amount
#if false 
    //@benedikt: same with "->operator()"
    CONFDATATYPE ConfidenceSignalStateIsValid = fuzzyAND<CONFDATATYPE>(
        2, LowestConfidenceMatching,
        FuzzyFunctionNumOfSamplesMatches->operator()(static_cast<INDATATYPE>(
            SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));
    //@benedikt: same with "->operator()"
    CONFDATATYPE ConfidenceSignalStateIsInvalid = fuzzyOR<CONFDATATYPE>(
        2, HighestConfidenceMismatching,
        FuzzyFunctionNumOfSamplesMismatches->operator()(static_cast<INDATATYPE>(
            SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));
#else
    //@benedikt: same with "->operator()"
    CONFDATATYPE ConfidenceSignalStateIsValid = fuzzyAND(
        LowestConfidenceMatching,
        FuzzyFunctionNumOfSamplesMatches->operator()(static_cast<INDATATYPE>(
            SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));
    //@benedikt: same with "->operator()"
    CONFDATATYPE ConfidenceSignalStateIsInvalid = fuzzyOR(
        HighestConfidenceMismatching,
        FuzzyFunctionNumOfSamplesMismatches->operator()(static_cast<INDATATYPE>(
            SignalStateInfo.NumberOfInsertedSamplesAfterEntrance)));
#endif

    if (ConfidenceSignalStateIsValid > ConfidenceSignalStateIsInvalid) {
      if (SignalStateInfo.SignalStateIsValid) {
        SignalStateInfo.SignalStateJustGotValid = false;
      } else {
        SignalStateInfo.SignalStateJustGotValid = true;
      }
      SignalStateInfo.SignalStateIsValid = true;
      SignalStateInfo.SignalStateIsValidAfterReentrance = true;
    }
  }

  void checkSignalStability(void) {
    CONFDATATYPE ConfidenceSignalIsStable;
    CONFDATATYPE ConfidenceSignalIsDrifting;

    if (DABHistory.numberOfEntries() >= 2) {
      //@benedikt: same "->operator()"
      ConfidenceSignalIsStable = FuzzyFunctionSignalIsStable->operator()(
          relativeDistance<INDATATYPE, PROCDATATYPE>(
              DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0]));
      //@benedikt: same "->operator()"
      ConfidenceSignalIsDrifting = FuzzyFunctionSignalIsDrifting->operator()(
          relativeDistance<INDATATYPE, PROCDATATYPE>(
              DABHistory[DABHistory.numberOfEntries() - 1], DABHistory[0]));
    } else {
      // TODO: change to enum
      ConfidenceSignalIsStable = 1;
      ConfidenceSignalIsDrifting = 0;
    }

    SignalStateInfo.SignalIsStable =
        ConfidenceSignalIsStable >= ConfidenceSignalIsDrifting;
  }
};

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

#endif // ROSA_AGENT_SIGNALSTATE_HPP
