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

#pragma once

#include "message.hpp"
#include <cassert>
#include <cstddef>
#include <limits>
#include <tuple>
#include <type_traits>
#include <utility>

namespace rotor_light {

struct ItemQueueBase;

/** \struct ItemGuard
 *  \brief Message removal helper after message delivery
 */
struct ItemGuard {
  ItemGuard() : message{nullptr} {}

  /** records message for removal */
  ItemGuard(Message *message_, ItemQueueBase *item_queue_)
      : message{message_}, item_queue{item_queue_} {}
  ItemGuard(const ItemGuard &) = delete;
  ItemGuard(ItemGuard &&) = delete;

  /** copy-assignment operator */
  ItemGuard &operator=(const ItemGuard &) = default;

  /** returns true if the guard holds a message */
  inline operator bool() { return message; }

  /** derefers to a message */
  inline Message &operator*() { return *message; }

  /** guarded message */
  Message *message;

  /** message origin/queue */
  ItemQueueBase *item_queue;
};

/** \struct ItemQueueBase
 *  \brief base class for queues.
 *
 *  Item = Message
 */
struct ItemQueueBase {
  /** initializes queue with the message size and maximum amount of messsages */
  ItemQueueBase(size_t item_size, size_t items_count);
  ItemQueueBase(const ItemQueueBase &) = delete;
  ItemQueueBase(ItemQueueBase &&) = delete;

  /** returns the next stored message if there is one; otherwise nullptr
   *  is returned */
  Message *next();

  /** constructs in-place new message by forwarding args into
   *  message ctor.
   *
   *  returns pointer to the newly created message or nullptr
   *  if the queue is full
   */
  template <typename MessageType, typename... Args>
  MessageType *put(Args... args) {
    static_assert(std::is_base_of<Message, MessageType>::value,
                  "type should be inherited from Message");
    assert((sizeof(MessageType) <= item_size) &&
           "no storage for the message, increase ItemSize");
    if (items_size == items_count) {
      return nullptr;
    }
    auto ptr = buff_ptr + item_size * free_index;
    if (free_index == items_size - 1) {
      free_index = 0;
    } else {
      ++free_index;
    }
    auto message = new (ptr) MessageType(std::forward<Args>(args)...);
    message->type = MessageType::type_id;
    ++items_count;
    return message;
  }

  /** deletes the last message from the queue */
  void release();

protected:
  friend struct Message;
  /** records buffer pointer */
  void post_constructor(char *buff);

private:
  char *buff_ptr;
  Index item_size;
  Index items_size;
  volatile Index items_count;
  volatile Index free_index;
  volatile Index occupied_index;
};

/** \struct ItemQueue
 *  \brief convenient helper for storing fixed amount of messages
 */
template <typename Storage, size_t ItemsCount>
struct ItemQueue : ItemQueueBase {
  static_assert(Storage::item_size > 0, "queue size have to be positive");

  ItemQueue() : ItemQueueBase(Storage::item_size, ItemsCount) {
    post_constructor(reinterpret_cast<char *>(&storage));
  }

private:
  using Item = typename Storage::Item;
  Item storage[ItemsCount];
};

/** \struct QueueBase
 *  \brief base class for meta-queue (list of queues, ordered by priorities))
 */
struct QueueBase {
  /** records storage of all sub-queues and their amount */
  QueueBase(ItemQueueBase **queues, size_t queue_count);
  QueueBase(const QueueBase &) = delete;
  QueueBase(QueueBase &&) = delete;

  /** constructs in-place new message by forwarding args into
   *  message ctor.
   *
   * returns `true` if message is successfully created
   **/
  template <typename MessageType, typename... Args>
  bool put(size_t queue_index, Args... args) {
    assert((queue_index < queue_count) && "valid queue/priority");
    return queues[queue_index]->put<MessageType>(std::forward<Args>(args)...);
  }

  /** returns item-guard for the next message. Queues priorities are taken
   *  into the account */
  ItemGuard next();

private:
  ItemQueueBase **queues;
  uint8_t queue_count;
};

/** \struct Queue
 *  \brief conveninent helper for meta-queue (list of queues, ordered by
 * priorities)
 */
template <typename Storage, size_t... Counts> struct Queue : QueueBase {
  static_assert(Storage::item_size > sizeof(Message),
                "not enough storage for a message");
  /** tuple alias to store different queues */
  using Queues = std::tuple<ItemQueue<Storage, Counts>...>;

  /** the total amount of sub-queues */
  static constexpr size_t Size = std::tuple_size<Queues>::value;
  static_assert(Size > 0, "there should be at least one queue");

  Queue() : QueueBase(queues, Size) { fill_queue<0>(); }

private:
  template <size_t Index> void fill_queue() {
    auto &queue = std::get<Index>(queues_storage);
    queues[Index] = &queue;
    if constexpr (Index + 1 < Size) {
      fill_queue<Index + 1>();
    }
  }

  Queues queues_storage;
  ItemQueueBase *queues[Size];
};

} // namespace rotor_light
