#include "./dbl_list.h"
#include "../mem/allocator/mem_allocator.h"
#include "../../common/assert/assert.h"
#include "../../common/aliases/aliases.h"
#include "../../common/platform/platform.h"
#include <stddef.h>

wapp_intern GenericList _node_to_list(GenericNode *node, u64 item_size);
wapp_intern void _dbl_list_validate(const GenericList *list, u64 item_size);
wapp_intern void _dbl_list_node_validate(const GenericNode *node, u64 item_size);

GenericList *_dbl_list_alloc(const Allocator *allocator, u64 item_size) {
  wapp_debug_assert(allocator != NULL, "`allocator` should not be NULL");

  GenericList *list = wapp_mem_allocator_alloc(allocator, sizeof(GenericList));
  if (!list) { goto DBL_LIST_ALLOC_RETURN; }

  memset((void *)list, 0, sizeof(GenericList));
  list->magic     = WAPP_DBL_LIST_MAGIC;
  list->item_size = item_size;

DBL_LIST_ALLOC_RETURN:
  return list;
}

GenericNode *_dbl_list_node_alloc(const Allocator *allocator, u64 item_size) {
  wapp_debug_assert(allocator != NULL, "`allocator` should not be NULL");

  GenericNode *node = wapp_mem_allocator_alloc(allocator, sizeof(GenericNode));
  if (!node) { goto DBL_LIST_NODE_ALLOC_RETURN; }

  memset((void *)node, 0, sizeof(GenericNode));
  node->magic     = WAPP_DBL_NODE_MAGIC;
  node->item_size = item_size;

DBL_LIST_NODE_ALLOC_RETURN:
  return node;
}

GenericNode *_dbl_list_get(const GenericList *list, u64 index, u64 item_size) {
  wapp_debug_assert(list != NULL, "`list` should not be NULL");
  _dbl_list_validate(list, item_size);
  wapp_runtime_assert(index < list->node_count, "`index` is out of bounds");

  GenericNode *output  = NULL;
  GenericNode *current = list->first;
  for (u64 i = 1; i <= index; ++i) {
    current = current->next;
  }

  output = current;

  return output;
}

void _dbl_list_push_front(GenericList *list, GenericNode *node, u64 item_size) {
  wapp_debug_assert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
  _dbl_list_validate(list, item_size);
  _dbl_list_node_validate(node, item_size);

  GenericList node_list = _node_to_list(node, item_size);

  if (list->node_count == 0) {
    *list = node_list;
    return;
  }

  list->node_count += node_list.node_count;

  GenericNode *first = list->first;
  if (first) {
    first->prev = node_list.last;
  }

  list->first          = node_list.first;
  node_list.last->next = first;
}

void _dbl_list_push_back(GenericList *list, GenericNode *node, u64 item_size) {
  wapp_debug_assert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
  _dbl_list_validate(list, item_size);
  _dbl_list_node_validate(node, item_size);

  GenericList node_list = _node_to_list(node, item_size);

  if (list->node_count == 0) {
    *list = node_list;
    return;
  }

  list->node_count += node_list.node_count;

  GenericNode *last = list->last;
  if (last) {
    last->next = node_list.first;
  }

  list->last            = node_list.last;
  node_list.first->prev = last;
}

void _dbl_list_insert(GenericList *list, GenericNode *node, u64 index, u64 item_size) {
  wapp_debug_assert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
  _dbl_list_validate(list, item_size);
  _dbl_list_node_validate(node, item_size);

  if (index == 0) {
    _dbl_list_push_front(list, node, item_size);
    return;
  } else if (index == list->node_count) {
    _dbl_list_push_back(list, node, item_size);
    return;
  }

  GenericNode *dst_node = _dbl_list_get(list, index, item_size);
  if (!dst_node) {
    return;
  }

  GenericList node_list = _node_to_list(node, item_size);

  list->node_count += node_list.node_count;

  GenericNode *prev = dst_node->prev;

  dst_node->prev = node_list.last;
  prev->next     = node_list.first;

  node_list.first->prev = prev;
  node_list.last->next  = dst_node;
}

GenericNode *_dbl_list_pop_front(GenericList *list, u64 item_size) {
  wapp_debug_assert(list != NULL, "`list` should not be NULL");
  _dbl_list_validate(list, item_size);

  GenericNode *output = NULL;

  if (list->node_count == 0) {
    goto RETURN_I32_LIST_POP_FRONT;
  }

  output = list->first;

  if (list->node_count == 1) {
    *list = (GenericList){0};
    goto RETURN_I32_LIST_POP_FRONT;
  }

  --(list->node_count);
  list->first = output->next;

  output->prev = output->next = NULL;

RETURN_I32_LIST_POP_FRONT:
  return output;
}

GenericNode *_dbl_list_pop_back(GenericList *list, u64 item_size) {
  wapp_debug_assert(list != NULL, "`list` should not be NULL");
  _dbl_list_validate(list, item_size);

  GenericNode *output = NULL;

  if (list->node_count == 0) {
    goto RETURN_I32_LIST_POP_BACK;
  }

  output = list->last;

  if (list->node_count == 1) {
    *list = (GenericList){.magic = WAPP_DBL_LIST_MAGIC, .item_size = item_size};
    goto RETURN_I32_LIST_POP_BACK;
  }

  --(list->node_count);
  list->last = output->prev;

  output->prev = output->next = NULL;

RETURN_I32_LIST_POP_BACK:
  return output;
}

GenericNode *_dbl_list_remove(GenericList *list, u64 index, u64 item_size) {
  wapp_debug_assert(list != NULL, "`list` should not be NULL");
  _dbl_list_validate(list, item_size);

  GenericNode *output = NULL;

  if (index == 0) {
    output = _dbl_list_pop_front(list, item_size);
    goto RETURN_I32_LIST_REMOVE;
  } else if (index == list->node_count) {
    output = _dbl_list_pop_back(list, item_size);
    goto RETURN_I32_LIST_REMOVE;
  }

  output = _dbl_list_get(list, index, item_size);
  if (!output) {
    goto RETURN_I32_LIST_REMOVE;
  }

  output->prev->next = output->next;
  output->next->prev = output->prev;

  --(list->node_count);

  output->prev = output->next = NULL;

RETURN_I32_LIST_REMOVE:
  return output;
}

void _dbl_list_empty(GenericList *list, u64 item_size) {
  wapp_debug_assert(list != NULL, "`list` should not be NULL");
  _dbl_list_validate(list, item_size);

  u64 count = list->node_count;
  for (u64 i = 0; i < count; ++i) {
    _dbl_list_pop_back(list, item_size);
  }
}

wapp_intern GenericList _node_to_list(GenericNode *node, u64 item_size) {
  GenericList output = {
    .magic      = WAPP_DBL_LIST_MAGIC,
    .first      = node,
    .last       = node,
    .node_count = 1,
    .item_size  = item_size,
  };

  while (output.first->prev != NULL) {
    output.first = output.first->prev;
    ++(output.node_count);
  }

  while (output.last->next != NULL) {
    output.last = output.last->next;
    ++(output.node_count);
  }

  return output;
}

wapp_intern void _dbl_list_validate(const GenericList *list, u64 item_size) {
  wapp_runtime_assert(list->magic == WAPP_DBL_LIST_MAGIC, "`list` isn't a valid wapp list type");
  wapp_runtime_assert(list->item_size == item_size, "Invalid item provided");
}

wapp_intern void _dbl_list_node_validate(const GenericNode *node, u64 item_size) {
  wapp_runtime_assert(node->magic == WAPP_DBL_NODE_MAGIC, "`node` isn't a valid wapp node type");
  wapp_runtime_assert(node->item_size == item_size, "Invalid item provided");
}
