#pragma once

#include <stdbool.h>

#ifndef QUEUE_ASSERT
void __flight_assert(bool cond, const char *file, int line, const char *cond_string);
#define QUEUE_ASSERT(condition) __flight_assert(condition, __FILE__, __LINE__, #condition)
#endif

typedef struct QueueElementHeader
{
  bool exists;
  struct QueueElementHeader *next;
  char data[];
} QueueElementHeader;

typedef struct Queue
{
  char *data;
  size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size
  size_t element_size;
  QueueElementHeader *next;
} Queue;

#define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements)

// oldest to newest
#define QUEUE_ITER(q_ptr, cur_header) for (QueueElementHeader *cur_header = (q_ptr)->next; cur_header != NULL; cur_header = cur_header->next)
void queue_init(Queue *q, size_t element_size, char *data, size_t data_length);
void queue_clear(Queue *q);
void *queue_push_element(Queue *q);
size_t queue_num_elements(Queue *q);
void *queue_pop_element(Queue *q);
void *queue_most_recent_element(Queue *q);

#ifdef QUEUE_IMPL

void queue_init(Queue *q, size_t element_size, char *data, size_t data_length)
{
  q->data = data;
  q->data_length = data_length;
  q->element_size = element_size;
  QUEUE_ASSERT(data_length % (sizeof(QueueElementHeader) + element_size) == 0);
}

void queue_clear(Queue *q)
{
  QUEUE_ASSERT(q->data != NULL);
  for (size_t i = 0; i < q->data_length; i++)
  {
    q->data[i] = 0;
  }
  q->next = NULL;
}

#define QUEUE_ELEM_ITER(cur) for (QueueElementHeader *cur = (QueueElementHeader *)q->data; (char *)cur < q->data + q->data_length; cur = (QueueElementHeader*)((char*)cur + (sizeof(QueueElementHeader) + q->element_size)))

// you push an element, get the return value, cast it to your type, and fill it with data. It's that easy!
// if it's null the queue is out of space
void *queue_push_element(Queue *q)
{
  QUEUE_ASSERT(q->data != NULL);
  QueueElementHeader *to_return = NULL;
  QUEUE_ELEM_ITER(cur)
  {
    if (!cur->exists)
    {
      to_return = cur;
      break;
    }
  }

  // no free packet found in the buffer
  if (to_return == NULL)
  {
    return NULL;
  }
  else
  {
    to_return->exists = true;
    to_return->next = NULL; // very important.
    for (size_t i = 0; i < q->element_size; i++)
      to_return->data[i] = 0;

    // add to the end of the linked list chain
    if (q->next != NULL)
    {
      QueueElementHeader *cur = q->next;
      while (cur->next != NULL)
        cur = cur->next;
      cur->next = to_return;
    }
    else
    {
      q->next = to_return;
    }

    return (void *)to_return->data;
  }
}

size_t queue_num_elements(Queue *q)
{
  QUEUE_ASSERT(q->data != NULL);
  size_t to_return = 0;
  QUEUE_ELEM_ITER(cur)
  if (cur->exists)
    to_return++;
  return to_return;
}

// returns null if the queue is empty
void *queue_pop_element(Queue *q)
{
  QUEUE_ASSERT(q->data != NULL);
  QueueElementHeader *to_return = q->next;
  if (q->next != NULL)
    q->next = q->next->next;
  if (to_return != NULL)
    to_return->exists = false; // jank!
  return to_return == NULL ? NULL : (void *)to_return->data;
}

void *queue_most_recent_element(Queue *q)
{
  if (q->next == NULL)
    return NULL;
  else
  {
    QueueElementHeader *cur = q->next;
    while (cur->next != NULL)
      cur = cur->next;
    return (void *)cur->data;
  }
}
#undef QUEUE_ELEM_ITER
#endif
