#include <stdio.h>
#include <stdlib.h>

#include "vm/Arrays.h"

#define MAX_ALLOCATED_BYTES (1024LL * 1024LL * 1024LL * 4LL)

static long long allocatedBytes = 0;

void asInit(Arrays* as) {
    as->capacity = 0;
    as->usedStart = -1;
    as->freeStart = -1;
    as->data = NULL;
}

void asDelete(Arrays* as) {
    for(int i = 0; i < as->capacity; i++) {
        allocatedBytes -= as->data[i].length * as->data[i].typeSize;
        free(as->data[i].data);
    }
    allocatedBytes -= as->capacity * sizeof(Array);
    free(as->data);
}

static void aInitArray(Array* a, int previous, int next) {
    a->length = 0;
    a->typeSize = 0;
    a->next = next;
    a->previous = previous;
    a->data = NULL;
}

static void asInitArrays(Arrays* as, int start) {
    int end = as->capacity - 1;
    aInitArray(as->data + start, -1, start + 1);
    for(int i = start + 1; i < end; i++) {
        aInitArray(as->data + i, i - 1, i + 1);
    }
    aInitArray(as->data + end, end - 1, -1);
    as->freeStart = start;
}

static bool asEnsureCapacity(Arrays* as) {
    if(as->freeStart != -1) {
        return false;
    }
    int oldCapacity = as->capacity;
    int capacity = (as->data == NULL) ? 4 : as->capacity * 2;
    int oldBytes = sizeof(Array) * oldCapacity;
    int bytes = sizeof(Array) * capacity;
    if(bytes < 0 || allocatedBytes - oldBytes + bytes > MAX_ALLOCATED_BYTES) {
        return true;
    }
    allocatedBytes += bytes - oldBytes;
    as->data = realloc(as->data, bytes);
    as->capacity = capacity;
    asInitArrays(as, oldCapacity);
    return false;
}

static void asPrintDebug(Arrays* as) {
    printf("Free: %d, Used: %d\n", as->freeStart, as->usedStart);
    for(int i = 0; i < as->capacity; i++) {
        Array* a = as->data + i;
        printf("%d: %s, length: %d, next: %d, previous: %d, size: %d\n", i,
               a->data == NULL ? "null" : "valid", a->length, a->next,
               a->previous, a->typeSize);
    }
}

int asAllocate(Arrays* as, int typeSize, int length) {
    (void)asPrintDebug;
    if(asEnsureCapacity(as)) {
        return -1;
    }
    int bytes = typeSize * length;
    if(bytes < 0) {
        return -2;
    } else if(allocatedBytes + bytes > MAX_ALLOCATED_BYTES) {
        return -1;
    }
    allocatedBytes += bytes;

    int index = as->freeStart;
    Array* array = as->data + index;

    as->freeStart = array->next;
    if(array->next != -1) {
        as->data[array->next].previous = -1;
    }

    array->next = as->usedStart;
    if(as->usedStart != -1) {
        as->data[as->usedStart].previous = index;
    }
    as->usedStart = index;

    array->length = length;
    array->typeSize = typeSize;
    array->data = malloc(bytes);
    return index;
}

Array* asGet(Arrays* as, int p) {
    if(p < 0 || p >= as->capacity || as->data[p].typeSize == 0) {
        return NULL;
    }
    return as->data + p;
}

void asDeleteArray(Arrays* as, Array* a, int p) {
    if(a->previous != -1) {
        as->data[a->previous].next = a->next;
    } else {
        as->usedStart = a->next;
    }

    if(a->next != -1) {
        as->data[a->next].previous = a->previous;
    }

    if(as->freeStart != -1) {
        a->next = as->freeStart;
        as->data[as->freeStart].previous = p;
    } else {
        a->next = -1;
    }
    as->freeStart = p;

    allocatedBytes -= a->typeSize * a->length;
    a->previous = -1;
    a->length = 0;
    a->typeSize = 0;
    free(a->data);
    a->data = NULL;
}