// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#pragma once

#include "context.hpp"
#include "definitions.hpp"
#include "messages.hpp"
#include <cassert>

namespace rotor_light {

struct SupervisorBase;
struct QueueBase;

/** \struct ActorBase
 *  \brief base interface and implementation for all actors, including
 * supervisors */
struct ActorBase {
  /// alias for generic message handler
  using GenericMethod = void (ActorBase::*)(Message &);

  /** \struct handler_traits
   *  \brief helps method signature extraction */
  template <typename T> struct handler_traits {};

  template <typename A, typename M> struct handler_traits<void (A::*)(M &)> {
    /// messsage type
    using FinalMessage = M;

    /// final actor type
    using Backend = A;
  };

  struct Handler;

  /// alias for method entry, enough to actually perfrom message delivery
  using MethodEntry = void (*)(Message &, ActorBase &, Handler &);

  /** \struct Handler
   *  \brief runtime info about message subscription handler */
  struct Handler {
    /// holds pointer-to-member-function for message processing
    GenericMethod method;

    /// holds message_id to do runtime message matching
    MessageTypeId message_id;

    /// Method entry item
    MethodEntry entry;
  };

  /** the minimum amount of handles, which should
   *  a derived actor class pre-allocate */
  static constexpr size_t min_handlers_amount = 1;

  ActorBase(const ActorBase &) = delete;
  ActorBase(ActorBase &&) = delete;

  /**
   *  Perform actor intialization, namely subscription to methods.
   *  Can ba invoked multiple times, if the actor is restarted several times.
   */
  virtual void initialize();

  /** assign actor id, supervisor and context. Performed only ONCE per
   *  program lifetime */
  virtual uint8_t bind(ActorId initial_value, SupervisorBase *supervisor,
                       Context &context);

  /** returns actor id */
  inline ActorId get_id() const { return id; }

  /** returns current state of the actor */
  inline State get_state() const { return state; }

  /** returns fail policy of the actor */
  inline FailPolicy get_fail_policy() const { return fail_policy; }

  /** set actor fail policy */
  inline void set_fail_policy(FailPolicy value) { fail_policy = value; }

  /** sends self a shutdown request message. The actor is NOT immediatly
   *  stopped. */
  void stop();

  /** adds event, which will trigger in THREAD context after the
   *  specified duration by invoking callbacke with the supplied data.
   *
   *  The templated ctx is either ctx::thread (masks/unmasks interrupts)
   *  or ctx::interrupt (does nothing).
   */
  template <typename Ctx>
  EventId add_event(Duration delta, Callback callback, void *data);

  /** cancels previously created event */
  void cancel_event(EventId event_id);

  /** \brief emplaces new message into appropriate queue
   *
   * Creates a new messsage of the specified type "in-place" of
   * the specified queue/priority; all supplied args are forwarded
   * to the message contructor
   *
   * Returns true if the message was succesfully created, and
   * false otherwise, i.e. if the queue is full.
   */
  template <typename Ctx, typename MessageType, typename... Args>
  bool send(size_t queue_index, Args... args);

  /** performs instant subscription to the specified message handler
   *
   *  This method should be invoked from `initialize()`
   */
  template <typename Method> void subscribe(Method method) {
    assert(state == State::off);
    assert(backend_idx < (int)backends_count);
    auto ptr = backends_ptr + ++backend_idx * sizeof(Handler);
    make_handler(method, *reinterpret_cast<Handler *>(ptr));
  }

protected:
  /** main actor ctor, takes pointer to backend storage and backends count.
   *
   *  Sets the fail policy to escalate by default. */
  ActorBase(char *backends, size_t backends_count);

  /** this method is invoked, when actor received "change state" message
   * from it's supervisor, i.e. to advance from "off" state to "initialized"
   * state.
   *
   * The idea to override this method in your actor class to perform some
   * hardware initialization, may be asynchronously, and when it is ready
   * call the advance_init() in  the parent class to acknowledge successful
   * initialization to supervisor.
   *
   * If the initialization fails, then instead of invoking the method,
   * the actor should send `message::ChangeStateAck` with the **false** value.
   *
   */
  virtual void advance_init();

  /** this method is invoked, when supervisor is ready to operate, i.e.
   *  all other actors acknowledged successful initialialization
   *  and it is time to initiate communication.
   *
   *  Usually this is "pro-active" method, i.e. to send high-level
   *  request to let all messaging machinery work
   */
  virtual void advance_start();

  /** this method is invoked when the actor is acked to shutdown by
   *  supervisor.
   *
   *  The idea is to override the method in a derived class, shutdown
   *  hardware (possibly asynchronously), and then invoke the method
   *  of the parent class.
   *
   *  Contrary to the `advance_init` method, this method have to
   *  be invoked sooner or later.
   */
  virtual void advance_stop();

  /** actor unique id, assigned once upon `bind` method */
  ActorId id;

  /** actor mask, used for messages routing.
   *
   *  For simple actors (non-supervisors) it is equal to actor id
   *
   *  For supervisors it is includes actor ids of all owned child-actors,
   *  including masks of child-supervisors etc.
   */
  ActorId mask;

  /** current actor state */
  State state = State::off;

  /** pointer to the owner/supervisor */
  SupervisorBase *supervisor;

  /** actor's fail policy */
  FailPolicy fail_policy;

private:
  void on_state_change(message::ChangeState &);

  template <typename Method>
  void make_handler(Method &method, Handler &handler) {
    using traits = handler_traits<Method>;
    using FinalMessage = typename traits::FinalMessage;
    using Backend = typename traits::Backend;

    handler.message_id = FinalMessage::type_id;
    handler.entry = [](Message &message, ActorBase &actor, Handler &handler) {
      auto &final_message = static_cast<FinalMessage &>(message);
      auto &backend = static_cast<Backend &>(actor);
      (backend.*reinterpret_cast<Method &>(handler.method))(final_message);
    };
    handler.method = reinterpret_cast<GenericMethod &>(method);
  }

  char *backends_ptr;
  size_t backends_count;
  int backend_idx;

  friend struct SupervisorBase;
};

/** \struct Actor
 *  \brief convenient templated base class for user-defined actors*/
template <size_t HandlersCount> struct Actor : ActorBase {
  static_assert(HandlersCount >= Actor::min_handlers_amount,
                "no enough handlers");
  Actor() : ActorBase(reinterpret_cast<char *>(&backends), HandlersCount) {}

  /** storage of message handlers */
  ActorBase::Handler backends[HandlersCount];
};

} // namespace rotor_light
