#pragma once

#include "defs.h"

#include <future>

namespace dBus
{
//--------------------------------------------------------------
class Message : public std::enable_shared_from_this<Message>
{
  friend class Bus;

  public:
  Message() = default;                                     // Default constructor
  virtual ~Message();                                      // Default destructor
  Message(const Message &obj)                = delete;     // Copy constructor
  Message(Message &&obj) noexcept            = delete;     // Move constructor
  Message &operator=(const Message &obj)     = delete;     // Copy assignment operator
  Message &operator=(Message &&obj) noexcept = delete;     // Move assignment operator

  explicit Message(const HashID          &messageTypeID,     // Constructor
                   const MessageCategory &category);

  // Message management
  TimePoint                               getPostedAt() const;                       // Get the timestamp of when the message was posted
  [[nodiscard]] std::chrono::microseconds getAge() const;                            // Get the age of the message in microseconds
  [[nodiscard]] bool                      isType(const HashID &eventType) const;     // Check if the message is of a specific type

  // Trace information
  [[nodiscard]] HashID          getMessageTypeID() const;     // Get the unique identifier for the message type
  [[nodiscard]] MessageCategory getCategory() const;          // Get the log category of the message

  [[nodiscard]] virtual std::string serializeData() const;     // Serialize the message data to a string
  [[nodiscard]] virtual std::string toString() const;          // Get a string representation of the message for logging purposes
  [[nodiscard]] virtual std::string toLogLine() const;         // Get a formatted log line representation of the message for logging purposes

  template<class Derived>
  [[nodiscard]] std::shared_ptr<Derived> as();     // Template method to cast the message to a specific derived type
  template<class Derived>
  [[nodiscard]] std::shared_ptr<const Derived> as() const;     // Template method to cast the message to a specific derived type

  protected:
  HashID          m_messageTypeID = 0;                         // Unique identifier for the message type
  MessageCategory m_category      = MessageCategory::None;     // Log category of the message

  Bus      *m_bus = nullptr;     // Pointer to the bus this message is posted to (to be set by the bus when the message is posted)
  TimePoint m_postedAt;          // Timestamp of when the message was posted

  private:
  void linkToBus(Bus *bus);     // Set the message parameters when the message is posted to the bus
};
//--------------------------------------------------------------
/* Template method to cast the message to a specific derived type */
template<class Derived>
std::shared_ptr<Derived> Message::as()
{
  return std::dynamic_pointer_cast<Derived>(shared_from_this());
}
//--------------------------------------------------------------
/* Template method to cast the message to a specific derived type */
template<class Derived>
std::shared_ptr<const Derived> Message::as() const
{
  return std::dynamic_pointer_cast<const Derived>(shared_from_this());
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
template<class T>
class EventMessage : public Message
{
  public:
  T value;     // Value associated with the event message

  EventMessage()                                       = delete;      // Default constructor
  virtual ~EventMessage()                              = default;     // Default destructor
  EventMessage(const EventMessage &obj)                = delete;      // Copy constructor
  EventMessage(EventMessage &&obj) noexcept            = delete;      // Move constructor
  EventMessage &operator=(const EventMessage &obj)     = delete;      // Copy assignment operator
  EventMessage &operator=(EventMessage &&obj) noexcept = delete;      // Move assignment operator

  explicit EventMessage(const HashID          &messageTypeID,     // Constructor
                        const MessageCategory &category,
                        T                      v);

  [[nodiscard]] std::string serializeData() const override;     // Serialize the message data to a string
};

//--------------------------------------------------------------
/* Constructor */
template<class T>
EventMessage<T>::EventMessage(const HashID &messageTypeID, const MessageCategory &category, T v)
  : Message(messageTypeID, category)
  , value(std::move(v))
{
}
//--------------------------------------------------------------
/* Serialize the message data to a string */
template<class T>
std::string EventMessage<T>::serializeData() const
{
  if constexpr (std::is_arithmetic_v<T>)
    return std::to_string(value);
  return "";
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
template<class T, class U>
class RequestMessage : public Message
{
  public:
  T               value;               // Value associated with the event message
  std::promise<U> responsePromise;     // Promise to be fulfilled with the response data once the request is processed

  RequestMessage()                                         = delete;      // Default constructor
  virtual ~RequestMessage()                                = default;     // Default destructor
  RequestMessage(const RequestMessage &obj)                = delete;      // Copy constructor
  RequestMessage(RequestMessage &&obj) noexcept            = delete;      // Move constructor
  RequestMessage &operator=(const RequestMessage &obj)     = delete;      // Copy assignment operator
  RequestMessage &operator=(RequestMessage &&obj) noexcept = delete;      // Move assignment operator

  explicit RequestMessage(const HashID          &messageTypeID,     // Constructor
                          const MessageCategory &category,
                          T                      v,
                          std::shared_ptr<U>     p);

  std::future<U> getFuture();     // Get the future associated with the response promise
};
//--------------------------------------------------------------
/* Constructor */
template<class T, class U>
RequestMessage<T, U>::RequestMessage(const HashID &messageTypeID, const MessageCategory &category, T v, std::shared_ptr<U> p)
  : Message(messageTypeID, category)
  , value(std::move(v))
  , responsePromise(std::promise<U>{})
{
}
//--------------------------------------------------------------
/* Get the future associated with the response promise */
template<class T, class U>
std::future<U> RequestMessage<T, U>::getFuture()
{
  return std::move(responsePromise.get_future());
}
//--------------------------------------------------------------
}     // namespace dBus
