//===-- rosa/agent/State.hpp ----------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/agent/State.hpp
///
/// \author Maximilian Götzinger (maximilian.goetzinger@tuwien.ac.at)
///
/// \date 2019
///
/// \brief Definition of *state* *functionality*.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_AGENT_STATE_HPP
#define ROSA_AGENT_STATE_HPP

#include "rosa/agent/FunctionAbstractions.hpp"
#include "rosa/agent/Functionality.h"
#include "rosa/agent/History.hpp"

#include <cstdarg>

namespace rosa {
namespace agent {

/// State conditions defining how the condition of a \c rosa::agent::State is
/// saved in \c rosa::agent::StateInformation.
enum class VariableStateCondition {
  STABLE,   ///< The state is stable
  DRIFTING, ///< The state is drifting
  UNKNOWN   ///< The state is unknown
};

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

  /// The state ID saved as an unsigned integer number
  unsigned int StateID;
  /// The StateConfidence shows the overall confidence value of the state.
  CONFDATATYPE StateConfidence;
  /// The VariableStateCondition shows the condition of a state (stable or
  /// drifting)
  VariableStateCondition VariableStateCondition;
  /// The StateIsValid 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 StateIsValid;
  /// The StateJustGotValid shows whether a state got valid (toggled from
  /// invalid to valid) during the current inserted sample.
  bool StateJustGotValid;
  /// The StateIsValidAfterReentrance shows whether a state is valid after the
  /// variable changed back to it again.
  bool StateIsValidAfterReentrance;
};

// @Benedikt: now there are 4 datatypes. Do you think we can merge PROCDATATYPE
// and PROCDATATYPE somehow?
/// \tparam INDATATYPE type of input data, \tparam CONFDATATYPE type of
/// data in that the confidence values are given, \param PROCDATATYPEtype of
/// the relative distance and the type of data in which DABs are saved.
template <typename INDATATYPE, typename CONFDATATYPE, typename PROCDATATYPE>
class State : 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<CONFDATATYPE>::value),
                "DAB storage type is not to arithmetic");

private:
  // For the convinience to write a shorter data type name
  using PartFuncPointer =
      std::shared_ptr<PartialFunction<INDATATYPE, CONFDATATYPE>>;
  using StepFuncPointer =
      std::shared_ptr<StepFunction<INDATATYPE, CONFDATATYPE>>;
  using StateInfoPtr = std::shared_ptr<StateInformation<CONFDATATYPE>>;

  /// StateInfo is a struct StateInformation that contains information about the
  /// current state.
  StateInfoPtr StateInfo;

  /// 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 FuzzyFunctionSampleMismatches is the fuzzy function that gives the
  /// confidence how bad the new sample matches another sample in the sample
  /// history.
  PartFuncPointer FuzzyFunctionSampleMismatches;

  /// The FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history match the new sample.
  StepFuncPointer FuzzyFunctionNumOfSamplesMatches;

  /// The FuzzyFunctionNumOfSamplesMismatches 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 (resp. the state of a signal)
  /// is drifting.
  PartFuncPointer FuzzyFunctionSignalIsDrifting;

  /// The FuzzyFunctionSignalIsStable is the fuzzy function that gives the
  /// confidence how likely it is that the signal (resp. the state of a signal)
  /// is stable (not drifting).
  PartFuncPointer FuzzyFunctionSignalIsStable;

  /// SampleHistory is a history in that the last sample values are stored.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::FIFO> SampleHistory;
  /// DAB is a (usually) small history of the last sample values of which a
  /// average is calculated if the DAB is full.
  DynamicLengthHistory<INDATATYPE, HistoryPolicy::SRWF> DAB;
  /// DABHistory is a history in that the last DABs (to be exact, the averages
  /// of the last DABs) are stored.
  DynamicLengthHistory<PROCDATATYPE, HistoryPolicy::LIFO> DABHistory;

  /// The StateIsValid 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 StateIsValid;
  /// The StateIsValidAfterReentrance shows whether a state is valid after the
  /// variable changed back to it again.
  bool StateIsValidAfterReentrance;

public:
  // @Maxi doxygen per default doesn't display private attributes of a class. So
  // I copied them to the constructor. So the user has more information.
  /// Creates an instance by setting all parameters
  /// \param StateID The Id of the Stateinfo \c StateInformation .
  ///
  /// \param FuzzyFunctionSampleMatches The FuzzyFunctionSampleMatches is the
  /// fuzzy function that gives the confidence how good the new sample matches
  /// another sample in the sample history.
  ///
  /// \param FuzzyFunctionSampleMismatches The FuzzyFunctionSampleMismatches is
  /// the fuzzy function that gives the confidence how bad the new sample
  /// matches another sample in the sample history.
  ///
  /// \param FuzzyFunctionNumOfSamplesMatches The
  /// FuzzyFunctionNumOfSamplesMatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history match the new sample.
  ///
  /// \param FuzzyFunctionNumOfSamplesMismatches The
  /// FuzzyFunctionNumOfSamplesMismatches is the fuzzy function that gives the
  /// confidence how many samples from the sampe history mismatch the new
  /// sample.
  ///
  /// \param FuzzyFunctionSignalIsDrifting The FuzzyFunctionSignalIsDrifting is
  /// the fuzzy function that gives the confidence how likely it is that the
  /// signal (resp. the state of a signal) is drifting.
  ///
  /// \param FuzzyFunctionSignalIsStable The FuzzyFunctionSignalIsStable is the
  /// fuzzy function that gives the confidence how likely it is that the signal
  /// (resp. the state of a signal) is stable (not drifting).
  ///
  /// \param SampleHistorySize Size of the Sample History \c
  /// DynamicLengthHistory . SampleHistory is a history in that the last sample
  /// values are stored.
  ///
  /// \param DABSize Size of DAB \c DynamicLengthHistory . DAB is a (usually)
  /// small history of the last sample values of which a average is calculated
  /// if the DAB is full.
  ///
  /// \param DABHistorySize Size of the DABHistory \c DynamicLengthHistory .
  /// DABHistory is a history in that the last DABs (to be exact, the averages
  /// of the last DABs) are stored.
  ///
  State(unsigned int StateID, PartFuncPointer FuzzyFunctionSampleMatches,
        PartFuncPointer FuzzyFunctionSampleMismatches,
        StepFuncPointer FuzzyFunctionNumOfSamplesMatches,
        StepFuncPointer FuzzyFunctionNumOfSamplesMismatches,
        PartFuncPointer FuzzyFunctionSignalIsDrifting,
        PartFuncPointer FuzzyFunctionSignalIsStable,
        unsigned int SampleHistorySize, unsigned int DABSize,
        unsigned int DABHistorySize) noexcept
      : StateInfo(StateID, 0, VariableStateCondition::UNKNOWN, false, false),
        SampleHistory(SampleHistorySize), DAB(DABSize),
        DABHistory(DABHistorySize),
        FuzzyFunctionSampleMatches(FuzzyFunctionSampleMatches),
        FuzzyFunctionSampleMismatches(FuzzyFunctionSampleMismatches),
        FuzzyFunctionNumOfSamplesMatches(FuzzyFunctionNumOfSamplesMatches),
        FuzzyFunctionNumOfSamplesMismatches(
            FuzzyFunctionNumOfSamplesMismatches),
        FuzzyFunctionSignalIsDrifting(FuzzyFunctionSignalIsDrifting),
        FuzzyFunctionSignalIsStable(FuzzyFunctionSignalIsStable) {}

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

  void leaveState(void) {
    DAB.clear();
    StateIsValidAfterReentrance = false;
  }

  void insertSample(INDATATYPE Sample) {
    SampleHistory.addEntry(Sample);

    DAB.addEntry(Sample);
    if (DAB.full()) {
      PROCDATATYPE AvgOfDAB = DAB.template average<PROCDATATYPE>();
      DABHistory.addEntry(AvgOfDAB);
      DAB.clear();
    }

    FuzzyFunctionNumOfSamplesMatches->setRightLimit(
        SampleHistory->numberOfEntries());
    FuzzyFunctionNumOfSamplesMismatches->setRightLimit(
        SampleHistory->numberOfEntries());

    // TODO: calculate whether state is valid and properly set StateIsValid,
    // StateJustGotValid, StateIsValidAfterReentrance

    // TODO: check actual state whether it drifts

    // TODO: write in StateInfo
  }

  /// Gives the confidence how likely the new sample matches the state.
  ///
  /// \param Sample is the actual sample of the observed signal.
  ///
  /// \return the confidence of the new sample is matching the state.
  CONFDATATYPE
  confSampleMatchesState(INDATATYPE Sample) {

    CONFDATATYPE ConfidenceOfBestCase = 0;

    // TODO: history
    std::vector<PROCDATATYPE> RelativeDistanceHistory;

    // calculate distances to all history samples
    for (auto &HistorySample : SampleHistory) {
      PROCDATATYPE RelativeDistance = relativeDistance(Sample, HistorySample);
      RelativeDistanceHistory.push_back(RelativeDistance);
    }

    // sort all calculated distances so that the lowest distance (will get the
    // highest confidence) is at the beginning.
    sort(RelativeDistanceHistory.begin(), RelativeDistanceHistory.end());

    CONFDATATYPE ConfidenceOfWorstFittingSample = 1;
    unsigned int Case = 1;

    // Case 1 means that one (the best fitting) sample of the history is
    // compared with the new sample. Case 2 means the two best history samples
    // are compared with the new sample. And so on.
    // TODO (future): to accelerate -> don't start with 1 start with some higher
    // number because a low number (i guess lower than 5) will definetely lead
    // to a low confidence. except the history is not full.

    // TODO: history
    // TODO: iterate thorugh case and use []
    // TODO:
    for (auto RelativeDistance = RelativeDistanceHistory.cbegin();
         RelativeDistance != RelativeDistanceHistory.cend();
         RelativeDistance++, Case++) {

      CONFDATATYPE ConfidenceFromRelativeDistance;

      // CHECK if I should use * for RelativeDistance
      if (std::isinf(RelativeDistance)) {
        // TODO (future) if fuzzy is defined in a way that infinity is not 0 it
        // would be a problem
        //@benedikt: check if your partialfunctions can take infinity as
        // argument
        ConfidenceFromRelativeDistance = 0;
      } else {
        ConfidenceFromRelativeDistance =
            FuzzyFunctionSampleMatches(RelativeDistance);
      }

      ConfidenceOfWorstFittingSample = fuzzyAND(ConfidenceOfWorstFittingSample,
                                                ConfidenceFromRelativeDistance);

      // @benedikt: change old-style cast to one of these: reinterpret_cast,
      // static_cast, dynamic_cast or const_cast. Which should I use? Or should
      // the HistSampleCounter variable already be CONFDATATYPE type?
      ConfidenceOfBestCase = fuzzyOR(
          ConfidenceOfBestCase,
          fuzzyAND(ConfidenceOfWorstFittingSample,
                   FuzzyFunctionNumOfSamplesMatches((CONFDATATYPE)Case)));
    }

    return ConfidenceOfBestCase;
  }

  /// Gives the confidence how likely the new sample mismatches the state.
  ///
  /// \param Sample is the actual sample of the observed signal.
  ///
  /// \return the confidence of the new sample is mismatching the state.
  CONFDATATYPE
  confSampleMismatchesState(INDATATYPE Sample) {

    float ConfidenceOfWorstCase = 1;

    // TODO: history!
    std::vector<PROCDATATYPE> RelativeDistanceHistory;

    // calculate distances to all history samples
    for (auto &HistorySample : SampleHistory) {
      RelativeDistanceHistory.push_back(
          relativeDistance(Sample, HistorySample));
    }

    // sort all calculated distances so that the highest distance (will get the
    // lowest confidence) is at the beginning.
    sort(RelativeDistanceHistory.rbegin(), RelativeDistanceHistory.rend());

    CONFDATATYPE ConfidenceOfBestFittingSample = 0;
    unsigned int Case = 1;

    // Case 1 means that one (the worst fitting) sample of the history is
    // compared with the new sample. Case 2 means the two worst history samples
    // are compared with the new sample. And so on.
    // TODO (future): to accelerate -> don't go until end. Confidences will only
    // get higher. See comment in "CONFDATATYPE
    // confSampleMatchesState(INDATATYPE Sample)".
    for (unsigned int RelativeDistance = RelativeDistanceHistory.cbegin();
         RelativeDistance != RelativeDistanceHistory.cend();
         RelativeDistance++, Case++) {

      CONFDATATYPE ConfidenceFromRelativeDistance;

      if (std::isinf(RelativeDistance)) {
        ConfidenceFromRelativeDistance = 1;
      } else {
        ConfidenceFromRelativeDistance =
            FuzzyFunctionSampleMismatches(RelativeDistance);
      }

      ConfidenceOfBestFittingSample = fuzzyOR(ConfidenceOfBestFittingSample,
                                              ConfidenceFromRelativeDistance);

      // @benedikt: change old-style cast to one of these: reinterpret_cast,
      // static_cast, dynamic_cast or const_cast. Which should I use? Or should
      // the HistSampleCounter variable already be CONFDATATYPE type?
      ConfidenceOfWorstCase = fuzzyAND(
          ConfidenceOfWorstCase,
          fuzzyOR(ConfidenceOfBestFittingSample,
                  FuzzyFunctionNumOfSamplesMismatches((CONFDATATYPE)Case)));
    }

    return ConfidenceOfWorstCase;
  }

  /// Gives information about the current state.
  ///
  /// \return a struct StateInformation that contains information about the
  /// current state.
  StateInfoPtr stateInformation(void) { return StateInfo; }

private:
  // @David: Where should these next functions (fuzzyAND, fuzzyOR,
  // relativeDistance) moved to (I guess we will use them also somewhere else)?

  // copied from the internet and adapted
  // (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c)
  CONFDATATYPE fuzzyAND(int n_args, ...) {
    va_list ap;
    va_start(ap, n_args);
    CONFDATATYPE min = va_arg(ap, CONFDATATYPE);
    for (int i = 2; i <= n_args; i++) {
      CONFDATATYPE a = va_arg(ap, CONFDATATYPE);
      min = std::min(a, min);
    }
    va_end(ap);
    return min;
  }

  // copied from the internet
  // (https://stackoverflow.com/questions/1657883/variable-number-of-arguments-in-c)
  CONFDATATYPE fuzzyOR(int n_args, ...) {
    va_list ap;
    va_start(ap, n_args);
    CONFDATATYPE max = va_arg(ap, CONFDATATYPE);
    for (int i = 2; i <= n_args; i++) {
      CONFDATATYPE a = va_arg(ap, CONFDATATYPE);
      std::max(a, max);
    }
    va_end(ap);
    return max;
  }

  PROCDATATYPE relativeDistance(INDATATYPE SampleValue,
                                INDATATYPE HistoryValue) {
    PROCDATATYPE Dist = HistoryValue - SampleValue;

    if (Dist == 0) {
      return 0;
    } else {
      Dist = Dist / SampleValue;
      if (Dist < 0) {
        //@benedikt: I guess this multiplication here should not be done because
        // it could be that the distance fuzzy functions are not symetrical
        //(negative and positive side)
        Dist = Dist * (-1);
      }
      return (Dist);
    }
  }
};

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

#endif // ROSA_AGENT_STATE_HPP
