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

#include <vector>

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 CONFTYPE is type of
/// data in that the confidence values are given
template <typename INDATATYPE, typename CONFTYPE>
class StateDetector : 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<CONFTYPE>::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, CONFTYPE>>;
  using StepFuncPointer = std::shared_ptr<StepFunction<INDATATYPE, CONFTYPE>>;
  using StatePtr = std::shared_ptr<StateInformation<CONFTYPE>>;
  using StateInfoPtr = std::shared_ptr<StateInformation<CONFTYPE>>;

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

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

  /// 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.
  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 FuzzyFunctionSampleMatches is the fuzzy function that gives the
  /// confidence how bad the new sample matches another sample in the sample
  /// history.
  PartFuncPointer FuzzyFunctionSampleMismatches;
  /// The FuzzyFunctionSampleMatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history match the new sample.
  StepFuncPointer FuzzyFunctionNumOfSamplesMatches;
  /// The FuzzyFunctionSampleMatches 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
  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.
  ~StateDetector(void) = default;

  /// 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 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 detectState(INDATATYPE Sample) {
    StateInfoPtr StateInfo = detectState__debug(Sample);
    return StateInfo->StateID;
  }

  /// Gives information about the current state.
  ///
  /// \return a the 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 currentStateInformation(void) {
    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 stateHasChanged(void) { return StateHasChanged; }

private:
  /// 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 the new created state or NULL if no state could be created.
  StatePtr createNewState(void) {

    StatePtr S = new (std::nothrow) State(
        SampleHistorySize, DABSize, DABHistorySize, FuzzyFunctionSampleMatches,
        FuzzyFunctionSampleMismatches, FuzzyFunctionNumOfSamplesMatches,
        FuzzyFunctionNumOfSamplesMismatches);

    // TODO: maybe assert which checks if push_back worked (ask benedikt)
    DetectedStates.push_back(S);

    return S;
  }

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

  /// 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)
  StateInfoPtr detectState__debug(INDATATYPE Sample) {

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

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

      if (ConfidenceSampleMatchesState > ConfidenceSampleMismatchesState) {
        StateHasChanged = false;
      } else {
        StateHasChanged = 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) {
            CONFTYPE ConfidenceSampleMatchesState =
                SavedState->confSampleMatchesState(Sample);
            CONFTYPE 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;
        }
      }
    }

    StateInfoPtr StateInfo = CurrentState->insertSample(Sample);

    if (StateInfo->StateJustGotValid) {
      NextStateID++;
    }

    return CurrentState->stateInformation();
  }

#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.
  StateInfoPtr currentStateInformation__debug(void) {
    if (CurrentState) {
      return CurrentState->stateInformation();
    } else {
      return NULL;
    }
  }
};

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

#endif // ROSA_AGENT_STATEDETECTOR_HPP
