//===-- examples/messaging-system/messaging-system.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/messaging-system/messaging-system.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2020
///
/// \brief A simple example on the \c rosa::MessagingSystem and \c 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"

using namespace rosa;
using namespace rosa::terminal;

/// A dummy wrapper for testing \c rosa::MessagingSystem.
///
/// \note Since we test \c rosa::MessagingSystem directly here, we need to get
/// access to its protected members. That we do by imitating to be a decent
/// subclass of \c 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 \c 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 {
    ++Counter;
    LOG_INFO_STREAM << "MyAgent Tick count: " << PRINTABLE(Counter) << '\n';
  }

  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: " << PRINTABLE(Counter)
                                << '\n';
              }),
              THISMEMBER(handler)),
        Counter(0) {}
};

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

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

  LOG_INFO("\n\n** Stateless Agents\n");

  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 << "\nAgent1 is valid: " << bool(Agent1)
                  << "\nAgent2 is valid: " << bool(Agent2) << '\n';

  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 << "\nAgent1 is valid: " << bool(Agent1)
                  << "\nAgent2 is valid: " << bool(Agent2) << '\n';

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

  SystemTester::destroyMyAgent(SP, Agent2);

  LOG_INFO("\n\n** Stateful Agents\n");

  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("\n");

  return 0;
}
