//===-- rosa/agent/SystemState.hpp ------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \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/support/debug.hpp"

#include <vector>

namespace rosa {
namespace agent {

// System state conditions defining how the condition of a \c
/// rosa::agent::SystemState is saved in \c rosa::agent::SystemStateInformation.
enum class SystemStateCondition : uint8_t {
  STABLE = 0,         ///< The system state is stable
  DRIFTING = 1,       ///< The system state is drifting
  MALFUNCTIONING = 2, ///< The system state is malfunctioning
  UNKNOWN = 3         ///< The system state is unknown
};

/// TODO: write description
template <typename CONFDATATYPE> struct SystemStateInformation {
  // Make sure the actual type arguments are matching our expectations.
  STATIC_ASSERT((std::is_arithmetic<CONFDATATYPE>::value),
                "confidence type is not to arithmetic");

  /// The system state ID saved as an uint32_teger number
  uint32_t SystemStateID;
  /// The SystemStateConfidence shows the overall confidence value of the system
  /// state.
  CONFDATATYPE OverallDetectionConfidence;
  /// The SystemStateCondition shows the condition of a system state (stable,
  /// drifting, malfunctioning, or unknown)
  SystemStateCondition SystemStateCondition;
  /// The SystemStateIsValid saves the number of samples which have been
  /// inserted into the state after entering it.
  uint32_t NumberOfInsertedSamplesAfterEntrance;
  /// The SystemStateIsValid shows whether a state is valid or invalid.
  /// In this context, valid means that enough samples which are in close
  /// proximitry have been inserted into the state.
  bool SystemStateIsValid;
  /// The SystemStateJustGotValid shows whether a system state got valid
  /// (toggled from invalid to valid) during the current inserted sample.
  bool SystemStateJustGotValid;
  /// The SystemStateIsValidAfterReentrance shows whether a system state is
  /// valid after the variable changed back to it again.
  bool SystemStateIsValidAfterReentrance;
};

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

  // 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:
  SystemStateInformation<CONFDATATYPE> SystemStateInfo;

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

  uint32_t NumberOfSignals;

public:
  /// TODO: write description
  SystemState(uint32_t StateID, uint32_t NumberOfSignals) noexcept
      : SystemStateInfo{StateID, 0,    SystemStateCondition::UNKNOWN, 0, false,
                        false,   false},
        NumberOfSignals(NumberOfSignals) {
    //@benedikt: there is now possibility to not doing the resize within these
    //{}-brackets, right?
    Signals.resize(NumberOfSignals);
  }

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

  /// TODO: write description
  template <std::size_t size>
  void insertSignalStateInformation(
      const std::array<SystemStateInformation<CONFDATATYPE>, size>
          &Data) noexcept {
    //@Daniel: is it possible to check with a ASSERT/STATIC_ASSERT whether the
    // number of parameter is the same as NumberOfSignals (which is the length
    // of the vectror)?
    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) {
    //@Daniel: is it possible to check with a ASSERT/STATIC_ASSERT whether the
    // number of parameter is the same as NumberOfSignals (which is the length
    // of the vectror)?
    //
    // 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 {
    //@Daniel: is it possible to check with a ASSERT/STATIC_ASSERT whether the
    // number of parameter is the same as NumberOfSignals (which is the length
    // of the vectror)?
    //
    // 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) {
    //@Daniel: is it possible to check with a ASSERT/STATIC_ASSERT whether the
    // number of parameter is the same as NumberOfSignals (which is the length
    // of the vectror)?
    return compareSignalStateInfos(
        std::array<SystemStateInformation<CONFDATATYPE>, sizeof...(Data)>(
            {Data...}));
  }
};

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

#endif // ROSA_AGENT_SYSTEMSTATE_HPP
