/***************************************************************************//**
 *
 * \file examples/messaging-system/messaging-system.cpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief A simple example on the `rosa::MessagingSystem` and `rosa::Agent`
 * classes.
 *
 ******************************************************************************/

#include "rosa/config/version.h"

#include "rosa/core/Agent.hpp"
#include "rosa/core/MessagingSystem.hpp"

#include "rosa/support/log.h"
#include "rosa/support/terminal_colors.h"

#include <iostream>

using namespace rosa;
using namespace rosa::terminal;

/// A dummy wrapper for testing `rosa::MessagingSystem`.
///
/// \note Since we test `rosa::MessagingSystem` directly here, we need to get
/// access to its protected members. That we do by imitating to be a decent
/// subclass of `rosa::MessagingSystem`, while calling protected member
/// functions on an object of a type from which we actually don't inherit.
struct SystemTester : protected MessagingSystem {
  template <typename T, typename... Funs>
  static AgentHandle createMyAgent(MessagingSystem *S, const std::string &Name,
                                   Funs &&... Fs) {
    return ((SystemTester *)S)->createAgent<T>(Name, std::move(Fs)...);
  }

  static void destroyMyAgent(MessagingSystem *S, const AgentHandle &H) {
    ((SystemTester *)S)->destroyUnit(unwrapAgent(H));
  }
};

/// A special `rosa::Agent` subclass with its own state.
class MyAgent : public Agent {
public:
  using Tick = AtomConstant<atom("tick")>;
  using Report = AtomConstant<atom("report")>;

private:
  size_t Counter;

public:

  void handler(Tick) noexcept {
    LOG_INFO_STREAM << "MyAgent Tick count: " << ++Counter << std::endl;
  }

  MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name,
          MessagingSystem &S)
      : Agent(Kind, Id, Name, S, Invoker::F<Report>([this](Report) noexcept {
                LOG_INFO_STREAM << "MyAgent count: " << Counter << std::endl;
              }),
              THISMEMBER(handler)),
        Counter(0) {}
};

int main(void) {
  LOG_INFO_STREAM << library_string() << " -- " << Color::Red
                  << "messaging-system example" << Color::Default << std::endl;

  std::unique_ptr<MessagingSystem> S = MessagingSystem::createSystem("Sys");
  MessagingSystem *SP = S.get();

  LOG_INFO_STREAM << std::endl
                  << std::endl
                  << "** Stateless Agents" << std::endl
                  << std::endl;

  AgentHandle Agent1 = SystemTester::createMyAgent<Agent>(
      SP, "Agent1", Invoker::F<std::string>([](const std::string &M) noexcept {
        LOG_INFO("Agent1: " + M);
      }));

  using Print = AtomConstant<atom("print")>;
  using Forward = AtomConstant<atom("forward")>;
  AgentHandle Agent2 = SystemTester::createMyAgent<Agent>(
      SP, "Agent2", Invoker::F<Print, uint8_t>([](Print, uint8_t N) noexcept {
        LOG_INFO("Agent2: " + std::to_string(N));
      }),
      Invoker::F<Forward, uint8_t>([&Agent1](Forward, uint8_t N) noexcept {
        if (Agent1) {
          Agent1.send(std::to_string(N));
        } else {
          LOG_INFO("Agent2 cannot forward: Agent1 is not valid");
        }
      }));

  LOG_INFO_STREAM << std::endl
                  << "Agent1 is valid: " << bool(Agent1) << std::endl
                  << "Agent2 is valid: " << bool(Agent2) << std::endl;

  LOG_INFO("Sending a print-message to Agent2...");
  SP->send<Print, uint8_t>(Agent2, Print::Value, 42);

  LOG_INFO("Sending a forward-message to Agent2...");
  SP->send<Forward, uint8_t>(Agent2, Forward::Value, 42);

  LOG_INFO("Sending an unexpected message to Agent2...");
  SP->send(Agent2, unit);

  SystemTester::destroyMyAgent(SP, Agent1);

  LOG_INFO_STREAM << std::endl
                  << "Agent1 is valid: " << bool(Agent1) << std::endl
                  << "Agent2 is valid: " << bool(Agent2) << std::endl;

  LOG_INFO("Sending a forward-message to Agent2...");
  SP->send<Forward, uint8_t>(Agent2, Forward::Value, 42);

  SystemTester::destroyMyAgent(SP, Agent2);

  LOG_INFO_STREAM << std::endl
                  << std::endl
                  << "** Stateful Agents" << std::endl
                  << std::endl;

  AgentHandle Agent3 = SystemTester::createMyAgent<MyAgent>(SP, "Agent3");

  for (size_t I = 0; I < 2; ++I) {
    LOG_INFO("Sending report-message to Agent3...");
    Agent3.send(MyAgent::Report::Value);

    LOG_INFO("Sending tick-message to Agent3...");
    Agent3.send(MyAgent::Tick::Value);
  }

  SystemTester::destroyMyAgent(SP, Agent3);

  LOG_INFO_STREAM << std::endl << std::endl;

  return 0;
}

