//===-- rosa/agent/SystemState.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/SystemState.hpp
///
/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *system state* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_SYSTEMSTATE_HPP
#define ROSA_AGENT_SYSTEMSTATE_HPP

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

#include "rosa/support/debug.hpp"

#include <vector>

namespace rosa {
namespace agent {

enum class SystemStateRelation : uint8_t {
  STATEISMATCHING = 0,      ///< The system state is matching
  ONLYINPUTISMATCHING = 1,  ///< Only inputs of the system state are matching
  ONLYOUTPUTISMATCHING = 2, ///< Only outputs of the system state are matching
  STATEISMISMATCHING = 3    ///< The system state is mismatching
};

/// TODO: write description
template <typename CONFDATATYPE>
struct SystemStateInformation : StateInformation<CONFDATATYPE> {
  /// TODO: describe
  CONFDATATYPE ConfidenceOfInputsMatchingState;
  CONFDATATYPE ConfidenceOfInputsMismatchingState;
  CONFDATATYPE ConfidenceOfOutputsMatchingState;
  CONFDATATYPE ConfidenceOfOutputsMismatchingState;
  CONFDATATYPE ConfidenceSystemIsFunctioning;
  CONFDATATYPE ConfidenceSystemIsMalfunctioning;
  CONFDATATYPE ConfidenceOfAllDecisions;

public:
  SystemStateInformation() = default;

  SystemStateInformation(unsigned int _SystemStateID,
                         StateConditions _StateCondition) {
    this->StateID = _SystemStateID;
    this->StateCondition = _StateCondition;
    this->StateIsValid = false;
    this->StateJustGotValid = false;
    this->StateIsValidAfterReentrance = false;
    this->ConfidenceOfInputsMatchingState = 0;
    this->ConfidenceOfInputsMismatchingState = 0;
    this->ConfidenceOfOutputsMatchingState = 0;
    this->ConfidenceOfOutputsMismatchingState = 0;
    this->ConfidenceSystemIsFunctioning = 0;
    this->ConfidenceSystemIsMalfunctioning = 0;
    this->ConfidenceOfAllDecisions = 0;
  }
};

// todo: do we need PROCDATATYPE?
/// TODO TEXT
template <typename INDATATYPE, typename CONFDATATYPE, typename PROCDATATYPE>
class SystemState : public State<INDATATYPE, CONFDATATYPE, PROCDATATYPE> {

  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT(std::is_arithmetic<INDATATYPE>::value,
                "input data type is not to arithmetic");
  STATIC_ASSERT(std::is_arithmetic<CONFDATATYPE>::value,
                "confidence abstraction type is not to arithmetic");
  STATIC_ASSERT(std::is_arithmetic<PROCDATATYPE>::value,
                "process data type is not to arithmetic");

private:
  /// SignalStateInfo is a struct of SignalStateInformation that contains
  /// information about the current signal state.
  SystemStateInformation<CONFDATATYPE> SystemStateInfo;

  std::vector<SignalStateInformation<CONFDATATYPE>> SignalStateInfos;

  uint32_t NumberOfSignals;

public:
  /// TODO: write description
  SystemState(uint32_t StateID, uint32_t NumberOfSignals) noexcept
      : SystemStateInfo(StateID, StateConditions::UNKNOWN),
        NumberOfSignals(NumberOfSignals) {
    SignalStateInfos.resize(NumberOfSignals);
  }

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

  /// TODO: write description
  SystemStateInformation<CONFDATATYPE> insertSignalStateInformation(
      const std::vector<SignalStateInformation<CONFDATATYPE>>
          _SignalStateInfos) noexcept {
    ASSERT(_SignalStateInfos.size() == NumberOfSignals);

    bool AllSignalsAreValid = true;
    bool AtLeastOneSignalJustGotValid = false;
    bool AllSignalsAreValidAfterReentrance = true;

    bool AtLeastOneSignalIsUnknown = false;
    bool AllSignalsAreStable = true;

    // TODO: change this
    SystemStateInfo.ConfidenceOfInputsMatchingState = 1;
    SystemStateInfo.ConfidenceOfInputsMismatchingState = 0;
    SystemStateInfo.ConfidenceOfOutputsMatchingState = 1;
    SystemStateInfo.ConfidenceOfOutputsMismatchingState = 0;
    SystemStateInfo.ConfidenceStateIsValid = 1;
    SystemStateInfo.ConfidenceStateIsInvalid = 0;
    SystemStateInfo.ConfidenceStateIsStable = 1;
    SystemStateInfo.ConfidenceStateIsDrifting = 0;

    std::size_t counter = 0;
    for (auto SSI : _SignalStateInfos) {

      if (!SSI.StateIsValid)
        AllSignalsAreValid = false;
      if (SSI.StateJustGotValid)
        AtLeastOneSignalJustGotValid = true;
      if (!SSI.StateIsValidAfterReentrance)
        AllSignalsAreValidAfterReentrance = false;

      if (SSI.StateCondition == StateConditions::UNKNOWN)
        AtLeastOneSignalIsUnknown = true;
      if (SSI.StateCondition == StateConditions::DRIFTING)
        AllSignalsAreStable = false;

      if (SSI.SignalProperty == SignalProperties::INPUT) {
        SystemStateInfo.ConfidenceOfInputsMatchingState =
            fuzzyAND(SystemStateInfo.ConfidenceOfInputsMatchingState,
                     SSI.ConfidenceOfMatchingState);
        SystemStateInfo.ConfidenceOfInputsMismatchingState =
            fuzzyOR(SystemStateInfo.ConfidenceOfInputsMismatchingState,
                    SSI.ConfidenceOfMismatchingState);
      } else {
        SystemStateInfo.ConfidenceOfOutputsMatchingState =
            fuzzyAND(SystemStateInfo.ConfidenceOfOutputsMatchingState,
                     SSI.ConfidenceOfMatchingState);
        SystemStateInfo.ConfidenceOfOutputsMismatchingState =
            fuzzyOR(SystemStateInfo.ConfidenceOfOutputsMismatchingState,
                    SSI.ConfidenceOfMismatchingState);
      }

      SystemStateInfo.ConfidenceStateIsValid = fuzzyAND(
          SystemStateInfo.ConfidenceStateIsValid, SSI.ConfidenceStateIsValid);
      SystemStateInfo.ConfidenceStateIsInvalid =
          fuzzyOR(SystemStateInfo.ConfidenceStateIsInvalid,
                  SSI.ConfidenceStateIsInvalid);
      SystemStateInfo.ConfidenceStateIsStable = fuzzyAND(
          SystemStateInfo.ConfidenceStateIsStable, SSI.ConfidenceStateIsStable);
      SystemStateInfo.ConfidenceStateIsDrifting =
          fuzzyOR(SystemStateInfo.ConfidenceStateIsDrifting,
                  SSI.ConfidenceStateIsDrifting);

      this->SignalStateInfos.at(counter) = SSI;
      counter++;
    }

    SystemStateInfo.StateIsValid = AllSignalsAreValid;
    SystemStateInfo.StateJustGotValid =
        AllSignalsAreValid && AtLeastOneSignalJustGotValid;
    SystemStateInfo.StateIsValidAfterReentrance =
        AllSignalsAreValidAfterReentrance;

    if (AtLeastOneSignalIsUnknown)
      SystemStateInfo.StateCondition = StateConditions::UNKNOWN;
    else if (AllSignalsAreStable)
      SystemStateInfo.StateCondition = StateConditions::STABLE;
    else
      SystemStateInfo.StateCondition = StateConditions::DRIFTING;

    return SystemStateInfo;
  }

  /// TODO: write description
  // TODO (future): think about saving the state information in a history
  SystemStateRelation compareSignalStateInformation(
      const std::vector<SignalStateInformation<CONFDATATYPE>>
          _SignalStateInfos) noexcept {
    bool inputsAreMatching = true;
    bool outputsAreMatching = true;
    std::size_t counter = 0;
    for (auto SSI : _SignalStateInfos) {
      if (this->SignalStateInfos.at(counter).StateID != SSI.StateID) {
        if (SSI.SignalProperty == SignalProperties::INPUT)
          inputsAreMatching = false;
        else // SignalProperties::OUTPUT
          outputsAreMatching = false;
      }
      counter++;
    }

    if (inputsAreMatching && outputsAreMatching)
      return SystemStateRelation::STATEISMATCHING;
    else if (inputsAreMatching && !outputsAreMatching)
      return SystemStateRelation::ONLYINPUTISMATCHING;
    else if (!inputsAreMatching && outputsAreMatching)
      return SystemStateRelation::ONLYOUTPUTISMATCHING;
    else
      return SystemStateRelation::STATEISMISMATCHING;
  }

#ifdef ADDITIONAL_FUNCTIONS
  /// TODO: write description
  template <std::size_t size>
  void insertSignalStateInformation(
      const std::array<SystemStateInformation<CONFDATATYPE>, size>
          &Data) noexcept {
    ASSERT(size <= NumberOfSignals);
    std::size_t counter = 0;
    for (auto tmp : Data) {
      Signals.at(counter) = tmp;
      counter++;
    }
  }

  /// TODO: write description
  template <typename... Types>
  std::enable_if_t<std::conjunction_v<std::is_same<
                       Types, SystemStateInformation<CONFDATATYPE>>...>,
                   void>
  insertSignalStateInformation(Types... Data) {
    // TODO (future): think about saving the state information in a history
    insertSignalStateInfos(
        std::array<SystemStateInformation<CONFDATATYPE>, sizeof...(Data)>(
            {Data...}));
  }

  // returns true if they are identical
  /// TODO: write description
  template <std::size_t size>
  bool compareSignalStateInformation(
      const std::array<SystemStateInformation<CONFDATATYPE>, size>
          &Data) noexcept {
    // TODO (future): think about saving the state information in a history
    std::size_t counter = 0;
    for (auto tmp : Data) {
      if (Signals.at(counter) != tmp)
        return false;
      counter++;
    }
    return true;
  }

  // checks only the given amount
  /// TODO: write description
  template <typename... Types>
  std::enable_if_t<std::conjunction_v<std::is_same<
                       Types, SystemStateInformation<CONFDATATYPE>>...>,
                   bool>
  compareSignalStateInformation(Types... Data) {
    return compareSignalStateInfos(
        std::array<SystemStateInformation<CONFDATATYPE>, sizeof...(Data)>(
            {Data...}));
  }
#endif

  /// Gives information about the current signal state.
  ///
  /// \return a struct SignalStateInformation that contains information about
  /// the current signal state.
  SystemStateInformation<CONFDATATYPE> systemStateInformation(void) noexcept {
    return SystemStateInfo;
  }
};

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

#endif // ROSA_AGENT_SYSTEMSTATE_HPP
