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


#ifndef ROSA_AGENT_STATEDETECTOR_HPP
#define ROSA_AGENT_STATEDETECTOR_HPP

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

#include <vector>

// TODO: change everything from state to signal state

namespace rosa {
namespace agent {

/// Implements \c rosa::agent::StateDetector as a functionality that detects
/// states given on input samples.
///
/// \note This implementation is supposed to be used for samples of an
/// arithmetic type.
///
/// \tparam INDATATYPE is the type of input data, \tparam CONFDATATYPE is type
/// of
/// data in that the confidence values are given
template <typename INDATATYPE, typename CONFDATATYPE>
class SignalStateDetector : 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 abstraction type 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>>;
  using StatePtr = std::shared_ptr<StateInformation<CONFDATATYPE>>;
  using StateInfoPtr = std::shared_ptr<StateInformation<CONFDATATYPE>>;

  /// The NextStateID is a counter variable which stores the ID which the next
  /// state shall have.
  unsigned int NextStateID;

  /// The SignalStateHasChanged is a flag that show whether a state change has
  /// happened.
  bool SignalStateHasChanged;

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

  /// The DetectedStates is vector in that all detected states are saved.
  // TODO: make it to history
  std::vector<StatePtr> DetectedStates;

  /// 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.
  unsigned int SampleHistorySize;
  /// DABSize the size of a DAB (Discrete Average Block).
  unsigned int DABSize;
  /// DABHistorySize is the (maximum) size of the DAB history.
  unsigned int 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
  /// State .
  ///
  /// \param DABSize Sets the DAB size which will be used by \c State .
  ///
  /// \param DABHistorySize Sets the size which will be used by \c State .
  ///
  StateDetector(PartFuncPointer FuzzyFunctionSampleMatches,
                PartFuncPointer FuzzyFunctionSampleMismatches,
                StepFuncPointer FuzzyFunctionNumOfSamplesMatches,
                StepFuncPointer FuzzyFunctionNumOfSamplesMismatches,
                PartFuncPointer FuzzyFunctionSignalIsDrifting,
                PartFuncPointer FuzzyFunctionSignalIsStable,
                unsigned int SampleHistorySize, unsigned int DABSize,
                unsigned int DABHistorySize) noexcept
      : NextStateID(1), StateHasChanged(false), CurrentState(NULL),
        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 a signal state to which the new sample belongs or create a new
  /// 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 signal state ID as unsigend integer type. State IDs start with
  /// number
  /// 1; that means if there is no current state, the return value is 0.
  unsigned int detectSignalState(INDATATYPE Sample) noexcept {
    StateInfoPtr StateInfo = detectState__debug(Sample);
    return StateInfo->StateID;
  }

  /// Gives information about the current state.
  ///
  /// \return a the Signal State ID (as unsigned integer type) of the current
  /// state.
  /// State IDs start with number 1; that means if there is no current state,
  /// the return value is 0.
  unsigned int currentSignalStateInformation(void) noexcept {
    StateInfoPtr StateInfo = currentStateInformation__debug();
    if (StateInfo) {
      return StateInfo->StateID;
    } else {
      return 0;
    }
  }

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

private:
  // TODO: change exlaination! it is not totally right
  //@maxi \param is there to Document a specific parameter of a method/function
  // this method doesn't have any parameters.
  /// Creates a new state and adds this state to the state vector in which all
  /// known states are saved.
  ///
  /// \param SampleHistorySize the (maximum) size of the sample history.
  /// \param DABSize the size of a DAB.
  /// \param DABHistorySize the (maximum) size of the DAB history.
  /// \param FuzzyFunctionSampleMatches the
  /// \param FuzzyFunctionSampleMismatches
  /// \param FuzzyFunctionNumOfSamplesMatches
  /// \param FuzzyFunctionNumOfSamplesMismatches
  ///
  /// \return a pointer to the newly created state or NULL if no state could be
  /// created.
  StatePtr createNewState(void) noexcept {
    StatePtr S = new (std::nothrow) State(
        NextStateID, SampleHistorySize, DABSize, DABHistorySize,
        FuzzyFunctionSampleMatches, FuzzyFunctionSampleMismatches,
        FuzzyFunctionNumOfSamplesMatches, FuzzyFunctionNumOfSamplesMismatches);

    // @benedikt: todo: assert in history, which checks if push_back worked
    DetectedStates.push_back(S);

    return S;
  }

#ifdef STATEDETECTORDEBUGMODE
public:
#else
private:
#endif // STATEDETECTORDEBUGMODE

  // @maxi is this a debug method or is it a method that will be used and
  // you simply want to have access to it in debug mode?
  // debug  ->	extend the preprocessor around the function
  // access ->	remove the __debug from the name ( it is confusing)
  //			if you want to have it marked as a debug method for auto
  //			complete you can do something like this :
  //
  //#ifdef STATEDETECTORDEBUGMODE
  // public:
  //  StateInfoPtr debug_detectState(INDATATYPE Sample) {
  //    return detectState(Sample);
  //  }
  //#endif // STATEDETECTORDEBUGMODE
  // private :
  //  StateInfoPtr detectState(INDATATYPE Sample) { ...
  //
  /// Detects the state to which the new sample belongs or create a new 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 actual state (state ID and other
  /// parameters).
  // TODO: return something const.. cannot remember exactly (ask benedikt)
  //
  // maybe: you are returning a pointer to the state info so who ever has that
  // pointer can actually change the information if you want to return only the
  // *current info* return a copy of the state info
  // like this:
  //
  // StateInfoPtr detectState__debug(INDATATYPE Sample) ->
  // StateInformation<CONFTYPE> detectState__debug(INDATATYPE Sample)
  //
  // return CurrentState->stateInformation(); ->
  // return *(CurrentState->stateInformation());
  StateInfoPtr detectState__debug(INDATATYPE Sample) noexcept {

    if (!CurrentState) {
      ASSERT(DetectedStates.empty());

      StatePtr S = createNewState();
      CurrentState = S;
    } else {
      CONFDATATYPE ConfidenceSampleMatchesState =
          CurrentState->confSampleMatchesState(Sample);
      CONFDATATYPE ConfidenceSampleMismatchesState =
          CurrentState->confSampleMismatchesState(Sample);

      if (ConfidenceSampleMatchesState > ConfidenceSampleMismatchesState) {
        SignalStateHasChanged = false;
      } else {
        SignalStateHasChanged = true;

        if (CurrentState->stateInformation()->StateIsValid) {
          CurrentState->leaveState();
        } else {
          DetectedStates.erase(std::find(DetectedStates.begin(),
                                         DetectedStates.end(), CurrentState));
        }

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

        for (auto &SavedState : DetectedStates) {
          if (SavedState != CurrentState) {
            CONFDATATYPE ConfidenceSampleMatchesState =
                SavedState->confSampleMatchesState(Sample);
            CONFDATATYPE ConfidenceSampleMismatchesState =
                SavedState->confSampleMismatchesState(Sample);

            if (ConfidenceSampleMatchesState >
                ConfidenceSampleMismatchesState) {
              // TODO (future): maybe it would be better to compare
              // ConfidenceSampleMatchesState of all states in the vector in
              // order to find the best matching state.
              CurrentState = SavedState;
              break;
            }
          }
        }

        if (!CurrentState) {
          StatePtr S = createNewState();
          CurrentState = S;
        }
      }
    }

    StateInformation<CONFDATATYPE> StateInfo =
        CurrentState->insertSample(Sample);

    if (StateInfo.StateJustGotValid) {
      NextStateID++;
    }

    return StateInfo;
  }

#ifdef STATEDETECTORDEBUGMODE
public:
#else
private:
#endif // STATEDETECTORDEBUGMODE

  /// Gives information about the current state.
  ///
  /// \return a struct StateInformation that contains information about the
  /// current state or NULL if no current state exists.
  StateInformation<CONFDATATYPE> currentStateInformation__debug(void) noexcept {
    if (CurrentState) {
      return CurrentState->stateInformation();
    } else {
      return NULL;
    }
  }
};

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

#endif // ROSA_AGENT_STATEDETECTOR_HPP
