#include "array.h"

#include "../core/memory.h"

typedef struct BzArrayHead {
    i32 capacity;
    i32 size;
    i32 stride;
} BzArrayHead;

#define ARRAY_HEAD(array) (((BzArrayHead *) array) -1)

void *_bzArrayCreate(i32 startCapacity, i32 stride) {
    BzArrayHead *head = bzAlloc(sizeof(*head) + startCapacity * stride);
    BZ_ASSERT(startCapacity > 0);
    head[0] = (BzArrayHead) {
        .capacity=startCapacity,
        .size=0,
        .stride=stride
    };
    return &head[1];
}
void _bzArrayDestroy(void *arr) {
    bzFree(ARRAY_HEAD(arr));
}
void _bzArrayClear(void *arr) {
    ARRAY_HEAD(arr)->size = 0;
}

i32 _bzArraySize(void *arr) {
    return ARRAY_HEAD(arr)->size;
}
i32 _bzArrayCapacity(void *arr) {
    return ARRAY_HEAD(arr)->capacity;
}

void *_bzArrayResize(void **arr, i32 newSize) {
    BzArrayHead *old = ARRAY_HEAD(*arr);
    BZ_ASSERT(newSize >= old->size);
    if (newSize == old->capacity) return *arr;

    BzArrayHead *new = bzResize(old, sizeof(*new) + newSize * old->stride);
    new[0].capacity = newSize;
    return &new[1];
}
void *_bzArrayEnsureCapacity(void **arr, i32 capacity) {
    BzArrayHead *head = ARRAY_HEAD(*arr);
    if (capacity > head->capacity)
        return _bzArrayResize(arr, head->capacity << 1);
    return *arr;
}

i32 _bzArrayGet(void *arr, i32 idx) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(idx >= 0 && idx < head->size);
    return idx;
}
void _bzArrayDelN(void *arr, i32 idx, i32 n) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(n <= head->size);
    BZ_ASSERT(idx >= 0 && idx + n <= head->size);

    bzMemMove((u8 *) arr + idx * head->stride,
              (u8 *) arr + (idx + n) * head->stride,
              n * head->stride);

    head->size -= n;
}

i32 _bzArrayPush(void *arr) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(head->size < head->capacity);
    i32 insertIdx = head->size;
    head->size++;
    return insertIdx;
}
i32 _bzArrayIns(void *arr, i32 idx) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(idx >= 0 && idx <= head->size);
    BZ_ASSERT(head->size + 1 <= head->capacity);

    if (idx != head->size) {
        bzMemMove((u8 *) arr + (idx + 1) * head->stride,
                  (u8 *) arr + idx * head->stride, head->stride);
    }
    head->size++;
    return idx;
}
i32 _bzArraySet(void *arr, i32 idx) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(idx >= 0 && idx < head->size);
    return idx;
}
i32 _bzArrayPop(void *arr) {
    BzArrayHead *head = ARRAY_HEAD(arr);
    BZ_ASSERT(head->size > 0);
    head->size--;
    return head->size;
}
