//===-- rosa/agent/SystemStateDetector.hpp ----------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/SystemStateDetector.hpp
///
/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *system state detector* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP
#define ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP

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

#include "rosa/support/debug.hpp"

namespace rosa {
namespace agent {

/// TODO: write description
template <typename INDATATYPE, typename CONFDATATYPE, typename PROCDATATYPE,
          HistoryPolicy HP>
class SystemStateDetector
    : public StateDetector<INDATATYPE, CONFDATATYPE, PROCDATATYPE, HP> {

  using StateDetector =
      StateDetector<INDATATYPE, CONFDATATYPE, PROCDATATYPE, HP>;
  using PartFuncPointer = typename StateDetector::PartFuncPointer;

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

  /// TODO: description
  uint32_t NumberOfSignals;

  /// The CurrentSystemState is a pointer to the (saved) system state in which
  /// the actual state of the observed system is.
  SystemStatePtr CurrentSystemState;

  /// The DetectedSystemStates is a history in that all detected system states
  /// are saved.
  DynamicLengthHistory<SystemStatePtr, HP> DetectedSystemStates;

  /// TODO: description
  unsigned int TimeOfDisparity;

  /// The FuzzyFunctionDelayTimeToBeWorking is the fuzzy function that gives
  /// the
  /// confidence whether the system is still OK allthough an input change
  /// without an output change or vice versa.
  PartFuncPointer FuzzyFunctionTimeSystemFunctioning;

  /// The FuzzyFunctionDelayTimeToGetBroken is the fuzzy function that gives
  /// the confidence whether the system is Broken because of an input change
  /// without an output change or vice versa. A small time gap between the two
  /// shall be allowed.
  PartFuncPointer FuzzyFunctionTimeSystemMalfunctioning;

public:
  // todo zwei parameter für variablen anzahl
  /// TODO: write description
  SystemStateDetector(
      uint32_t MaximumNumberOfSystemStates, uint32_t NumberOfSignals,
      PartFuncPointer FuzzyFunctionTimeSystemMalfunctioning,
      PartFuncPointer FuzzyFunctionTimeSystemFunctioning) noexcept
      : NumberOfSignals(NumberOfSignals), CurrentSystemState(nullptr),
        DetectedSystemStates(MaximumNumberOfSystemStates), TimeOfDisparity(0),
        FuzzyFunctionTimeSystemFunctioning(FuzzyFunctionTimeSystemFunctioning),
        FuzzyFunctionTimeSystemMalfunctioning(
            FuzzyFunctionTimeSystemMalfunctioning) {
    //@Benedikt: if I write "NextStateID(1), StateHasChanged(false)" before the
    //{}-brackets, the compiler tells me: "SystemStateDetector.hpp:72:9: error:
    // member initializer 'NextStateID'/'StateHasChanged' does not name a
    // non-static data member or base class"
    this->NextStateID = 1;
    this->StateHasChanged = false;
  }

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

  /// TODO: write description
  SystemStateInformation<CONFDATATYPE>
  detectSystemState(std::vector<SignalStateInformation<CONFDATATYPE>>
                        SignalStateInfos) noexcept {

    SystemStateInformation<CONFDATATYPE> SystemStateInfo;

    if (!CurrentSystemState) {
      ASSERT(DetectedSystemStates.empty());
      SystemStatePtr S = createNewSystemState();
      CurrentSystemState = S;

      SystemStateInfo =
          CurrentSystemState->insertSignalStateInformation(SignalStateInfos);
    } else {

      SystemStateRelation SysStateRel =
          CurrentSystemState->compareSignalStateInformation(SignalStateInfos);

      if (SysStateRel == SystemStateRelation::STATEISMATCHING) {
        TimeOfDisparity = 0;

        SystemStateInfo =
            CurrentSystemState->insertSignalStateInformation(SignalStateInfos);
      } else { // ONLYINPUTISMATCHING, ONLYOUTPUTISMATCHING, STATEISMISMATCHING

        if (!CurrentSystemState->systemStateInformation().StateIsValid)
          DetectedSystemStates.deleteEntry(CurrentSystemState);
        CurrentSystemState = nullptr;

        SystemStatePtr potentialSystemState = nullptr;

        // search all saved system states
        for (auto &SavedSystemState : DetectedSystemStates) {
          SysStateRel =
              SavedSystemState->compareSignalStateInformation(SignalStateInfos);
          if (SysStateRel == SystemStateRelation::STATEISMATCHING) {
            CurrentSystemState = SavedSystemState;
            break;
          } else if (SysStateRel == SystemStateRelation::ONLYINPUTISMATCHING ||
                     SysStateRel == SystemStateRelation::ONLYOUTPUTISMATCHING) {
            // TODO: choose best matching
            potentialSystemState = SavedSystemState;
          }
        }

        // actions depending whether state is matchin fully or only half
        if (CurrentSystemState) {
          TimeOfDisparity = 0;

          SystemStateInfo = CurrentSystemState->insertSignalStateInformation(
              SignalStateInfos);

        } else if (potentialSystemState) {
          TimeOfDisparity++;

          CurrentSystemState = potentialSystemState;

          SystemStateInfo = CurrentSystemState->systemStateInformation();

        } else {
          SystemStatePtr S = createNewSystemState();
          TimeOfDisparity = 0;

          CurrentSystemState = S;

          SystemStateInfo = CurrentSystemState->insertSignalStateInformation(
              SignalStateInfos);
        }
      }
    }

    // TODO: is this right? if i don't insert if broke, it will never be valid?!
    // right?
    if (!SystemStateInfo.StateIsValidAfterReentrance) {
      TimeOfDisparity = 0;
    }

    // TODO: maybe make reference instead of pointer
    SystemStateInfo.ConfidenceSystemIsFunctioning =
        (*FuzzyFunctionTimeSystemFunctioning)(
            static_cast<INDATATYPE>(TimeOfDisparity));
    SystemStateInfo.ConfidenceSystemIsMalfunctioning =
        (*FuzzyFunctionTimeSystemMalfunctioning)(
            static_cast<INDATATYPE>(TimeOfDisparity));

    if (SystemStateInfo.ConfidenceSystemIsMalfunctioning >
        SystemStateInfo.ConfidenceSystemIsFunctioning)
      SystemStateInfo.StateCondition = StateConditions::MALFUNCTIONING;

    if (SystemStateInfo.StateCondition == StateConditions::UNKNOWN)
      // TODO: think about a Confidence calculation when system state is unkown
      SystemStateInfo.ConfidenceOfAllDecisions = 0;
    else if (SystemStateInfo.StateCondition == StateConditions::STABLE) {
      SystemStateInfo.ConfidenceOfAllDecisions = fuzzyAND(
          fuzzyOR(
              fuzzyAND(SystemStateInfo.ConfidenceOfInputsMatchingState,
                       SystemStateInfo.ConfidenceOfOutputsMatchingState),
              fuzzyAND(SystemStateInfo.ConfidenceOfInputsMismatchingState,
                       SystemStateInfo.ConfidenceOfOutputsMismatchingState)),
          SystemStateInfo.ConfidenceSystemIsFunctioning,
          SystemStateInfo.ConfidenceStateIsStable,
          SystemStateInfo.ConfidenceStateIsValid);
    } else if (SystemStateInfo.StateCondition == StateConditions::DRIFTING) {
      SystemStateInfo.ConfidenceOfAllDecisions =
          fuzzyAND(SystemStateInfo.ConfidenceOfInputsMatchingState,
                   SystemStateInfo.ConfidenceOfOutputsMatchingState,
                   SystemStateInfo.ConfidenceStateIsDrifting,
                   SystemStateInfo.ConfidenceStateIsValid);
    } else if (SystemStateInfo.StateCondition ==
               StateConditions::MALFUNCTIONING) {
      SystemStateInfo.ConfidenceOfAllDecisions =
          fuzzyAND(SystemStateInfo.ConfidenceOfInputsMismatchingState,
                   SystemStateInfo.ConfidenceOfOutputsMismatchingState,
                   SystemStateInfo.ConfidenceSystemIsMalfunctioning,
                   SystemStateInfo.ConfidenceStateIsValid);
    }
    if (SystemStateInfo.StateJustGotValid) {
      this->NextStateID++;
    }

    return SystemStateInfo;
  }

private:
  /// Creates a new system state and adds it to the system 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.
  SystemStatePtr createNewSystemState(void) noexcept {
    SystemStatePtr S(new SystemState<INDATATYPE, CONFDATATYPE, PROCDATATYPE>(
        this->NextStateID, this->NumberOfSignals));
    DetectedSystemStates.addEntry(S);
    return S;
  }
};

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

#endif // ROSA_AGENT_SYSTEMSTATEDETECTOR_HPP
