
#include "arc/std/vector.h"

#include "arc/std/bool.h"
#include "arc/std/errno.h"
#include <stdint.h>
#include <stdlib.h>

struct ARC_Vector {
    uint32_t currentCapacity;
    uint32_t currentSize;

    void **data;

    ARC_Vector_CompareDataFn  compareDataFn;
    ARC_Vector_DestroyDataFn *destroyDataFn;
};

//this is a private function used as the default check for removing data from a given pointer
ARC_Bool ARC_Vector_CompareDataDefaultFn(void *dataA, void *dataB){
    if(dataA == dataB){
        return ARC_True;
    }

    return ARC_False;
}

void ARC_Vector_Create(ARC_Vector **vector, ARC_Vector_CompareDataFn *compareDataFn, ARC_Vector_DestroyDataFn *destroyDataFn){
    //create the vector
    *vector = (ARC_Vector *)malloc(sizeof(ARC_Vector));

    //initialize all the values stored in the vector
    (*vector)->data            = (void **)malloc(sizeof(void *));
    (*vector)->currentCapacity = 1;
    (*vector)->currentSize     = 0;

    //set a default for compareDataFn, then override it if it is passed in through parameters
    (*vector)->compareDataFn = ARC_Vector_CompareDataDefaultFn;
    if(compareDataFn != NULL){
        (*vector)->compareDataFn = *compareDataFn;
    }

    //set NULL as a default for deleteDataFn, then copy the delete data function callback if it exists
    (*vector)->destroyDataFn = NULL;
    if(destroyDataFn != NULL){
        (*vector)->destroyDataFn = (ARC_Vector_DestroyDataFn *)malloc(sizeof(ARC_Vector_DestroyDataFn));
        *((*vector)->destroyDataFn) = *destroyDataFn;
    }
}

void ARC_Vector_Destroy(ARC_Vector *vector){
    //remove all the contents before destroying the vector
    ARC_Vector_Clear(vector);

    //free the delete data function if it exists
    if(vector->destroyDataFn){
        free(vector->destroyDataFn);
    }

    //free everything stored in the vector
    free(vector->data);

    //free the vector
    free(vector);
}

void ARC_Vector_Add(ARC_Vector *vector, void *data){
    //check to see if the current size is the same as a max uint32_t and if so it will overflow so throw an error
    if(vector->currentSize == ~((uint32_t)0)){
        arc_errno = ARC_ERRNO_OVERFLOW;
        ARC_DEBUG_LOG_ERROR("ARC_Vector_Add(vector, data), vector at max capacity tried adding another value");
        return;
    }

    //check if we are at the max of the current capacity
    if(vector->currentSize == vector->currentCapacity){
        //increase the current capacity by double
        vector->currentCapacity <<= 1;

        //if for some reason the capacity is 0, we should set it to one so we do not error on realloc
        if(vector->currentCapacity != 0){
            vector->currentCapacity++;
        }

        //resize the vectors array and copy the contents at the same time
        vector->data = (void **)realloc(vector->data, sizeof(void *) * vector->currentCapacity);
    }

    //add to the vectors array and increase its current size
    vector->data[vector->currentSize] = data;
    vector->currentSize++;
}

void ARC_Vector_Remove(ARC_Vector *vector, void *data){
    //iterate through every item to check to see if it exists
    for(uint32_t index = 0; index < vector->currentSize; index++){
        //keep the code cleaner by pulling the current index data into a temp variable
        void *dataB = vector->data[index];

        //check if the data matches, and if so remove by index
        if(vector->compareDataFn(data, dataB) == ARC_True){
            ARC_Vector_RemoveIndex(vector, index);
        }
    }
}

void ARC_Vector_RemoveIndex(ARC_Vector *vector, uint32_t index){
    //check to make sure the given index is in bounds of the vector
    if(index >= vector->currentSize){
        arc_errno = ARC_ERRNO_DATA;
        ARC_DEBUG_LOG_ERROR("ARC_Vector_Add(vector, data), vector at max capacity tried adding another value");
        return;
    }

    //call delete data to clean up item if delete data function exists
    if(vector->destroyDataFn != NULL){
        (*(vector->destroyDataFn))(vector->data[index]);
    }

    //we will be using index to iterate as we will not use it again, so we can skip the first part of the for loop
    for(; index + 1 < vector->currentSize; index++){
        //override the data from index to the end by shifting it back one
        vector->data[index] = vector->data[index + 1];
    }

    //we have removed the item so we can decrease the current size
    vector->currentSize--;

    //if the current size is half the current capacity or the current capacity is at the smallest limit, we do not need to do anything else
    if(vector->currentSize != vector->currentCapacity >> 1 || vector->currentCapacity == 1){
        return;
    }

    //half the capacity and copy it into a smaller array
    vector->currentCapacity >>= 1;
    vector->data = (void **)realloc(vector->data, sizeof(void *) * vector->currentCapacity);
}

void ARC_Vector_Clear(ARC_Vector *vector){
    //remove each item in the vector untill the vector is empty
    while(ARC_Vector_GetSize(vector) != 0){
        ARC_Vector_RemoveIndex(vector, 0);
    }
}

uint32_t ARC_Vector_GetSize(ARC_Vector *vector){
    return vector->currentSize;
}

void *ARC_Vector_Get(ARC_Vector *vector, uint32_t index){
    //check to make sure the given index is in bounds of the vector
    if(index >= vector->currentSize){
        arc_errno = ARC_ERRNO_DATA;
        ARC_DEBUG_LOG_ERROR_WITH_VARIABLES("ARC_Vector_Get(vector, %u), null value as the index was out of bounds", index);
        return NULL;
    }

    return vector->data[index];
}
