/*******************************************************************************
 *
 * File:     agent-modules.cpp
 *
 * Contents: A simple example on how to define Agents with Modules.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#include "rosa/agent/Abstraction.hpp"
#include "rosa/agent/Confidence.hpp"

#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>
#include <vector>

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

// A dummy wrapper for testing MessagingSystem.
// NOTE: Since we test MessagingSystem directly here, we need to get access to
// its protected members. That we do by imitating to be a decent subclass of
// 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 Agent with its own state.
class MyAgent : public Agent {
public:
  using Tick = AtomConstant<atom("tick")>;

private:
  enum class Categories { Bad, Normal, Good };

  static const std::map<Categories, const char *> CategoryNames;
  History<uint8_t, 10, HistoryPolicy::FIFO> H;
  Confidence<uint8_t> C;
  RangeAbstraction<uint8_t, Categories> A;

public:
  void handler(Tick, uint8_t V) noexcept {
    // Record V to the History, then print state info.
    H << V;
    ASSERT(H.entry() == V); // Sanity check.
    LOG_INFO_STREAM << std::endl
                    << "Next value: " << PRINTABLE(V)
                    << ", confidence: " << C(H)
                    << ", category: " << CategoryNames.at(A(H.entry()))
                    << std::endl;
  }

  MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name,
          MessagingSystem &S)
      : Agent(Kind, Id, Name, S, THISMEMBER(handler)), H(), C(5, 20, 1),
        A({{{10, 14}, Categories::Normal},
           {{15, 17}, Categories::Good},
           {{18, 19}, Categories::Normal}},
          Categories::Bad) {}
};

const std::map<MyAgent::Categories, const char *> MyAgent::CategoryNames{
    {Categories::Bad, "Bad"},
    {Categories::Normal, "Normal"},
    {Categories::Good, "Good"}};

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

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

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

  std::vector<uint8_t> Vs{4,  5,  6,  7,  9,  10, 11, 13,
                          15, 14, 15, 16, 19, 20, 21};
  for (auto I = Vs.begin(); I != Vs.end(); ++I) {
    A.send<MyAgent::Tick, uint8_t>(MyAgent::Tick::Value, *I);
  }

  SystemTester::destroyMyAgent(SP, A);

  return 0;
}

