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

#ifndef ROSA_AGENT_SIGNALSTATEDETECTOR_HPP
#define ROSA_AGENT_SIGNALSTATEDETECTOR_HPP

#include "rosa/agent/Functionality.h"
#include "rosa/agent/SignalState.hpp"
#include "rosa/agent/StateDetector.hpp"

#include <vector>

namespace rosa {
namespace agent {

/// Implements \c rosa::agent::SignalStateDetector as a functionality that
/// detects signal states given on input samples.
///
/// \note This implementation is supposed to be used for samples of an
/// arithmetic type.
///
/// \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,
          HistoryPolicy HP>
class SignalStateDetector
    : public StateDetector<INDATATYPE, CONFDATATYPE, PROCDATATYPE, HP> {

  // @maxi added them so it is compilable is this what you intended?
  using StateDetector =
      StateDetector<INDATATYPE, CONFDATATYPE, PROCDATATYPE, HP>;
  using PartFuncPointer = typename StateDetector::PartFuncPointer;
  using StepFuncPointer = typename StateDetector::StepFuncPointer;

private:
  // For the convinience to write a shorter data type name
  using SignalStatePtr =
      std::shared_ptr<SignalState<INDATATYPE, CONFDATATYPE, PROCDATATYPE>>;

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

  /// The NextSignalStateID is a counter variable which stores the ID which the
  /// next signal state shall have.
  uint32_t NextSignalStateID;

  /// The SignalStateHasChanged is a flag that show whether a signal has changed
  /// its state.
  bool SignalStateHasChanged;

  /// The CurrentSignalState is a pointer to the (saved) signal state in which
  /// the actual variable (signal) of the observed system is.
  SignalStatePtr CurrentSignalState;

  /// The DetectedSignalStates is a history in that all detected signal states
  /// are saved.
  DynamicLengthHistory<SignalStatePtr, HP> DetectedSignalStates;

  /// 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 is drifting.
  PartFuncPointer FuzzyFunctionSignalIsDrifting;

  /// The FuzzyFunctionSignalIsStable is the fuzzy function that gives the
  /// confidence how likely it is that the signal is stable (not drifting).
  PartFuncPointer FuzzyFunctionSignalIsStable;

  /// SampleHistorySize is the (maximum) size of the sample history.
  uint32_t SampleHistorySize;
  /// DABSize the size of a DAB (Discrete Average Block).
  uint32_t DABSize;
  /// DABHistorySize is the (maximum) size of the DAB history.
  uint32_t DABHistorySize;

public:
  /// Creates an instance by setting all parameters
  /// \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 Sets the History size which will be used by \c
  /// SignalState.
  ///
  /// \param DABSize Sets the DAB size which will be used by \c SignalState.
  ///
  /// \param DABHistorySize Sets the size which will be used by \c SignalState.
  ///
  SignalStateDetector(SignalProperties SignalProperty,
                      uint32_t MaximumNumberOfSignalStates,
                      PartFuncPointer FuzzyFunctionSampleMatches,
                      PartFuncPointer FuzzyFunctionSampleMismatches,
                      StepFuncPointer FuzzyFunctionNumOfSamplesMatches,
                      StepFuncPointer FuzzyFunctionNumOfSamplesMismatches,
                      PartFuncPointer FuzzyFunctionSignalIsDrifting,
                      PartFuncPointer FuzzyFunctionSignalIsStable,
                      uint32_t SampleHistorySize, uint32_t DABSize,
                      uint32_t DABHistorySize) noexcept
      : NextSignalStateID(1), SignalStateHasChanged(false),
        CurrentSignalState(nullptr), SignalProperty(SignalProperty),
        DetectedSignalStates(MaximumNumberOfSignalStates),
        FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches),
        FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches),
        FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches),
        FuzzyFunctionNumOfSamplesMismatches(
            FuzzyFunctionNumOfSamplesMismatches),
        FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting),
        FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable),
        SampleHistorySize(SampleHistorySize), DABSize(DABSize),
        DABHistorySize(DABHistorySize) {}

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

  /// Detects the signal state to which the new sample belongs or create a new
  /// signal state if the new sample does not match to any of the saved states.
  ///
  /// \param Sample is the actual sample of the observed signal.
  ///
  /// \return the information of the current signal state (signal state ID and
  /// other parameters).
  // TODO (future): change to operator()
  SignalStateInformation<CONFDATATYPE>
  detectSignalState(INDATATYPE Sample) noexcept {

    if (!CurrentSignalState) {
      ASSERT(DetectedSignalStates.empty());

      SignalStatePtr S = createNewSignalState();
      CurrentSignalState = S;
    } else {
      CONFDATATYPE ConfidenceSampleMatchesSignalState =
          CurrentSignalState->confidenceSampleMatchesSignalState(Sample);
      CONFDATATYPE ConfidenceSampleMismatchesSignalState =
          CurrentSignalState->confidenceSampleMismatchesSignalState(Sample);

      if (ConfidenceSampleMatchesSignalState >
          ConfidenceSampleMismatchesSignalState) {
        SignalStateHasChanged = false;
      } else {
        SignalStateHasChanged = true;

        if (CurrentSignalState->signalStateInformation().SignalStateIsValid) {
          CurrentSignalState->leaveSignalState();
        } else {
          DetectedSignalStates.deleteEntry(CurrentSignalState);
        }

        // TODO (future): additionally save averages to enable fast iteration
        // through recorded signl state history (maybe sort vector based on
        // these average values)
        CurrentSignalState = nullptr;

        //@benedikt: same question
        for (auto &SavedSignalState : DetectedSignalStates) {
          if (SavedSignalState != CurrentSignalState) {
            ConfidenceSampleMatchesSignalState =
                SavedSignalState->confidenceSampleMatchesSignalState(Sample);
            ConfidenceSampleMismatchesSignalState =
                SavedSignalState->confidenceSampleMismatchesSignalState(Sample);

            if (ConfidenceSampleMatchesSignalState >
                ConfidenceSampleMismatchesSignalState) {
              // TODO (future): maybe it would be better to compare
              // ConfidenceSampleMatchesSignalState of all signal states in the
              // vector in order to find the best matching signal state.
              CurrentSignalState = SavedSignalState;
              break;
            }
          }
        }

        if (!CurrentSignalState) {
          SignalStatePtr S = createNewSignalState();
          CurrentSignalState = S;
        }
      }
    }

    SignalStateInformation<CONFDATATYPE> SignalStateInfo =
        CurrentSignalState->insertSample(Sample);

    if (SignalStateInfo.SignalStateJustGotValid) {
      NextSignalStateID++;
    }

    return SignalStateInfo;
  }

  /// Gives information about the current signal state.
  ///
  /// \return a struct SignalStateInformation that contains information about
  /// the current signal state or NULL if no current signal state exists.
  SignalStateInformation<CONFDATATYPE>
  currentSignalStateInformation(void) noexcept {
    if (CurrentSignalState) {
      return CurrentSignalState->signalStateInformation();
    } else {
      return NULL;
    }
  }

  /// Gives information whether a signal state change has happened or not.
  ///
  /// \return true if a signal state change has happened, and false if not.
  bool signalStateHasChanged(void) noexcept { return SignalStateHasChanged; }

private:
  /// Creates a new signal state and adds it to the signal state vector in which
  /// all known states are saved.
  ///
  /// \return a pointer to the newly created signal state or NULL if no state
  /// could be created.
  SignalStatePtr createNewSignalState(void) noexcept {
    SignalStatePtr S(new SignalState<INDATATYPE, CONFDATATYPE, PROCDATATYPE>(
        NextSignalStateID, SampleHistorySize, DABSize, DABHistorySize,
        *FuzzyFunctionSampleMatches, *FuzzyFunctionSampleMismatches,
        *FuzzyFunctionNumOfSamplesMatches, *FuzzyFunctionNumOfSamplesMismatches,
        *FuzzyFunctionSignalIsDrifting, *FuzzyFunctionSignalIsStable));

    DetectedSignalStates.addEntry(S);

    return S;
  }
};

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

#endif // ROSA_AGENT_SIGNALSTATEDETECTOR_HPP
