/***************************************************************************************************

  Zyan Core Library (Zycore-C)

  Original Author : Florian Bernd

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.

***************************************************************************************************/

#include <Zycore/LibC.h>
#include <Zycore/Vector.h>

/* ============================================================================================== */
/* Internal macros                                                                                */
/* ============================================================================================== */

/**
 * Checks, if the passed vector should grow.
 *
 * @param   size        The desired size of the vector.
 * @param   capacity    The current capacity of the vector.
 *
 * @return  `ZYAN_TRUE`, if the vector should grow or `ZYAN_FALSE`, if not.
 */
#define ZYCORE_VECTOR_SHOULD_GROW(size, capacity) \
    ((size) > (capacity))

/**
 * Checks, if the passed vector should shrink.
 *
 * @param   size        The desired size of the vector.
 * @param   capacity    The current capacity of the vector.
 * @param   threshold   The shrink threshold.
 *
 * @return  `ZYAN_TRUE`, if the vector should shrink or `ZYAN_FALSE`, if not.
 */
#define ZYCORE_VECTOR_SHOULD_SHRINK(size, capacity, threshold) \
    (((threshold) != 0) && ((size) * (threshold) < (capacity)))

/**
 * Returns the offset of the element at the given `index`.
 *
 * @param   vector  A pointer to the `ZyanVector` instance.
 * @param   index   The element index.
 *
 * @return  The offset of the element at the given `index`.
 */
#define ZYCORE_VECTOR_OFFSET(vector, index) \
    ((void*)((ZyanU8*)(vector)->data + ((index) * (vector)->element_size)))

/* ============================================================================================== */
/* Internal functions                                                                             */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Helper functions                                                                               */
/* ---------------------------------------------------------------------------------------------- */

/**
 * Reallocates the internal buffer of the vector.
 *
 * @param   vector      A pointer to the `ZyanVector` instance.
 * @param   capacity    The new capacity.
 *
 * @return  A zyan status code.
 */
static ZyanStatus ZyanVectorReallocate(ZyanVector* vector, ZyanUSize capacity)
{
    ZYAN_ASSERT(vector);
    ZYAN_ASSERT(vector->capacity >= ZYAN_VECTOR_MIN_CAPACITY);
    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    if (!vector->allocator)
    {
        if (vector->capacity < capacity)
        {
            return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
        }
        return ZYAN_STATUS_SUCCESS;
    }

    ZYAN_ASSERT(vector->allocator);
    ZYAN_ASSERT(vector->allocator->reallocate);

    if (capacity < ZYAN_VECTOR_MIN_CAPACITY)
    {
        if (vector->capacity > ZYAN_VECTOR_MIN_CAPACITY)
        {
            capacity = ZYAN_VECTOR_MIN_CAPACITY;
        } else
        {
            return ZYAN_STATUS_SUCCESS;
        }
    }

    vector->capacity = capacity;
    ZYAN_CHECK(vector->allocator->reallocate(vector->allocator, &vector->data,
        vector->element_size, vector->capacity));

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Shifts all elements starting at the specified `index` by the amount of `count` to the left.
 *
 * @param   vector  A pointer to the `ZyanVector` instance.
 * @param   index   The start index.
 * @param   count   The amount of shift operations.
 *
 * @return  A zyan status code.
 */
static ZyanStatus ZyanVectorShiftLeft(ZyanVector* vector, ZyanUSize index, ZyanUSize count)
{
    ZYAN_ASSERT(vector);
    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);
    ZYAN_ASSERT(count > 0);
    //ZYAN_ASSERT((ZyanISize)count - (ZyanISize)index + 1 >= 0);

    const void* const source = ZYCORE_VECTOR_OFFSET(vector, index + count);
    void* const dest         = ZYCORE_VECTOR_OFFSET(vector, index);
    const ZyanUSize size     = (vector->size - index - count) * vector->element_size;
    ZYAN_MEMMOVE(dest, source, size);

    return ZYAN_STATUS_SUCCESS;
}

/**
 * Shifts all elements starting at the specified `index` by the amount of `count` to the right.
 *
 * @param   vector  A pointer to the `ZyanVector` instance.
 * @param   index   The start index.
 * @param   count   The amount of shift operations.
 *
 * @return  A zyan status code.
 */
static ZyanStatus ZyanVectorShiftRight(ZyanVector* vector, ZyanUSize index, ZyanUSize count)
{
    ZYAN_ASSERT(vector);
    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);
    ZYAN_ASSERT(count > 0);
    ZYAN_ASSERT(vector->size + count <= vector->capacity);

    const void* const source = ZYCORE_VECTOR_OFFSET(vector, index);
    void* const dest         = ZYCORE_VECTOR_OFFSET(vector, index + count);
    const ZyanUSize size     = (vector->size - index) * vector->element_size;
    ZYAN_MEMMOVE(dest, source, size);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */

/* ============================================================================================== */
/* Exported functions                                                                             */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Constructor and destructor                                                                     */
/* ---------------------------------------------------------------------------------------------- */

#ifndef ZYAN_NO_LIBC

ZyanStatus ZyanVectorInit(ZyanVector* vector, ZyanUSize element_size, ZyanUSize capacity,
    ZyanMemberProcedure destructor)
{
    return ZyanVectorInitEx(vector, element_size, capacity, destructor, ZyanAllocatorDefault(),
        ZYAN_VECTOR_DEFAULT_GROWTH_FACTOR, ZYAN_VECTOR_DEFAULT_SHRINK_THRESHOLD);
}

#endif // ZYAN_NO_LIBC

ZyanStatus ZyanVectorInitEx(ZyanVector* vector, ZyanUSize element_size, ZyanUSize capacity,
    ZyanMemberProcedure destructor, ZyanAllocator* allocator, ZyanU8 growth_factor,
    ZyanU8 shrink_threshold)
{
    if (!vector || !element_size || !allocator || (growth_factor < 1))
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(allocator->allocate);

    vector->allocator        = allocator;
    vector->growth_factor    = growth_factor;
    vector->shrink_threshold = shrink_threshold;
    vector->size             = 0;
    vector->capacity         = ZYAN_MAX(ZYAN_VECTOR_MIN_CAPACITY, capacity);
    vector->element_size     = element_size;
    vector->destructor       = destructor;
    vector->data             = ZYAN_NULL;

    return allocator->allocate(vector->allocator, &vector->data, vector->element_size,
        vector->capacity);
}

ZyanStatus ZyanVectorInitCustomBuffer(ZyanVector* vector, ZyanUSize element_size,
    void* buffer, ZyanUSize capacity, ZyanMemberProcedure destructor)
{
    if (!vector || !element_size || !buffer || !capacity)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    vector->allocator        = ZYAN_NULL;
    vector->growth_factor    = 1;
    vector->shrink_threshold = 0;
    vector->size             = 0;
    vector->capacity         = capacity;
    vector->element_size     = element_size;
    vector->destructor       = destructor;
    vector->data             = buffer;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorDestroy(ZyanVector* vector)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    if (vector->destructor)
    {
        for (ZyanUSize i = 0; i < vector->size; ++i)
        {
            vector->destructor(ZYCORE_VECTOR_OFFSET(vector, i));
        }
    }

    if (vector->allocator && vector->capacity)
    {
        ZYAN_ASSERT(vector->allocator->deallocate);
        ZYAN_CHECK(vector->allocator->deallocate(vector->allocator, vector->data,
            vector->element_size, vector->capacity));
    }

    vector->data = ZYAN_NULL;
    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Duplication                                                                                    */
/* ---------------------------------------------------------------------------------------------- */

#ifndef ZYAN_NO_LIBC

ZyanStatus ZyanVectorDuplicate(ZyanVector* destination, const ZyanVector* source,
    ZyanUSize capacity)
{
    return ZyanVectorDuplicateEx(destination, source, capacity, ZyanAllocatorDefault(),
        ZYAN_VECTOR_DEFAULT_GROWTH_FACTOR, ZYAN_VECTOR_DEFAULT_SHRINK_THRESHOLD);
}

#endif // ZYAN_NO_LIBC

ZyanStatus ZyanVectorDuplicateEx(ZyanVector* destination, const ZyanVector* source,
    ZyanUSize capacity, ZyanAllocator* allocator, ZyanU8 growth_factor, ZyanU8 shrink_threshold)
{
    if (!source)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = source->size;

    capacity = ZYAN_MAX(capacity, len);
    ZYAN_CHECK(ZyanVectorInitEx(destination, source->element_size, capacity, source->destructor,
        allocator, growth_factor, shrink_threshold));
    ZYAN_ASSERT(destination->capacity >= len);

    ZYAN_MEMCPY(destination->data, source->data, len * source->element_size);
    destination->size = len;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorDuplicateCustomBuffer(ZyanVector* destination, const ZyanVector* source,
    void* buffer, ZyanUSize capacity)
{
    if (!source)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = source->size;

    if (capacity < len)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_CHECK(ZyanVectorInitCustomBuffer(destination, source->element_size, buffer, capacity,
        source->destructor));
    ZYAN_ASSERT(destination->capacity >= len);

    ZYAN_MEMCPY(destination->data, source->data, len * source->element_size);
    destination->size = len;

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Element access                                                                                 */
/* ---------------------------------------------------------------------------------------------- */

const void* ZyanVectorGet(const ZyanVector* vector, ZyanUSize index)
{
    if (!vector || (index >= vector->size))
    {
        return ZYAN_NULL;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    return ZYCORE_VECTOR_OFFSET(vector, index);
}

void* ZyanVectorGetMutable(const ZyanVector* vector, ZyanUSize index)
{
    if (!vector || (index >= vector->size))
    {
        return ZYAN_NULL;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    return ZYCORE_VECTOR_OFFSET(vector, index);
}

ZyanStatus ZyanVectorGetPointer(const ZyanVector* vector, ZyanUSize index, const void** value)
{
    if (!vector || !value)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index >= vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    *value = (const void*)ZYCORE_VECTOR_OFFSET(vector, index);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorGetPointerMutable(const ZyanVector* vector, ZyanUSize index, void** value)
{
    if (!vector || !value)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index >= vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    *value = ZYCORE_VECTOR_OFFSET(vector, index);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorSet(ZyanVector* vector, ZyanUSize index, const void* value)
{
    if (!vector || !value)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index >= vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    void* const offset = ZYCORE_VECTOR_OFFSET(vector, index);
    if (vector->destructor)
    {
        vector->destructor(offset);
    }
    ZYAN_MEMCPY(offset, value, vector->element_size);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Insertion                                                                                      */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorPushBack(ZyanVector* vector, const void* element)
{
    if (!vector || !element)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    if (ZYCORE_VECTOR_SHOULD_GROW(vector->size + 1, vector->capacity))
    {
        ZYAN_CHECK(ZyanVectorReallocate(vector,
            ZYAN_MAX(1, (ZyanUSize)((vector->size + 1) * vector->growth_factor))));
    }

    void* const offset = ZYCORE_VECTOR_OFFSET(vector, vector->size);
    ZYAN_MEMCPY(offset, element, vector->element_size);

    ++vector->size;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorInsert(ZyanVector* vector, ZyanUSize index, const void* element)
{
    return ZyanVectorInsertRange(vector, index, element, 1);
}

ZyanStatus ZyanVectorInsertRange(ZyanVector* vector, ZyanUSize index, const void* elements,
    ZyanUSize count)
{
    if (!vector || !elements || !count)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index > vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    if (ZYCORE_VECTOR_SHOULD_GROW(vector->size + count, vector->capacity))
    {
        ZYAN_CHECK(ZyanVectorReallocate(vector,
            ZYAN_MAX(1, (ZyanUSize)((vector->size + count) * vector->growth_factor))));
    }

    if (index < vector->size)
    {
        ZYAN_CHECK(ZyanVectorShiftRight(vector, index, count));
    }

    void* const offset = ZYCORE_VECTOR_OFFSET(vector, index);
    ZYAN_MEMCPY(offset, elements, count * vector->element_size);
    vector->size += count;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorEmplace(ZyanVector* vector, void** element, ZyanMemberFunction constructor)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorEmplaceEx(vector, vector->size, element, constructor);
}

ZyanStatus ZyanVectorEmplaceEx(ZyanVector* vector, ZyanUSize index, void** element,
    ZyanMemberFunction constructor)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index > vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    if (ZYCORE_VECTOR_SHOULD_GROW(vector->size + 1, vector->capacity))
    {
        ZYAN_CHECK(ZyanVectorReallocate(vector,
            ZYAN_MAX(1, (ZyanUSize)((vector->size + 1) * vector->growth_factor))));
    }

    if (index < vector->size)
    {
        ZYAN_CHECK(ZyanVectorShiftRight(vector, index, 1));
    }

    *element = ZYCORE_VECTOR_OFFSET(vector, index);
    if (constructor)
    {
        ZYAN_CHECK(constructor(*element));
    }

    ++vector->size;

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Utils                                                                                          */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorSwapElements(ZyanVector* vector, ZyanUSize index_first, ZyanUSize index_second)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if ((index_first >= vector->size) || (index_second >= vector->size))
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (vector->size == vector->capacity)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    ZyanU64* const t = ZYCORE_VECTOR_OFFSET(vector, vector->size);
    ZyanU64* const a = ZYCORE_VECTOR_OFFSET(vector, index_first);
    ZyanU64* const b = ZYCORE_VECTOR_OFFSET(vector, index_second);
    ZYAN_MEMCPY(t, a, vector->element_size);
    ZYAN_MEMCPY(a, b, vector->element_size);
    ZYAN_MEMCPY(b, t, vector->element_size);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Deletion                                                                                       */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorDelete(ZyanVector* vector, ZyanUSize index)
{
    return ZyanVectorDeleteRange(vector, index, 1);
}

ZyanStatus ZyanVectorDeleteRange(ZyanVector* vector, ZyanUSize index, ZyanUSize count)
{
    if (!vector || !count)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (index + count > vector->size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (vector->destructor)
    {
        for (ZyanUSize i = index; i < index + count; ++i)
        {
            vector->destructor(ZYCORE_VECTOR_OFFSET(vector, i));
        }
    }

    if (index + count < vector->size)
    {
        ZYAN_CHECK(ZyanVectorShiftLeft(vector, index, count));
    }

    vector->size -= count;
    if (ZYCORE_VECTOR_SHOULD_SHRINK(vector->size, vector->capacity, vector->shrink_threshold))
    {
        return ZyanVectorReallocate(vector,
            ZYAN_MAX(1, (ZyanUSize)(vector->size * vector->growth_factor)));
    }

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorPopBack(ZyanVector* vector)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (vector->size == 0)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (vector->destructor)
    {
         vector->destructor(ZYCORE_VECTOR_OFFSET(vector, vector->size - 1));
    }

    --vector->size;
    if (ZYCORE_VECTOR_SHOULD_SHRINK(vector->size, vector->capacity, vector->shrink_threshold))
    {
        return ZyanVectorReallocate(vector,
            ZYAN_MAX(1, (ZyanUSize)(vector->size * vector->growth_factor)));
    }

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorClear(ZyanVector* vector)
{
    return ZyanVectorResizeEx(vector, 0, ZYAN_NULL);
}

/* ---------------------------------------------------------------------------------------------- */
/* Searching                                                                                      */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorFind(const ZyanVector* vector, const void* element, ZyanISize* found_index,
    ZyanEqualityComparison comparison)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorFindEx(vector, element, found_index, comparison, 0, vector->size);
}

ZyanStatus ZyanVectorFindEx(const ZyanVector* vector, const void* element, ZyanISize* found_index,
    ZyanEqualityComparison comparison, ZyanUSize index, ZyanUSize count)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if ((index + count > vector->size) || (index == vector->size))
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (!count)
    {
        *found_index = -1;
        return ZYAN_STATUS_FALSE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    for (ZyanUSize i = index; i < index + count; ++i)
    {
        if (comparison(ZYCORE_VECTOR_OFFSET(vector, i), element))
        {
            *found_index = i;
            return ZYAN_STATUS_TRUE;
        }
    }

    *found_index = -1;
    return ZYAN_STATUS_FALSE;
}

ZyanStatus ZyanVectorBinarySearch(const ZyanVector* vector, const void* element,
    ZyanUSize* found_index, ZyanComparison comparison)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorBinarySearchEx(vector, element, found_index, comparison, 0, vector->size);
}

ZyanStatus ZyanVectorBinarySearchEx(const ZyanVector* vector, const void* element,
    ZyanUSize* found_index, ZyanComparison comparison, ZyanUSize index, ZyanUSize count)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (((index >= vector->size) && (count > 0)) || (index + count > vector->size))
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (!count)
    {
        *found_index = index;
        return ZYAN_STATUS_FALSE;
    }

    ZYAN_ASSERT(vector->element_size);
    ZYAN_ASSERT(vector->data);

    ZyanStatus status = ZYAN_STATUS_FALSE;
    ZyanISize l = index;
    ZyanISize h = index + count - 1;
    while (l <= h)
    {
        const ZyanUSize mid = l + ((h - l) >> 1);
        const ZyanI32 cmp = comparison(ZYCORE_VECTOR_OFFSET(vector, mid), element);
        if (cmp < 0)
        {
            l = mid + 1;
        } else
        {
            h = mid - 1;
            if (cmp == 0)
            {
                status = ZYAN_STATUS_TRUE;
            }
        }
    }

    *found_index = l;
    return status;
}

/* ---------------------------------------------------------------------------------------------- */
/* Memory management                                                                              */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorResize(ZyanVector* vector, ZyanUSize size)
{
    return ZyanVectorResizeEx(vector, size, ZYAN_NULL);
}

ZyanStatus ZyanVectorResizeEx(ZyanVector* vector, ZyanUSize size, const void* initializer)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (size == vector->size)
    {
        return ZYAN_STATUS_SUCCESS;
    }

    if (vector->destructor && (size < vector->size))
    {
        for (ZyanUSize i = size; i < vector->size; ++i)
        {
            vector->destructor(ZYCORE_VECTOR_OFFSET(vector, i));
        }
    }

    if (ZYCORE_VECTOR_SHOULD_GROW(size, vector->capacity) ||
        ZYCORE_VECTOR_SHOULD_SHRINK(size, vector->capacity, vector->shrink_threshold))
    {
        ZYAN_ASSERT(vector->growth_factor >= 1);
        ZYAN_CHECK(ZyanVectorReallocate(vector, (ZyanUSize)(size * vector->growth_factor)));
    }

    if (initializer && (size > vector->size))
    {
        for (ZyanUSize i = vector->size; i < size; ++i)
        {
            ZYAN_MEMCPY(ZYCORE_VECTOR_OFFSET(vector, i), initializer, vector->element_size);
        }
    }

    vector->size = size;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorReserve(ZyanVector* vector, ZyanUSize capacity)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (capacity > vector->capacity)
    {
        ZYAN_CHECK(ZyanVectorReallocate(vector, capacity));
    }

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorShrinkToFit(ZyanVector* vector)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorReallocate(vector, vector->size);
}

/* ---------------------------------------------------------------------------------------------- */
/* Information                                                                                    */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanVectorGetCapacity(const ZyanVector* vector, ZyanUSize* capacity)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    *capacity = vector->capacity;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanVectorGetSize(const ZyanVector* vector, ZyanUSize* size)
{
    if (!vector)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    *size = vector->size;

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */

/* ============================================================================================== */
