//===-- 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 statePtr = std::shared_ptr<StateInformation<CONFTYPE>>;
  using stateInfoPtr = std::shared_ptr<StateInformation<CONFTYPE>>;

  /// 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.
  std::shared_ptr<statePtr> CurrentState;
  /// The DetectedStates is vector in that all detected states are saved.
  std::vector<statePtr> DetectedStates;

  /// The PartialFunctionSampleMatches is the fuzzy function that gives the
  /// convidence how good the new sample matches another sample in the sample
  /// history.
  partFuncPointer PartialFunctionSampleMatches;
  /// The PartialFunctionSampleMatches is the fuzzy function that gives the
  /// convidence how bad the new sample matches another sample in the sample
  /// history.
  partFuncPointer PartialFunctionSampleMismatches;
  /// The PartialFunctionSampleMatches is the fuzzy function that gives the
  /// convidence how many samples from the sampe history match the new sample.
  partFuncPointer PartialFunctionNumOfSamplesMatches;
  /// The PartialFunctionSampleMatches is the fuzzy function that gives the
  /// convidence how many samples from the sampe history mismatch the new
  /// sample.
  partFuncPointer PartialFunctionNumOfSamplesMismatches;

  /// 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 PartialFunctionSampleMatches,
                partFuncPointer PartialFunctionSampleMismatches,
                partFuncPointer PartialFunctionNumOfSamplesMatches,
                partFuncPointer PartialFunctionNumOfSamplesMismatches,
                unsigned int SampleHistorySize, unsigned int DABSize,
                unsigned int DABHistorySize) noexcept
      : StateHasChanged(false), CurrentState(NULL),
        PartialFunctionSampleMatches(PartialFunctionSampleMatches),
        PartialFunctionSampleMismatches(PartialFunctionSampleMismatches),
        PartialFunctionNumOfSamplesMatches(PartialFunctionNumOfSamplesMatches),
        PartialFunctionNumOfSamplesMismatches(
            PartialFunctionNumOfSamplesMismatches),
        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
  ///
  /// \return the information of the actual state (state ID and other
  /// parameters)
  stateInfoPtr detectState(INDATATYPE Sample) {

    if (CurrentState == NULL) {
      ASSERT(DetectedStates.empty());

      statePtr S = createNewState();
      if (S) {
        CurrentState = S;
      } else {
        // TODO: handle (or at least log) if now state could be created.
        // However, this handling/logging could be also done in
        // createNewState().
      }
    } 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 = NULL;

        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) {
          if (statePtr S = createNewState()) {
            CurrentState = S;
          } else {
            // TODO: handle (or at least log) if now state could be created.
            // However, this handling/logging could be also done in
            // createNewState().
          }
        }
      }
    }

    CurrentState->insertSample(Sample);

    return CurrentState->stateInfo();
  }

  /// 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(void) {
    if (CurrentState) {
      return CurrentState->stateInfo();
    } else {
      return NULL;
    }
  }

  /// 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 PartialFunctionSampleMatches the
  /// \param PartialFunctionSampleMismatches
  /// \param PartialFunctionNumOfSamplesMatches
  /// \param PartialFunctionNumOfSamplesMismatches
  ///
  /// \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,
              PartialFunctionSampleMatches, PartialFunctionSampleMismatches,
              PartialFunctionNumOfSamplesMatches,
              PartialFunctionNumOfSamplesMismatches);

    if (S) {
      DetectedStates.push_back(S);
      return S;
    } else {
      return NULL;
    }
  }
};

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

#endif // ROSA_AGENT_STATEDETECTOR_HPP
