//===- examples/agent-functionalities/Reliability-functionality.cpp *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 examples/agent-functionalities/Reliability-functionality.cpp
///
/// \author Daniel Schnoell (daniel.schnoell@tuwien.ac.at )
///
/// \date 2019
///
/// \brief A simple example on defining Relianility Functionalities inside a
/// master slave context.
/// \note This is not finished. and is currently inactive
///
//===----------------------------------------------------------------------===//

#define Reliability_trace_level 5

#include "rosa/config/version.h"

#include "rosa/support/log.h"

#include "rosa/agent/CrossReliability.h"
#include "rosa/agent/RangeConfidence.hpp"
#include "rosa/agent/Reliability.h"
#include "rosa/deluxe/DeluxeContext.hpp"

#include <map>
#include <vector>

typedef double SensorValueType;
typedef long StateType;
typedef double ReliabilityType;

#include "./helper.h" // just stuff from Rel-func to increase readability

using namespace rosa::agent;
using namespace rosa;
using namespace rosa::deluxe;
using namespace rosa::terminal;

#define NumberOfSimulationCycles 10

// -------------------------------------------------------------------------------
//				Bunch of recusive templates to simplify usage
// -------------------------------------------------------------------------------

template <std::size_t, typename... types> struct conversion;

template <std::size_t size, typename... TypesA, template <typename...> class A,
          typename... TypesB, template <typename...> class B>
struct conversion<size, A<TypesA...>, B<TypesB...>> {
  using type = typename conversion<size - 1, std::tuple<TypesA...>,
                                   std::tuple<TypesB..., TypesA...>>::type;
};

template <typename... TypesA, template <typename...> class A,
          typename... TypesB, template <typename...> class B>
struct conversion<0, A<TypesA...>, B<TypesB...>> {
  using type = DeluxeTuple<TypesB...>;
};

template <std::size_t size, typename... Types>
using unrolled_data_type =
    typename conversion<size, std::tuple<Types...>, std::tuple<>>::type;

//--------------------------------------------------------------------------------
template <std::size_t max, std::size_t at> struct __convert_to_vector {
  void operator()(std::vector<ConfOrRel<StateType, ReliabilityType>> &feedback,
                  unrolled_data_type<max / 2, StateType, ReliabilityType> I) {
    __convert_to_vector<max, at - 2>()(feedback, I);
    feedback.push_back({std::get<at>(I), std::get<at + 1>(I)});
  }
};
template <std::size_t max> struct __convert_to_vector<max, 0> {
  void operator()(std::vector<ConfOrRel<StateType, ReliabilityType>> &feedback,
                  unrolled_data_type<max / 2, StateType, ReliabilityType> I) {
    feedback.push_back({std::get<0>(I), std::get<1>(I)});
  }
};

template <std::size_t number>
void convert_to_vector(
    std::vector<ConfOrRel<StateType, ReliabilityType>> &feedback,
    unrolled_data_type<number, StateType, ReliabilityType> I) {
  __convert_to_vector<number * 2, number * 2 - 2>()(feedback, I);
}
//----------------------------------------------------------------------------
// template <std::size_t size, typename... types> struct unrole_vector {};
//
// template <std::size_t size, template <typename...> v, typename... vT,
//          template <typename...> class T, typename... TT, typename... types>
// struct unrole_vector<size, v<vT...>,T<TT...>, types...> {
//  void operator()(c vec, types... A) {
//    unrole_vector<size - 1, v<vT...>, T<TT...>, auto>()(vec,
//    vec.at(size).score,
//                                                        vec.at(size).score);
//  }
//};

// ---------------------------------------------------------------------------
//					main
// ---------------------------------------------------------------------------
int main(void) {

  const std::size_t number_of_states = 3;

  std::unique_ptr<DeluxeContext> C = DeluxeContext::create("Deluxe");

  //---------------------- Sensors -------------------------------------
  //--------------------------------------------------------------------
  const std::string SensorName1 = "Sensor1";
  const std::string SensorName2 = "Sensor2";
  const std::string SensorName3 = "Sensor3";
  AgentHandle Sensor1 = C->createSensor<uint32_t, SensorValueType>(
      SensorName1, [&SensorName1](std::pair<uint32_t, bool> I) {
        LOG_INFO_STREAM << "\n******\n"
                        << SensorName1 << " sensor-input "
                        << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
      });
  AgentHandle Sensor2 = C->createSensor<uint32_t, SensorValueType>(
      SensorName2, [&SensorName2](std::pair<uint32_t, bool> I) {
        LOG_INFO_STREAM << "\n******\n"
                        << SensorName2 << " sensor-input "
                        << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
      });
  AgentHandle Sensor3 = C->createSensor<uint32_t, SensorValueType>(
      SensorName3, [&SensorName3](std::pair<uint32_t, bool> I) {
        LOG_INFO_STREAM << "\n******\n"
                        << SensorName3 << " sensor-input "
                        << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
      });

  //------------------------- lowlevel agents --------------------------------
  //--------------------------------------------------------------------------

  const std::string LowLevelAgentName1 = "LowLevelAgent1";
  const std::string LowLevelAgentName2 = "LowLevelAgent2";
  const std::string LowLevelAgentName3 = "LowLevelAgent3";

  using conf = unrolled_data_type<
      number_of_states, StateType,
      ReliabilityType>; // this is the confidence expressed as one tuple it
                        // uses the format
                        // (first.state,first.rel,second.sate...)

  using LowLevelAgentMasterResult = void; // no return
  using LowLevelReturnFromMaster = std::pair<conf, bool>;
  using FloatMasterHandler =
      std::function<LowLevelAgentMasterResult(LowLevelReturnFromMaster)>;
  using FloatResult = Optional<DeluxeTuple<StateType, ReliabilityType>>;
  using FloatHandler =
      std::function<FloatResult(std::pair<DeluxeTuple<SensorValueType>, bool>)>;

  auto lowlevel1 = create_lowlevel_func();
  auto lowlevel2 = create_lowlevel_func();
  auto lowlevel3 = create_lowlevel_func();

  AgentHandle SlaveAgent1 = C->createAgent(
      LowLevelAgentName1,
      // Master-input handler.
      FloatMasterHandler(
          [&LowLevelAgentName1,
           lowlevel1](LowLevelReturnFromMaster I) -> LowLevelAgentMasterResult {
            LOG_INFO_STREAM << "inside: " << LowLevelAgentName1 << "feedback\n";
            if (I.second) {
              std::vector<ConfOrRel<StateType, ReliabilityType>> feedback;
              convert_to_vector<number_of_states>(feedback, I.first);
              lowlevel1->feedback(feedback);
            }
          }),
      // Slave-input handler.
      FloatHandler(
          [&LowLevelAgentName1, lowlevel1](
              std::pair<DeluxeTuple<SensorValueType>, bool> I) -> FloatResult {
            LOG_INFO_STREAM
                << "\n******\n"
                << LowLevelAgentName1 << " " << (I.second ? "<New>" : "<Old>")
                << " value: " << std::get<0>(I.first) << "\n******\n";

            auto tmp = lowlevel1->operator()(std::get<0>(I.first));
            DeluxeTuple<long, double> ret(tmp.score, tmp.Likeliness);
            return {ret};
          }));

  AgentHandle SlaveAgent2 = C->createAgent(
      LowLevelAgentName2,
      // Master-input handler.
      FloatMasterHandler(
          [&LowLevelAgentName2,
           lowlevel2](LowLevelReturnFromMaster I) -> LowLevelAgentMasterResult {
            LOG_INFO_STREAM << "inside: " << LowLevelAgentName2 << "feedback\n";
            if (I.second) {
              std::vector<ConfOrRel<StateType, ReliabilityType>> feedback;
              convert_to_vector<number_of_states>(feedback, I.first);
              lowlevel2->feedback(feedback);
            }
          }),
      // Slave-input handler.
      FloatHandler(
          [&LowLevelAgentName2, lowlevel2](
              std::pair<DeluxeTuple<SensorValueType>, bool> I) -> FloatResult {
            LOG_INFO_STREAM
                << "\n******\n"
                << LowLevelAgentName2 << " " << (I.second ? "<New>" : "<Old>")
                << " value: " << std::get<0>(I.first) << "\n******\n";

            auto tmp = lowlevel2->operator()(std::get<0>(I.first));
            DeluxeTuple<long, double> ret(tmp.score, tmp.Likeliness);
            return {ret};
          }));

  AgentHandle SlaveAgent3 = C->createAgent(
      LowLevelAgentName3,
      // Master-input handler.
      FloatMasterHandler(
          [&LowLevelAgentName3,
           lowlevel3](LowLevelReturnFromMaster I) -> LowLevelAgentMasterResult {
            LOG_INFO_STREAM << "inside: " << LowLevelAgentName3 << "feedback\n";
            if (I.second) {
              std::vector<ConfOrRel<StateType, ReliabilityType>> feedback;
              convert_to_vector<number_of_states>(feedback, I.first);
              lowlevel3->feedback(feedback);
            }
          }),
      // Slave-input handler.
      FloatHandler(
          [&LowLevelAgentName3, lowlevel3](
              std::pair<DeluxeTuple<SensorValueType>, bool> I) -> FloatResult {
            LOG_INFO_STREAM
                << "\n******\n"
                << LowLevelAgentName3 << " " << (I.second ? "<New>" : "<Old>")
                << " value: " << std::get<0>(I.first) << "\n******\n";

            auto tmp = lowlevel3->operator()(std::get<0>(I.first));
            DeluxeTuple<long, double> ret(tmp.score, tmp.Likeliness);
            return {ret};
          }));

  //------------------------- high level rel ---------------------------------
  //--------------------------------------------------------------------------

  auto highlevel = create_highlevel_func();

  using conf1 = Optional<unrolled_data_type<
      number_of_states, StateType,
      ReliabilityType>>; // this is the confidence expressed as one tuple it
                         // uses the format
                         // (first.state,first.rel,second.sate...)

  using MasterResult =
      std::tuple<Optional<DeluxeTuple<ReliabilityType>>, conf1, conf1, conf1>;
  using SlaveOutputs = DeluxeTuple<StateType, ReliabilityType>;
  using MasterHandler = std::function<MasterResult(
      std::pair<SlaveOutputs, bool>, std::pair<SlaveOutputs, bool>,
      std::pair<SlaveOutputs, bool>)>;
  AgentHandle MasterAgent = C->createAgent(
      "Master Agent",
      MasterHandler([&](std::pair<SlaveOutputs, bool> I0,
                        std::pair<SlaveOutputs, bool> I1,
                        std::pair<SlaveOutputs, bool> I2) -> MasterResult {
        ReliabilityForHighLevelAgents<StateType, ReliabilityType>::InputType
            input;
        input.push_back({0, std::get<0>(I0.first), std::get<1>(I0.first)});
        input.push_back({1, std::get<0>(I1.first), std::get<1>(I1.first)});
        input.push_back({2, std::get<0>(I2.first), std::get<1>(I2.first)});
        auto out = highlevel->operator()(input);

        DeluxeTuple<ReliabilityType> rel(out.CrossLikeliness);

        conf c[3];
        for (std::size_t at = 0; at < 3; at++) {
          std::get<0>(c[at]) = out.Likeliness.at(at).at(0).score;
          std::get<1>(c[at]) = out.Likeliness.at(at).at(0).Likeliness;
          std::get<2>(c[at]) = out.Likeliness.at(at).at(1).score;
          std::get<3>(c[at]) = out.Likeliness.at(at).at(1).Likeliness;
          std::get<4>(c[at]) = out.Likeliness.at(at).at(2).score;
          std::get<5>(c[at]) = out.Likeliness.at(at).at(2).Likeliness;
        }
        return {{rel}, {c[0]}, {c[1]}, {c[2]}};
      }));
  // -------------------------------------------------------------------------
  //			Sensors and connecting
  // -------------------------------------------------------------------------

  std::vector<SensorValueType> FloatValues(NumberOfSimulationCycles);
  std::generate(FloatValues.begin(), FloatValues.end(),
                [f = 0.5f](void) mutable {
                  f += 0.3f;
                  return std::floor(f) + 0.5f;
                });
  C->registerSensorValues(Sensor1, FloatValues.begin(), FloatValues.end());
  C->registerSensorValues(Sensor2, FloatValues.begin(), FloatValues.end());
  C->registerSensorValues(Sensor3, FloatValues.begin(), FloatValues.end());

  // Connection
  C->connectSensor(SlaveAgent1, 0, Sensor1, "Sensor 1");
  C->connectSensor(SlaveAgent2, 0, Sensor2, "Sensor 2");
  C->connectSensor(SlaveAgent3, 0, Sensor3, "Sensor 3");

  C->connectAgents(MasterAgent, 0, SlaveAgent1, "Slave1");
  C->connectAgents(MasterAgent, 1, SlaveAgent2, "Slave2");
  C->connectAgents(MasterAgent, 2, SlaveAgent3, "Slave3");

  // Logger
  AgentHandle LoggerAgent = C->createAgent(
      "Logger Agent", std::function<Optional<DeluxeTuple<unit_t>>(
                          std::pair<DeluxeTuple<ReliabilityType>, bool>)>(
                          [](std::pair<DeluxeTuple<ReliabilityType>, bool> Sum)
                              -> Optional<DeluxeTuple<unit_t>> {
                            if (Sum.second) {
                              LOG_INFO_STREAM
                                  << "Result: Rel: " << std::get<0>(Sum.first)
                                  << "\n";
                            }
                            return {};
                          }));

  C->connectAgents(LoggerAgent, 0, MasterAgent, "Sum Agent Channel");
  // -------------------------------------------------------------------------------
  //			Simulate
  // -------------------------------------------------------------------------------

  C->simulate(NumberOfSimulationCycles);

  delete highlevel;
  delete lowlevel1;
  delete lowlevel2;
}
