// vim:fileencoding=utf-8:foldmethod=marker

#include "./array.h"
#include "../../common/assert/assert.h"
#include "../mem/allocator/mem_allocator.h"
#include "../../common/misc/misc_utils.h"
#include "../../common/aliases/aliases.h"
#include <stddef.h>

#define _offset_pointer(PTR, OFFSET) ((void *)((uptr)(PTR) + (OFFSET)))

void *_array_get(GenericArray *array, u64 index, u64 item_size) {
  wapp_runtime_assert(array != NULL, "`array` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == array->magic, "`array` is not a valid wapp array");
  wapp_runtime_assert(item_size == array->item_size, "Invalid item type provided");
  wapp_runtime_assert(index < array->count, "`index` is out of bounds");

  return _offset_pointer(array->items, array->item_size * index);
}

void _array_set(GenericArray *array, u64 index, void *value, u64 item_size) {
  void *item = _array_get(array, index, item_size);
  memcpy(item, value, array->item_size);
}

void _array_append_capped(GenericArray *array, void *value, u64 item_size) {
  wapp_runtime_assert(array != NULL, "`array` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == array->magic, "`array` is not a valid wapp array");
  wapp_runtime_assert(item_size == array->item_size, "Invalid item type provided");

  if (array->count >= array->capacity) { return; }

  u64 index = (array->count)++;
  _array_set(array, index, value, item_size);
}

void _array_extend_capped(GenericArray *dst, const GenericArray *src, u64 item_size) {
  wapp_runtime_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == dst->magic, "`dst` is not a valid wapp array");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == src->magic, "`src` is not a valid wapp array");
  wapp_runtime_assert(item_size == dst->item_size && item_size == src->item_size, "Invalid item type provided");

  u64 remaining_capacity = dst->capacity - dst->count;

  u64 copy_count = src->count < remaining_capacity ? src->count : remaining_capacity;
  void *dst_ptr = _offset_pointer(dst->items, dst->count * dst->item_size);
  memcpy(dst_ptr, src->items, copy_count * src->item_size);
  dst->count += copy_count;
}

void _array_copy_capped(GenericArray *dst, const GenericArray *src, u64 item_size) {
  wapp_runtime_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == dst->magic, "`dst` is not a valid wapp array");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == src->magic, "`src` is not a valid wapp array");
  wapp_runtime_assert(item_size == dst->item_size && item_size == src->item_size, "Invalid item type provided");

  _array_clear(dst, item_size);
  u64 copy_count = src->count < dst->capacity ? src->count : dst->capacity;
  memcpy(dst->items, src->items, copy_count * src->item_size);
  dst->count = copy_count;
}

GenericArray *_array_append_alloc(const Allocator *allocator, GenericArray *array, void *value, u64 item_size) {
  wapp_runtime_assert(allocator != NULL && array != NULL, "`allocator` and `array` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == array->magic, "`array` is not a valid wapp array");
  wapp_runtime_assert(item_size == array->item_size, "Invalid item type provided");

  GenericArray *output = array;

  if (array->count >= array->capacity) {
    u64 new_capacity = wapp_misc_utils_u64_round_up_pow2(array->capacity * 2);
    output           = (GenericArray *)_array_alloc_capacity(allocator, new_capacity, array->item_size);
    if (!output) {
      output = array;
      goto RETURN_ARRAY_APPEND_ALLOC;
    }
    _array_copy_capped(output, array, item_size);
  }

  _array_append_capped(output, value, item_size);

RETURN_ARRAY_APPEND_ALLOC:
  return output;
}

GenericArray *_array_extend_alloc(const Allocator *allocator, GenericArray *dst, const GenericArray *src, u64 item_size) {
  wapp_runtime_assert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == dst->magic, "`dst` is not a valid wapp array");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == src->magic, "`src` is not a valid wapp array");
  wapp_runtime_assert(item_size == dst->item_size && item_size == src->item_size, "Invalid item type provided");

  GenericArray *output = dst;

  u64 remaining_capacity = dst->capacity - dst->count;
  if (src->count >= remaining_capacity) {
    u64 new_capacity = wapp_misc_utils_u64_round_up_pow2(dst->capacity * 2);
    output           = (GenericArray *)_array_alloc_capacity(allocator, new_capacity, dst->item_size);
    if (!output) {
      output = dst;
      goto RETURN_ARRAY_EXTEND_ALLOC;
    }
    _array_copy_capped(output, dst, item_size);
  }

  _array_extend_capped(output, src, item_size);

RETURN_ARRAY_EXTEND_ALLOC:
  return output;
}

GenericArray *_array_copy_alloc(const Allocator *allocator, GenericArray *dst, const GenericArray *src, u64 item_size) {
  wapp_runtime_assert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == dst->magic, "`dst` is not a valid wapp array");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == src->magic, "`src` is not a valid wapp array");
  wapp_runtime_assert(item_size == dst->item_size && item_size == src->item_size, "Invalid item type provided");

  GenericArray *output = dst;

  if (src->count >= dst->capacity) {
    u64 new_capacity = wapp_misc_utils_u64_round_up_pow2(dst->capacity * 2);
    output           = (GenericArray *)_array_alloc_capacity(allocator, new_capacity, src->item_size);
    if (!output) {
      output = dst;
      goto RETURN_ARRAY_COPY_ALLOC;
    }
  }

  _array_copy_capped(output, src, item_size);

RETURN_ARRAY_COPY_ALLOC:
  return output;
}

void *_array_pop(GenericArray *array, u64 item_size) {
  wapp_runtime_assert(array != NULL, "`array` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == array->magic, "`array` is not a valid wapp array");
  wapp_runtime_assert(item_size == array->item_size, "Invalid item type provided");

  if (array->count == 0) { return NULL; }

  u64 index = array->count - 1;
  void *out = _array_get(array, index, item_size);
  --(array->count);
  return out;
}

void _array_clear(GenericArray *array, u64 item_size) {
  wapp_runtime_assert(array != NULL, "`array` should not be NULL");
  wapp_runtime_assert(WAPP_ARRAY_MAGIC == array->magic, "`array` is not a valid wapp array");
  wapp_runtime_assert(item_size == array->item_size, "Invalid item type provided");

  array->count = 0;
}

GenericArray *_array_alloc_capacity(const Allocator *allocator, u64 capacity, u64 item_size) {
  wapp_runtime_assert(allocator != NULL, "`allocator` should not be NULL");

  GenericArray *output = NULL;

  u64 allocation_size = sizeof(GenericArray) + item_size * capacity;

  output = wapp_mem_allocator_alloc(allocator, allocation_size);
  if (!output) {
    goto RETURN_ARRAY_ALLOC;
  }

  output->magic     = WAPP_ARRAY_MAGIC;
  output->count     = 0;
  output->capacity  = capacity;
  output->item_size = item_size;
  output->items     = (void *)(output + 1);

RETURN_ARRAY_ALLOC:
  return output;
}
