#ifndef TYPED_LIST_H
#define TYPED_LIST_H

#include "aliases.h"
#include "mem_arena.h"
#include <assert.h>
#include <string.h>

#define BASE_LIST_CAPACITY 1024

#define CONCAT(A, B) A##B
#define LIST_TYPE(T) CONCAT(list_, T)

#define MAKE_LIST_TYPE(T)                                                      \
  typedef struct {                                                             \
    T *items;                                                                  \
    u64 capacity;                                                              \
    u64 count;                                                                 \
  } LIST_TYPE(T)

#define list_create(T, ARENA)                                                  \
  (LIST_TYPE(T) *)_create_list(ARENA, sizeof(T), BASE_LIST_CAPACITY)
#define list_create_with_capacity(T, ARENA, CAPACITY)                          \
  (LIST_TYPE(T) *)_create_list(ARENA, sizeof(T), CAPACITY)

#define _increase_list_capacity(T, ARENA, LP, CAP)                             \
  do {                                                                         \
    u64 new_capacity = LP->capacity * 2;                                       \
    T *tmp = _alloc_for_list(ARENA, sizeof(T) * new_capacity);                 \
    assert(tmp != NULL && "Failed to increase capacity");                      \
                                                                               \
    memcpy(tmp, LP->items, sizeof(T) * LP->count);                             \
                                                                               \
    LP->capacity = new_capacity;                                               \
    LP->items = tmp;                                                           \
  } while (0)

#define list_append(T, ARENA, LP, ITEM)                                        \
  do {                                                                         \
    if (LP->count + 1 >= LP->capacity) {                                       \
      _increase_list_capacity(T, ARENA, LP, LP->capacity * 2);                 \
    }                                                                          \
                                                                               \
    LP->items[(LP->count)++] = ITEM;                                           \
  } while (0)

#define list_remove(LP, IDX)                                                   \
  do {                                                                         \
    LP->items[IDX] = LP->items[(LP->count)--];                                 \
  } while (0)
#define list_pop(LP) (LP->count -= 1)

#define list_set(LP, IDX, ITEM) LP->items[IDX] = ITEM
#define list_get(LP, IDX) LP->items[IDX]

#define list_merge(T, ARENA, DST, LP1, LP2)                                    \
  do {                                                                         \
    u64 new_count = LP1->count + LP2->count;                                   \
    u64 capacity =                                                             \
        new_count < BASE_LIST_CAPACITY ? BASE_LIST_CAPACITY : new_count * 2;   \
                                                                               \
    DST = (LIST_TYPE(T) *)_create_list(ARENA, new_count, capacity);            \
    assert(DST != NULL && "Failed to allocate new list");                      \
                                                                               \
    u64 lp1_copy_size = sizeof(T) * LP1->count;                                \
    u64 lp2_copy_size = sizeof(T) * LP2->count;                                \
    memcpy(DST->items, LP1->items, lp1_copy_size);                             \
    T *dst = &(DST->items[LP1->count]);                                        \
    memcpy(dst, LP2->items, lp2_copy_size);                                    \
    DST->capacity = capacity;                                                  \
    DST->count = new_count;                                                    \
  } while (0)

MAKE_LIST_TYPE(void);

list_void *_create_list(Arena *arena, u64 size, u64 count);
void *_alloc_for_list(Arena *arena, u64 size);

#endif // !TYPED_LIST_H
