//===-- examples/agent-functionalities/agent-functionalities.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/agent-functionalities/agent-functionalities.cpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2020
///
/// \brief A simple example on defining \c rosa::Agent instances using
/// \c rosa::agent::Functionality object as components.
///
//===----------------------------------------------------------------------===//

// Make sure M_PI is available, needed for _WIN32
#define _USE_MATH_DEFINES
#include <cmath>

#include "rosa/agent/Abstraction.hpp"
#include "rosa/agent/Confidence.hpp"
#include "rosa/agent/FunctionAbstractions.hpp"
#include "rosa/agent/RangeConfidence.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 <vector>

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

// We use pi as float rather than double, which M_PI is.
constexpr float Pi = (float) M_PI;

/// 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 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;
  StaticLengthHistory<uint8_t, 10, HistoryPolicy::FIFO> H;
  Confidence<uint8_t> C;
  RangeAbstraction<uint8_t, Categories> A;
  PartialFunction<int, int> L;
  RangeConfidence<float, Categories, float> RCL;
  RangeConfidence<float, Categories, float> RCS;

public:
  void handler(Tick, uint8_t V) noexcept {
    // Record \p V to the \c rosa::agent::History, then print state info.
    H << V;
    ASSERT(H.entry() == V); // Sanity check.
    LOG_INFO_STREAM << "\nNext value: " << PRINTABLE(V)
                    << ", confidence: " << C(H)
                    << ", category: " << CategoryNames.at(A(H.entry()))
                    << ", partial: " << PRINTABLE(L(H.entry()))
                    << ", range-confidence-linear: ";

    std::map<Categories, float> ResLin = RCL(H.entry());
    for (auto Con : ResLin) {
      LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second
                      << ",";
    }
    LOG_INFO_STREAM << " range-confidence-sine: ";
    std::map<Categories, float> ResSine = RCS(H.entry());
    for (auto Con : ResSine) {
      LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second
                      << ",";
    }
    LOG_INFO_STREAM << '\n';
  }

  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({{{(uint8_t)10, (uint8_t)14}, Categories::Normal},
           {{(uint8_t)15, (uint8_t)17}, Categories::Good},
           {{(uint8_t)18, (uint8_t)19}, Categories::Normal}},
          Categories::Bad),
        L({{{0, 2}, std::make_shared<LinearFunction<int, int>>(0, 1)},
           {{2, 4}, std::make_shared<LinearFunction<int, int>>(2, 0)},
           {{4, 6}, std::make_shared<LinearFunction<int, int>>(6, -1)}},
          0),
        RCL({
          {Categories::Bad, PartialFunction<float, float>({
            {{0.f, 3.f}, std::make_shared<LinearFunction<float, float>>
              (0.f, 1.f/3)},
            {{3.f, 6.f}, std::make_shared<LinearFunction<float, float>>
              (1.f, 0.f)},
            {{6.f, 9.f}, std::make_shared<LinearFunction<float, float>>
              (3.f, -1.f/3)},
          },0)},
          {Categories::Normal, PartialFunction<float, float>({
            {{6.f, 9.f}, std::make_shared<LinearFunction<float, float>>
              (-2.f, 1.f/3)},
            {{9.f, 12.f}, std::make_shared<LinearFunction<float, float>>
              (1.f, 0.f)},
            {{12.f, 15.f}, std::make_shared<LinearFunction<float, float>>
              (5.f, -1.f/3)},
          },0)},
          {Categories::Good, PartialFunction<float, float>({
            {{12.f, 15.f}, std::make_shared<LinearFunction<float, float>>
              (-4.f, 1.f/3)},
            {{15.f, 18.f}, std::make_shared<LinearFunction<float, float>>
              (1.f, 0.f)},
            {{18.f, 21.f}, std::make_shared<LinearFunction<float, float>>
              (7.f, -1.f/3)},
          },0)}
        }),
        RCS({
          {Categories::Bad, PartialFunction<float, float>({
            {{0.f, 3.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2, 0.5f)},
            {{3.f, 6.f}, std::make_shared<LinearFunction<float, float>>(1.f, 0.f)},
            {{6.f, 9.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)},
          },0)},
          {Categories::Normal, PartialFunction<float, float>({
            {{6.f, 9.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2, 0.5f)},
            {{9.f, 12.f}, std::make_shared<LinearFunction<float, float>>(1.f, 0.f)},
            {{12.f, 15.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)},
          },0)},
          {Categories::Good, PartialFunction<float, float>({
            {{12.f, 15.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2, 0.5f)},
            {{15.f, 18.f}, std::make_shared<LinearFunction<float, float>>(1.f, 0.f)},
            {{18.f, 21.f}, std::make_shared<SineFunction<float, float>>
              (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)},
          },0)}
        }, true){}
};

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-functionalities example" << Color::Default << '\n';

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

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

  std::vector<uint8_t> Vs{0,  1,  2,  3,  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;
}
