//===-- examples/deluxe-interface/deluxe-interface.cpp ----------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file examples/deluxe-interface/deluxe-interface.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \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;

/// Helper function creating a deluxe agent for logging and forwarding values.
///
/// Received values are dumped to \c LOG_INFO_STREAM and then returned as
/// result.
///
/// \tparam T type of values to handle
///
/// \param C the deluxe context to create the agent in
/// \param Name name of the new agent
///
/// \return handle for the new agent
template <typename T>
AgentHandle createLowLevelAgent(std::unique_ptr<DeluxeContext> &C,
                                const std::string &Name) {
  using handler = DeluxeAgent::D<T, T>;
  using result = Optional<T>;
  return C->createAgent(
      Name, handler([&, Name](std::pair<T, bool> I) -> result {
        LOG_INFO_STREAM << "\n******\n"
                        << Name << " " << (I.second ? "<New>" : "<Old>")
                        << " value: " << I.first << "\n******\n";
        return {I.first};
      }));
}

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 second argument. That, however, requires the
  // data type to be explicitly defined. This is good for simulation only.
  AgentHandle IntSensor = C->createSensor<int32_t>("IntSensor");
  AgentHandle FloatSensor = C->createSensor<float>("FloatSensor");

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

  AgentHandle IntAgent = createLowLevelAgent<int32_t>(C, "IntAgent");
  AgentHandle FloatAgent = createLowLevelAgent<float>(C, "FloatAgent");

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

  C->connectSensor(IntAgent, 0, IntSensor, "Int Sensor Channel");
  C->connectSensor(FloatAgent, 0, FloatSensor, "Float Sensor Channel");

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

  // The new agent logs its input values and results in the the sum of them.
  AgentHandle SumAgent = C->createAgent(
      "Sum Agent", DeluxeAgent::D<double, int32_t, float>(
                       [](std::pair<int32_t, bool> I1,
                          std::pair<float, bool> I2) -> Optional<double> {
                         LOG_INFO_STREAM
                             << "\n*******\nSum Agent triggered with values:\n"
                             << (I1.second ? "<New>" : "<Old>")
                             << " int value: " << I1.first << "\n"
                             << (I2.second ? "<New>" : "<Old>")
                             << " float value: " << I2.first << "\n******\n";
                         return {I1.first + I2.first};
                       }));

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

  C->connectAgents(SumAgent, 0, IntAgent, "Int Agent Channel");
  C->connectAgents(SumAgent, 1, FloatAgent, "Float 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 logs each new input value and produces nothing.
  AgentHandle LoggerAgent =
      C->createAgent("Logger Agent",
                     DeluxeAgent::D<unit_t, double>(
                         [](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<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());

  //
  // Simulate.
  //

  C->simulate(NumberOfSimulationCycles);

  return 0;
}
