/*******************************************************************************
 *
 * File:     messaging.cpp
 *
 * Contents: An example showcasing features related to Messages.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 ******************************************************************************/

#include "rosa/config/version.h"
#include "rosa/core/Invoker.hpp"
#include "rosa/support/log.h"
#include "rosa/support/terminal_colors.h"
#include <iostream>

using namespace rosa;
using namespace rosa::terminal;

// A simple implementation of the Message interface with two hardcoded types.
class MyMessage : public Message {
  const uint8_t U8;   // First value
  const uint16_t U16; // Second value
public:
  MyMessage(const uint8_t U8, const uint16_t U16)
      : Message(U8, U16), U8(U8), U16(U16) {}

  const void *getPointerTo(const size_t Pos) const noexcept override {
    switch (Pos) {
    default:
      ROSA_CRITICAL("wrong position");
    case 0:
      return &U8;
    case 1:
      return &U16;
    }
  }
};

class MyAtomMessage : public Message {
  const AtomValue A; // First value
public:
  template <AtomValue V>
  MyAtomMessage(AtomConstant<V> C) : Message(C), A(V) {}

  const void *getPointerTo(const size_t Pos) const noexcept override {
    switch (Pos) {
    default:
      ROSA_CRITICAL("wrong position");
    case 0:
      return &A;
    }
  }
};

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

  auto &Log = LOG_TRACE_STREAM << std::endl;

  MyMessage Msg(1, 2);
  Log << "Checking on 'MyMessage(1, 2)' that is of 'Message with "
         "TypeList<<uint8_t, uint16_t>>':"
      << std::endl
      << "  Size: " << Msg.Size << std::endl
      << "  Pos 0 is uint8_t: " << Msg.isTypeAt<uint8_t>(0) << std::endl
      << "  Pos 1 is uint16_t: " << Msg.isTypeAt<uint16_t>(1) << std::endl
      << "  Pos 2 is uint32_t: " << Msg.isTypeAt<uint32_t>(1) << std::endl
      << "  Value at pos 0: " << PRINTABLE(uint8_t)(Msg.getValueAt<uint8_t>(0))
      << std::endl
      << "  Value at pos 1: " << Msg.getValueAt<uint16_t>(1) << std::endl
      << std::endl;

  using MyMatcher = MsgMatcher<uint8_t, uint16_t>;
  Log << "Matching against 'TypeList<uint8_t, uint16_t>':" << std::endl
      << "  matching: " << MyMatcher::doesStronglyMatch(Msg) << std::endl;
  auto Vs = MyMatcher::extractedValues(Msg);
  Log << "  value: '(" << PRINTABLE(uint8_t)(std::get<0>(Vs)) << ", "
      << std::get<1>(Vs) << ")'" << std::endl
      << std::endl;

  using MyWrongMatcher = MsgMatcher<uint8_t, uint8_t>;
  Log << "Matching against 'TypeList<uint8_t, uint8_t>':" << std::endl
      << "  matching: " << MyWrongMatcher::doesStronglyMatch(Msg) << std::endl
      << std::endl;

  using MyAtom = AtomConstant<atom("atom")>;
  const MyAtom &A = MyAtom::Value;
  using MyNAtom = AtomConstant<atom("noatom")>;
  MyAtomMessage AMsg(A);
  Log << "Checking on 'MyAtomMessage(AtomConstant<atom(\"atom\")>::Value)' "
         "that is of 'Message with TypeList<AtomConstant<atom(\"atom\")>>':"
      << std::endl
      << "  Size: " << AMsg.Size << std::endl
      << "  Pos 0 is 'AtomValue': " << AMsg.isTypeAt<AtomValue>(0) << std::endl
      << "  Pos 0 is 'AtomConstant<atom(\"noatom\")>': "
      << AMsg.isTypeAt<MyNAtom>(0) << std::endl
      << std::endl;

  using MyAtomMatcher = MsgMatcher<MyAtom>;
  Log << "Matching against 'TypeList<AtomConstant<atom(\"atom\")>>':"
      << std::endl
      << "  matching: " << MyAtomMatcher::doesStronglyMatch(AMsg) << std::endl
      << "  value: '("
      << to_string(std::get<0>(MyAtomMatcher::extractedValues(AMsg))) << ")'"
      << std::endl
      << std::endl;

  using MyWrongAtomMatcher = MsgMatcher<MyNAtom>;
  Log << "Matching against 'TypeList<AtomConstant<atom(\"noatom\")>>':"
      << std::endl
      << "  matching: " << MyWrongAtomMatcher::doesStronglyMatch(AMsg)
      << std::endl
      << std::endl;

  // FIXME: Is this a compiler bug,
  // cannot use Invoker::wrap<MyAtom>([&](MyAtom)...  ???
  auto WrapFix = Invoker::wrap<MyAtom>;
  auto IP = WrapFix([&Log](MyAtom) noexcept->void {
    Log << "** Lambda-function called via Invoker." << std::endl;
  });
  auto &I = *IP; // Get a reference from the pointer.
  Log << "Invoking a function of signature 'void(MyAtom) noexcept':"
      << std::endl
      << "  with 'Message with TypeList<uint8_t, uint16_t>'" << std::endl
      << "    does Message match Invoker: " << I.match(Msg) << std::endl
      << "    invoking..." << std::endl;
  I(Msg);
  Log << "  with 'Message with TypeList<MyAtom>'..." << std::endl
      << "    does Message match Invoker: " << I.match(AMsg) << std::endl
      << "    invoking..." << std::endl;
  I(AMsg);

  return 0;
}

