//===-- examples/deluxe-interface/deluxe-interface.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/deluxe-interface/deluxe-interface.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief A simple example on the \c rosa::deluxe::DeluxeContext and related
/// classes.
//===----------------------------------------------------------------------===//

#include "rosa/config/version.h"

#include "rosa/deluxe/DeluxeContext.hpp"

#include <algorithm>
#include <cmath>
#include <vector>

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

/// How many cycles of simulation to perform.
const size_t NumberOfSimulationCycles = 16;

int main(void) {
  LOG_INFO_STREAM << '\n'
                  << library_string() << " -- " << Color::Red
                  << "deluxe-interface example" << Color::Default << '\n';

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

  //
  // Create deluxe sensors.
  //
  LOG_INFO("Creating sensors.");

  // All sensors are created without defining a normal generator function, but
  // with the default value of the last argument. That, however, requires the
  // data type to be explicitly defined. This is good for simulation only.

  // The first and second sensors do not receive master-input.
  AgentHandle BoolSensor = C->createSensor<bool>("BoolSensor");
  AgentHandle IntSensor = C->createSensor<int32_t>("IntSensor");

  // This sensor receives master-input and dumps it to \c LOG_INFO_STREAM.
  const std::string FloatSensorName = "FloatSensor";
  AgentHandle FloatSensor = C->createSensor<uint32_t, float>(
      FloatSensorName, [&FloatSensorName](std::pair<uint32_t, bool> I) {
        LOG_INFO_STREAM << "\n******\n"
                        << FloatSensorName
                        << " master-input " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
      });

  // This sensor do not receive master-input but produces tuples.
  using TupleType = DeluxeTuple<float, float>;
  AgentHandle TupleSensor = C->createSensor<TupleType>("TupleSensor");

  //
  // Check and set execution policy for sensors.
  //
  LOG_INFO("Execution policies for sensors.");

  LOG_INFO(std::to_string(*C->getExecutionPolicy(IntSensor)));
  C->setExecutionPolicy(IntSensor, DeluxeExecutionPolicy::decimation(2));
  C->setExecutionPolicy(FloatSensor, DeluxeExecutionPolicy::decimation(2));
  LOG_INFO(std::to_string(*C->getExecutionPolicy(IntSensor)));

  //
  // Create low-level deluxe agents with \c createLowLevelAgent.
  //
  LOG_INFO("Creating low-level agents.");

  // All agents below dump their received values to \c LOG_INFO_STREAM on each
  // triggering.

  // This agent does not receive master-input and does not produce
  // master-output. It results in the value it received.
  const std::string BoolAgentName = "BoolAgent";
  using BoolResult = Optional<bool>;
  using BoolHandler = std::function<BoolResult(std::pair<bool, bool>)>;
  AgentHandle BoolAgent = C->createAgent(
      BoolAgentName,
      BoolHandler([&BoolAgentName](std::pair<bool, bool> I) -> BoolResult {
        LOG_INFO_STREAM << "\n******\n"
                        << BoolAgentName << " "
                        << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        return {I.first};
      }));

  // This agent receives master-input but does not produce master-output. The
  // agent maintains a state in \c IntAgentOffset. The master-input handler
  // updates \c IntAgentOffset according to each received (new) value from its
  // master. The slave-input handler results in the sum of the received value
  // and the actual value of \c IntAgentOffset.
  const std::string IntAgentName = "IntAgent";
  using IntMasterHandler = std::function<void(std::pair<uint32_t, bool>)>;
  using IntResult = Optional<int32_t>;
  using IntHandler = std::function<IntResult(std::pair<int32_t, bool>)>;
  uint32_t IntAgentOffset = 0;
  AgentHandle IntAgent = C->createAgent(
      IntAgentName,
      // Master-input handler.
      IntMasterHandler([&IntAgentName,
                        &IntAgentOffset](std::pair<uint32_t, bool> I) {
        LOG_INFO_STREAM << "\n******\n"
                        << IntAgentName
                        << " master-input " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        if (I.second) {
          IntAgentOffset = I.first;
        }
      }),
      // Slave-input handler.
      IntHandler([&IntAgentName,
                  &IntAgentOffset](std::pair<int32_t, bool> I) -> IntResult {
        LOG_INFO_STREAM << "\n******\n"
                        << IntAgentName << " " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        return {I.first + IntAgentOffset};
      }));

  // This agent receives master-input and produces master-output. The
  // master-input handler propagaates each received (new) value to its slave as
  // master-output. The slave-input handler results in the value it received and
  // produces no actual master-output.
  const std::string FloatAgentName = "FloatAgent";
  using FloatMasterResult = std::tuple<Optional<uint32_t>>;
  using FloatMasterHandler =
      std::function<FloatMasterResult(std::pair<uint32_t, bool>)>;
  using FloatResult = std::tuple<Optional<float>, Optional<uint32_t>>;
  using FloatHandler = std::function<FloatResult(std::pair<float, bool>)>;
  AgentHandle FloatAgent = C->createAgent(
      FloatAgentName,
      // Master-input handler.
      FloatMasterHandler([&FloatAgentName](
                             std::pair<uint32_t, bool> I) -> FloatMasterResult {
        LOG_INFO_STREAM << "\n******\n"
                        << FloatAgentName
                        << " master-input " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        const auto Output =
            I.second ? Optional<uint32_t>(I.first) : Optional<uint32_t>();
        return {Output};
      }),
      // Slave-input handler.
      FloatHandler([&FloatAgentName](std::pair<float, bool> I) -> FloatResult {
        LOG_INFO_STREAM << "\n******\n"
                        << FloatAgentName << " "
                        << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        return {{I.first}, {}};
      }));

  // This agent does not receive master-input and does not produce
  // master-output. It results in the sum of the values it receives in a tuple.
  const std::string TupleAgentName = "TupleAgent";
  using TupleSumResult = Optional<DeluxeTuple<double>>;
  using TupleHandler =
      std::function<TupleSumResult(std::pair<TupleType, bool>)>;
  AgentHandle TupleAgent = C->createAgent(
      TupleAgentName,
      TupleHandler(
          [&TupleAgentName](std::pair<TupleType, bool> I) -> TupleSumResult {
            LOG_INFO_STREAM << "\n******\n"
                            << TupleAgentName << " "
                            << (I.second ? "<New>" : "<Old>")
                            << " value: " << I.first << "\n******\n";
            return {std::get<0>(I.first) + std::get<1>(I.first)};
          }));

  //
  // Set execution policies for low-level agents.
  //
  LOG_INFO("Setting Execution policies for low-level agents.");

  C->setExecutionPolicy(IntAgent, DeluxeExecutionPolicy::awaitAll({0}));
  C->setExecutionPolicy(FloatAgent, DeluxeExecutionPolicy::awaitAll({0}));

  //
  // Connect sensors to low-level agents.
  //
  LOG_INFO("Connect sensors to their corresponding low-level agents.");

  C->connectSensor(BoolAgent, 0, BoolSensor, "Bool Sensor Channel");
  C->connectSensor(IntAgent, 0, IntSensor, "Int Sensor Channel");
  C->connectSensor(FloatAgent, 0, FloatSensor, "Float Sensor Channel");
  C->connectSensor(TupleAgent, 0, TupleSensor, "Tuple Sensor Channel");

  //
  // Create a high-level deluxe agent.
  //
  LOG_INFO("Create high-level agent.");

  using SingleDoubleOutputType = Optional<DeluxeTuple<double>>;
  using SingleUInt32OutputType = Optional<DeluxeTuple<uint32_t>>;
  using NoOutputType = Optional<EmptyDeluxeTuple>;

  // This agent does not receive master-input but produces master-output for its
  // slaves at positions `1` and `2` but not for that at position `0`. The agent
  // maintains a state in \c SumAgentState. The handler increments \c
  // SumAgentState upon each received (new) `true` value from its slave at
  // position `0`. Whenever \c SumAgentState has been updated, it is sent to the
  // slaves at positions `1` and `2`. The handler results in the sum of the
  // values received from slaves at positions `1`, `2`, and `3`.
  using SumResult =
      std::tuple<SingleDoubleOutputType, NoOutputType, SingleUInt32OutputType,
                 SingleUInt32OutputType, NoOutputType>;
  using SumHandler = std::function<SumResult(
      std::pair<DeluxeTuple<bool>, bool>, std::pair<DeluxeTuple<int32_t>, bool>,
      std::pair<DeluxeTuple<float>, bool>,
      std::pair<DeluxeTuple<double>, bool>)>;
  uint32_t SumAgentState = 0;
  AgentHandle SumAgent = C->createAgent(
      "Sum Agent",
      SumHandler([&SumAgentState](
                     std::pair<DeluxeTuple<bool>, bool> I0,
                     std::pair<DeluxeTuple<int32_t>, bool> I1,
                     std::pair<DeluxeTuple<float>, bool> I2,
                     std::pair<DeluxeTuple<double>, bool> I3) -> SumResult {
        const auto V0 = std::get<0>(I0.first);
        const auto V1 = std::get<0>(I1.first);
        const auto V2 = std::get<0>(I2.first);
        const auto V3 = std::get<0>(I3.first);
        LOG_INFO_STREAM << "\n*******\nSum Agent triggered with values:\n"
                        << (I0.second ? "<New>" : "<Old>")
                        << " bool value: " << V0 << "\n"
                        << (I1.second ? "<New>" : "<Old>")
                        << " int value: " << V1 << "\n"
                        << (I2.second ? "<New>" : "<Old>")
                        << " float value: " << V2 << "\n"
                        << (I3.second ? "<New>" : "<Old>")
                        << " double value: " << V3 << "\n******\n";
        if (I0.second && V0) {
          ++SumAgentState;
        }
        const SingleUInt32OutputType MasterOutput =
            I0.second && V0
                ? SingleUInt32OutputType(DeluxeTuple<uint32_t>(SumAgentState))
                : SingleUInt32OutputType();
        const DeluxeTuple<double> Output = {V1 + V2 + V3};
        return {{Output}, {}, {MasterOutput}, {MasterOutput}, {}};
      }));

  //
  // Connect low-level agents to the high-level agent.
  //
  LOG_INFO("Connect low-level agents to the high-level agent.");

  C->connectAgents(SumAgent, 0, BoolAgent, "Bool Agent Channel");
  C->connectAgents(SumAgent, 1, IntAgent, "Int Agent Channel");
  C->connectAgents(SumAgent, 2, FloatAgent, "Float Agent Channel");
  C->connectAgents(SumAgent, 3, TupleAgent, "Tuple Agent Channel");

  //
  // For simulation output, create a logger agent writing the output of the
  // high-level agent into a log stream.
  //
  LOG_INFO("Create a logger agent.");

  // The agent dumps each received (new) value to \c LOG_INFO_STREAM and
  // produces nothing; does not receive mater-input and does not produce
  // master-output.
  AgentHandle LoggerAgent =
      C->createAgent("Logger Agent",
                     std::function<Optional<unit_t>(std::pair<double, bool>)>(
                         [](std::pair<double, bool> Sum) -> Optional<unit_t> {
                           if (Sum.second) {
                             LOG_INFO_STREAM << "Result: " << Sum.first << "\n";
                           }
                           return {};
                         }));

  //
  // Connect the high-level agent to the logger agent.
  //
  LOG_INFO("Connect the high-level agent to the logger agent.");

  C->connectAgents(LoggerAgent, 0, SumAgent, "Sum Agent Channel");

  //
  // Do simulation.
  //
  LOG_INFO("Setting up and performing simulation.");

  //
  // Initialize deluxe context for simulation.
  //

  C->initializeSimulation();

  //
  // Create some vectors and register them for their corresponding sensors.
  //

  std::vector<bool> BoolValues(NumberOfSimulationCycles);
  std::generate(BoolValues.begin(), BoolValues.end(),
                [i = 0](void) mutable -> bool { return (++i % 4) == 0; });
  C->registerSensorValues(BoolSensor, BoolValues.begin(), BoolValues.end());

  std::vector<int32_t> IntValues(NumberOfSimulationCycles);
  std::generate(IntValues.begin(), IntValues.end(),
                [i = 0](void) mutable { return ++i; });
  C->registerSensorValues(IntSensor, IntValues.begin(), IntValues.end());

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

  std::vector<TupleType> TupleValues(NumberOfSimulationCycles);
  std::generate(TupleValues.begin(), TupleValues.end(),
                [f1 = 0.f, f2 = 3.14f](void) mutable -> TupleType {
                  f1 += f2;
                  f2 -= f1;
                  return {f1, f2};
                });
  C->registerSensorValues(TupleSensor, TupleValues.begin(), TupleValues.end());

  //
  // Simulate.
  //

  C->simulate(NumberOfSimulationCycles);

  return 0;
}
