#ifndef STATEHANDLERUTILS_H
#define STATEHANDLERUTILS_H
#include "rosa/agent/Abstraction.hpp"
#include "rosa/agent/Confidence.hpp"
#include "rosa/agent/FunctionAbstractions.hpp"
#include <functional>
#include <iostream>
#include <tuple>
#include <vector>

#include "rosa/config/version.h"

#include "rosa/agent/SignalStateDetector.hpp"
#include "rosa/agent/SystemStateDetector.hpp"
#include "rosa/app/Application.hpp"

#include "rosa/support/csv/CSVReader.hpp"
#include "rosa/support/csv/CSVWriter.hpp"

#include <fstream>
#include <limits>
#include <memory>
#include <streambuf>
using namespace rosa;
using namespace rosa::agent;
using namespace rosa::app;
using namespace rosa::terminal;

// For the convinience to write a shorter data type name
using SignalStateTuple = AppTuple<float, uint32_t, uint8_t, float, float, float,
                                  float, float, float, char, uint32_t, uint8_t>;

AgentHandle createSignalStateDetectorAgent(
    std::unique_ptr<Application> &C, const std::string &Name,
    std::shared_ptr<
        SignalStateDetector<float, float, float, HistoryPolicy::FIFO>>
        SigSD) {

  using Input = std::pair<AppTuple<float>, bool>;

  using Result = Optional<SignalStateTuple>;
  using Handler = std::function<Result(Input)>;

  return C->createAgent(
      Name, Handler([&, Name, SigSD](Input I) -> Result {
        LOG_INFO_STREAM << "\n******\n"
                        << Name << " " << (I.second ? "<New>" : "<Old>")
                        << " value: "
                        << std::get<0>(
                               static_cast<std::tuple<float> &>(I.first))
                        << "\n******\n";

        auto StateInfo = SigSD->detectSignalState(
            std::get<0>(static_cast<std::tuple<float> &>(I.first)));

        if (I.second) {
          SignalStateTuple Res = {
              std::get<0>(static_cast<std::tuple<float> &>(I.first)),
              StateInfo.StateID,
              StateInfo.SignalProperty,
              StateInfo.ConfidenceOfMatchingState,
              StateInfo.ConfidenceOfMismatchingState,
              StateInfo.ConfidenceStateIsValid,
              StateInfo.ConfidenceStateIsInvalid,
              StateInfo.ConfidenceStateIsStable,
              StateInfo.ConfidenceStateIsDrifting,
              StateInfo.StateCondition,
              StateInfo.NumberOfInsertedSamplesAfterEntrance,
              static_cast<uint8_t>(
                  (StateInfo.StateIsValid ? 4 : 0) +
                  (StateInfo.StateJustGotValid ? 2 : 0) +
                  (StateInfo.StateIsValidAfterReentrance ? 1 : 0))};

          return Result(Res);
        }
        return Result();
      }));
}

// System State
using SystemStateTuple = AppTuple<std::string>;

template <std::size_t size, typename ret, typename functype, typename... A>
struct Handler_helper;

template <typename B, typename func, typename A, typename... As>
struct function_helper {
  static_assert(std::conjunction_v<std::is_same<A, As>...>,
                "All types need to be identical");

  static B function(A valA, As... valAs) {
    std::vector<A> ar({valA, valAs...});
    return func()(ar);
  }
};

template <typename ret, typename typeA, typename functype, typename... B>
struct Handler_helper<0, ret, functype, typeA, B...> {
  using handler = function_helper<ret, functype, B...>;
};

template <std::size_t size, typename ret, typename typeA, typename functype,
          typename... B>
struct Handler_helper<size, ret, functype, typeA, B...> {
  using handler =
      typename Handler_helper<size - 1, ret, functype, typeA,
                              std::pair<typeA, bool>, B...>::handler;
};

template <std::size_t size, typename ret, typename functype, typename typeA>
using Handler = typename Handler_helper<size, ret, functype, typeA>::handler;

// TODO: Change it from global to local variable if possible
std::shared_ptr<
    SystemStateDetector<uint32_t, float, float, HistoryPolicy::FIFO>>
    SysSD;

template <typename ret, typename A> struct function {
  ret operator()(A a) {
    std::vector<SignalStateInformation<float>> SignalStateInfos;
    std::stringstream OutString;

    for (auto _SignalStateTuple : a) {
      // convert tuple to info struct out.push_back({});
      OutString << std::get<0>(_SignalStateTuple.first) << ",";
      SignalStateInformation<float> Info;
      Info.StateID = std::get<1>(_SignalStateTuple.first);
      Info.SignalProperty =
          static_cast<SignalProperties>(std::get<2>(_SignalStateTuple.first));
      Info.ConfidenceOfMatchingState = std::get<3>(_SignalStateTuple.first);
      Info.ConfidenceOfMismatchingState = std::get<4>(_SignalStateTuple.first);
      Info.ConfidenceStateIsValid = std::get<5>(_SignalStateTuple.first);
      Info.ConfidenceStateIsInvalid = std::get<6>(_SignalStateTuple.first);
      Info.ConfidenceStateIsStable = std::get<7>(_SignalStateTuple.first);
      Info.ConfidenceStateIsDrifting = std::get<8>(_SignalStateTuple.first);
      Info.StateCondition =
          static_cast<StateConditions>(std::get<9>(_SignalStateTuple.first));
      Info.NumberOfInsertedSamplesAfterEntrance =
          std::get<10>(_SignalStateTuple.first);
      Info.StateIsValid = (std::get<11>(_SignalStateTuple.first) & 4) > 0;
      Info.StateJustGotValid = (std::get<11>(_SignalStateTuple.first) & 2) > 0;
      Info.StateIsValidAfterReentrance =
          (std::get<11>(_SignalStateTuple.first) & 1) > 0;
      SignalStateInfos.push_back(Info);
    }
    SystemStateInformation<float> SystemStateInfo =
        SysSD->detectSystemState(SignalStateInfos);

    OutString << SystemStateInfo.StateID << ",";
    OutString << SystemStateInfo.ConfidenceStateIsValid << ",";
    OutString << SystemStateInfo.ConfidenceStateIsInvalid << ",";
    OutString << SystemStateInfo.ConfidenceOfInputsMatchingState << ",";
    OutString << SystemStateInfo.ConfidenceOfInputsMismatchingState << ",";
    OutString << SystemStateInfo.ConfidenceOfOutputsMatchingState << ",";
    OutString << SystemStateInfo.ConfidenceOfOutputsMismatchingState << ",";
    OutString << SystemStateInfo.StateCondition << ",";
    OutString << SystemStateInfo.ConfidenceSystemIsFunctioning << ",";
    OutString << SystemStateInfo.ConfidenceSystemIsMalfunctioning << ",";
    OutString << SystemStateInfo.ConfidenceOfAllDecisions << ",";

    return ret(std::make_tuple<std::string>(OutString.str()));
  }
};

using arr = std::vector<std::pair<SignalStateTuple, bool>>;

template <size_t NumOfSlaves>
AgentHandle createSystemStateDetectorAgent(
    std::unique_ptr<Application> &C, const std::string &Name,
    std::shared_ptr<PartialFunction<uint32_t, float>> BrokenDelayFunction,
    std::shared_ptr<PartialFunction<uint32_t, float>> OkDelayFunction) {
  LOG_TRACE("Creating fixed SystemStateDetectorAgent");
  using Input = SignalStateTuple;
  using Result = Optional<SystemStateTuple>;

  std::shared_ptr<
      SystemStateDetector<uint32_t, float, float, HistoryPolicy::FIFO>>
      _SysSD(
          new SystemStateDetector<uint32_t, float, float, HistoryPolicy::FIFO>(
              std::numeric_limits<uint32_t>::max(), NumOfSlaves,
              BrokenDelayFunction, OkDelayFunction));
  SysSD = _SysSD;

  auto HandlerFunction =
      Handler<NumOfSlaves, Result, function<Optional<SystemStateTuple>, arr>,
              Input>::function;

  return C->createAgent(Name, std::function(HandlerFunction));
}

AgentHandle createSystemStateDetectorAgent(
    std::unique_ptr<Application> &C, const std::string &Name,
    size_t NumOfSlaves,
    std::shared_ptr<PartialFunction<uint32_t, float>> BrokenDelayFunction,
    std::shared_ptr<PartialFunction<uint32_t, float>> OkDelayFunction) {
  LOG_TRACE("Creating dynamic SystemStateDetectorAgent");
  switch (NumOfSlaves) {
  // clang-format off
  case  2: return createSystemStateDetectorAgent< 2>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  3: return createSystemStateDetectorAgent< 3>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  4: return createSystemStateDetectorAgent< 4>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  5: return createSystemStateDetectorAgent< 5>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  6: return createSystemStateDetectorAgent< 6>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  7: return createSystemStateDetectorAgent< 7>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  8: return createSystemStateDetectorAgent< 8>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case  9: return createSystemStateDetectorAgent< 9>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 10: return createSystemStateDetectorAgent<10>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 11: return createSystemStateDetectorAgent<11>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 12: return createSystemStateDetectorAgent<12>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 13: return createSystemStateDetectorAgent<13>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 14: return createSystemStateDetectorAgent<14>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 15: return createSystemStateDetectorAgent<15>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 16: return createSystemStateDetectorAgent<16>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 17: return createSystemStateDetectorAgent<17>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 18: return createSystemStateDetectorAgent<18>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 19: return createSystemStateDetectorAgent<19>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 20: return createSystemStateDetectorAgent<20>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 21: return createSystemStateDetectorAgent<21>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 22: return createSystemStateDetectorAgent<22>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 23: return createSystemStateDetectorAgent<23>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 24: return createSystemStateDetectorAgent<24>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 25: return createSystemStateDetectorAgent<25>(C, Name, BrokenDelayFunction, OkDelayFunction);
  case 1:
  default: return createSystemStateDetectorAgent<1>(C, Name, BrokenDelayFunction, OkDelayFunction);
    // clang-format on
  }
}
#endif // STATEHANDLERUTILS_H
