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

  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/String.h>
#include <Zycore/LibC.h>

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

/**
 * Writes a terminating '\0' character at the end of the string data.
 */
#define ZYCORE_STRING_NULLTERMINATE(string) \
      *(char*)((ZyanU8*)(string)->vector.data + (string)->vector.size - 1) = '\0';

/**
 * Checks for a terminating '\0' character at the end of the string data.
 */
#define ZYCORE_STRING_ASSERT_NULLTERMINATION(string) \
      ZYAN_ASSERT(*(char*)((ZyanU8*)(string)->vector.data + (string)->vector.size - 1) == '\0');

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

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

#ifndef ZYAN_NO_LIBC

ZyanStatus ZyanStringInit(ZyanString* string, ZyanUSize capacity)
{
    return ZyanStringInitEx(string, capacity, ZyanAllocatorDefault(),
        ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
}

#endif // ZYAN_NO_LIBC

ZyanStatus ZyanStringInitEx(ZyanString* string, ZyanUSize capacity, ZyanAllocator* allocator,
    ZyanU8 growth_factor, ZyanU8 shrink_threshold)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    string->flags = 0;
    capacity = ZYAN_MAX(ZYAN_STRING_MIN_CAPACITY, capacity) + 1;
    ZYAN_CHECK(ZyanVectorInitEx(&string->vector, sizeof(char), capacity, ZYAN_NULL, allocator,
        growth_factor, shrink_threshold));
    ZYAN_ASSERT(string->vector.capacity >= capacity);
    // Some of the string code relies on `sizeof(char) == 1`
    ZYAN_ASSERT(string->vector.element_size == 1);

    *(char*)string->vector.data = '\0';
    ++string->vector.size;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringInitCustomBuffer(ZyanString* string, char* buffer, ZyanUSize capacity)
{
    if (!string || !capacity)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    string->flags = ZYAN_STRING_HAS_FIXED_CAPACITY;
    ZYAN_CHECK(ZyanVectorInitCustomBuffer(&string->vector, sizeof(char), (void*)buffer, capacity,
        ZYAN_NULL));
    ZYAN_ASSERT(string->vector.capacity == capacity);
    // Some of the string code relies on `sizeof(char) == 1`
    ZYAN_ASSERT(string->vector.element_size == 1);

    *(char*)string->vector.data = '\0';
    ++string->vector.size;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringDestroy(ZyanString* string)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }
    if (string->flags & ZYAN_STRING_HAS_FIXED_CAPACITY)
    {
        return ZYAN_STATUS_SUCCESS;
    }

    return ZyanVectorDestroy(&string->vector);
}

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

#ifndef ZYAN_NO_LIBC

ZyanStatus ZyanStringDuplicate(ZyanString* destination, const ZyanStringView* source,
    ZyanUSize capacity)
{
    return ZyanStringDuplicateEx(destination, source, capacity, ZyanAllocatorDefault(),
        ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
}

#endif // ZYAN_NO_LIBC

ZyanStatus ZyanStringDuplicateEx(ZyanString* destination, const ZyanStringView* source,
    ZyanUSize capacity, ZyanAllocator* allocator, ZyanU8 growth_factor, ZyanU8 shrink_threshold)
{
    if (!source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = source->string.vector.size;
    capacity = ZYAN_MAX(capacity, len - 1);
    ZYAN_CHECK(ZyanStringInitEx(destination, capacity, allocator, growth_factor, shrink_threshold));
    ZYAN_ASSERT(destination->vector.capacity >= len);

    ZYAN_MEMCPY(destination->vector.data, source->string.vector.data,
        source->string.vector.size - 1);
    destination->vector.size = len;
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringDuplicateCustomBuffer(ZyanString* destination, const ZyanStringView* source,
    char* buffer, ZyanUSize capacity)
{
    if (!source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = source->string.vector.size;
    if (capacity < len)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_CHECK(ZyanStringInitCustomBuffer(destination, buffer, capacity));
    ZYAN_ASSERT(destination->vector.capacity >= len);

    ZYAN_MEMCPY(destination->vector.data, source->string.vector.data,
        source->string.vector.size - 1);
    destination->vector.size = len;
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Concatenation                                                                                  */
/* ---------------------------------------------------------------------------------------------- */

#ifndef ZYAN_NO_LIBC

ZyanStatus ZyanStringConcat(ZyanString* destination, const ZyanStringView* s1,
    const ZyanStringView* s2, ZyanUSize capacity)
{
    return ZyanStringConcatEx(destination, s1, s2, capacity, ZyanAllocatorDefault(),
        ZYAN_STRING_DEFAULT_GROWTH_FACTOR, ZYAN_STRING_DEFAULT_SHRINK_THRESHOLD);
}

#endif // ZYAN_NO_LIBC

ZyanStatus ZyanStringConcatEx(ZyanString* destination, const ZyanStringView* s1,
    const ZyanStringView* s2, ZyanUSize capacity, ZyanAllocator* allocator, ZyanU8 growth_factor,
    ZyanU8 shrink_threshold)
{
    if (!s1 || !s2 || !s1->string.vector.size || !s2->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = s1->string.vector.size + s2->string.vector.size - 1;
    capacity = ZYAN_MAX(capacity, len - 1);
    ZYAN_CHECK(ZyanStringInitEx(destination, capacity, allocator, growth_factor, shrink_threshold));
    ZYAN_ASSERT(destination->vector.capacity >= len);

    ZYAN_MEMCPY(destination->vector.data, s1->string.vector.data, s1->string.vector.size - 1);
    ZYAN_MEMCPY((char*)destination->vector.data + s1->string.vector.size - 1,
        s2->string.vector.data, s2->string.vector.size - 1);
    destination->vector.size = len;
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringConcatCustomBuffer(ZyanString* destination, const ZyanStringView* s1,
    const ZyanStringView* s2, char* buffer, ZyanUSize capacity)
{
    if (!s1 || !s2 || !s1->string.vector.size || !s2->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = s1->string.vector.size + s2->string.vector.size - 1;
    if (capacity < len)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_CHECK(ZyanStringInitCustomBuffer(destination, buffer, capacity));
    ZYAN_ASSERT(destination->vector.capacity >= len);

    ZYAN_MEMCPY(destination->vector.data, s1->string.vector.data, s1->string.vector.size - 1);
    ZYAN_MEMCPY((char*)destination->vector.data + s1->string.vector.size - 1,
        s2->string.vector.data, s2->string.vector.size - 1);
    destination->vector.size = len;
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Views                                                                                          */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanStringViewInsideView(ZyanStringView* view, const ZyanStringView* source)
{
    if (!view || !source)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    view->string.vector.data = source->string.vector.data;
    view->string.vector.size = source->string.vector.size;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringViewInsideViewEx(ZyanStringView* view, const ZyanStringView* source,
    ZyanUSize index, ZyanUSize count)
{
    if (!view || !source)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (index + count >= source->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    view->string.vector.data = (void*)((char*)source->string.vector.data + index);
    view->string.vector.size = count;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringViewInsideBuffer(ZyanStringView* view, const char* string)
{
    if (!view || !string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    view->string.vector.data = (void*)string;
    view->string.vector.size = ZYAN_STRLEN(string) + 1;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringViewInsideBufferEx(ZyanStringView* view, const char* buffer, ZyanUSize length)
{
    if (!view || !buffer || !length)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    view->string.vector.data = (void*)buffer;
    view->string.vector.size = length + 1;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringViewGetSize(const ZyanStringView* view, ZyanUSize* size)
{
    if (!view || !size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(view->string.vector.size >= 1);
    *size = view->string.vector.size - 1;

    return ZYAN_STATUS_SUCCESS;
}

ZYCORE_EXPORT ZyanStatus ZyanStringViewGetData(const ZyanStringView* view, const char** buffer)
{
    if (!view || !buffer)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    *buffer = view->string.vector.data;

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Character access                                                                               */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanStringGetChar(const ZyanStringView* string, ZyanUSize index, char* value)
{
    if (!string || !value)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow direct access to the terminating '\0' character
    if (index + 1 >= string->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    const char* chr;
    ZYAN_CHECK(ZyanVectorGetPointer(&string->string.vector, index, (const void**)&chr));
    *value = *chr;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringGetCharMutable(ZyanString* string, ZyanUSize index, char** value)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow direct access to the terminating '\0' character
    if (index + 1 >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    return ZyanVectorGetPointerMutable(&string->vector, index, (void**)value);
}

ZyanStatus ZyanStringSetChar(ZyanString* string, ZyanUSize index, char value)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow direct access to the terminating '\0' character
    if (index + 1 >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    return ZyanVectorSet(&string->vector, index, (void*)&value);
}

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

ZyanStatus ZyanStringInsert(ZyanString* destination, ZyanUSize index, const ZyanStringView* source)
{
    if (!destination || !source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (index == destination->vector.size)
    {
        return ZyanStringAppend(destination, source);
    }

    // Don't allow insertion after the terminating '\0' character
    if (index >= destination->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_CHECK(ZyanVectorInsertRange(&destination->vector, index, source->string.vector.data,
        source->string.vector.size - 1));
    ZYCORE_STRING_ASSERT_NULLTERMINATION(destination);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringInsertEx(ZyanString* destination, ZyanUSize destination_index,
    const ZyanStringView* source, ZyanUSize source_index, ZyanUSize count)
{
    if (!destination || !source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (destination_index == destination->vector.size)
    {
        return ZyanStringAppendEx(destination, source, source_index, count);
    }

    // Don't allow insertion after the terminating '\0' character
    if (destination_index >= destination->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    // Don't allow access to the terminating '\0' character
    if (source_index + count >= source->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_CHECK(ZyanVectorInsertRange(&destination->vector, destination_index,
        (char*)source->string.vector.data + source_index, count));
    ZYCORE_STRING_ASSERT_NULLTERMINATION(destination);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Appending                                                                                      */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanStringAppend(ZyanString* destination, const ZyanStringView* source)
{
    if (!destination || !source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    const ZyanUSize len = destination->vector.size;
    ZYAN_CHECK(ZyanVectorResize(&destination->vector, len + source->string.vector.size - 1));
    ZYAN_MEMCPY((char*)destination->vector.data + len - 1, source->string.vector.data,
        source->string.vector.size - 1);
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringAppendEx(ZyanString* destination, const ZyanStringView* source,
    ZyanUSize source_index, ZyanUSize count)
{
    if (!destination || !source || !source->string.vector.size)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if (source_index + count >= source->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    const ZyanUSize len = destination->vector.size;
    ZYAN_CHECK(ZyanVectorResize(&destination->vector, len + count));
    ZYAN_MEMCPY((char*)destination->vector.data + len - 1,
        (const char*)source->string.vector.data + source_index, count);
    ZYCORE_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

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

ZyanStatus ZyanStringDelete(ZyanString* string, ZyanUSize index, ZyanUSize count)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow removal of the terminating '\0' character
    if (index + count >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_CHECK(ZyanVectorDeleteRange(&string->vector, index, count));
    ZYCORE_STRING_NULLTERMINATE(string);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringTruncate(ZyanString* string, ZyanUSize index)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow removal of the terminating '\0' character
    if (index >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    ZYAN_CHECK(ZyanVectorDeleteRange(&string->vector, index, string->vector.size - index - 1));
    ZYCORE_STRING_NULLTERMINATE(string);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringClear(ZyanString* string)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_CHECK(ZyanVectorClear(&string->vector));
    // `ZyanVector` guarantees a minimum capacity of 1 element/character
    ZYAN_ASSERT(string->vector.capacity >= 1);

    *(char*)string->vector.data = '\0';
    string->vector.size++;

    return ZYAN_STATUS_SUCCESS;
}

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

ZyanStatus ZyanStringLPos(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index)
{
    if (!haystack)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringLPosEx(haystack, needle, found_index, 0, haystack->string.vector.size - 1);
}

ZyanStatus ZyanStringLPosEx(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
{
    if (!haystack || !needle || !found_index)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if (index + count >= haystack->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if ((haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
        (haystack->string.vector.size < needle->string.vector.size))
    {
        *found_index = -1;
        return ZYAN_STATUS_FALSE;
    }

    const char* s = (const char*)haystack->string.vector.data + index;
    const char* b = (const char*)needle->string.vector.data;
    for (; s + 1 < (const char*)haystack->string.vector.data + haystack->string.vector.size; ++s)
    {
        if (*s != *b)
        {
            continue;
        }
        const char* a = s;
        for (;;)
        {
            if ((ZyanUSize)(a - (const char*)haystack->string.vector.data) > index + count)
            {
                *found_index = -1;
                return ZYAN_STATUS_FALSE;
            }
            if (*b == 0)
            {
                *found_index = (ZyanISize)(s - (const char*)haystack->string.vector.data);
                return ZYAN_STATUS_TRUE;
            }
            if (*a++ != *b++)
            {
                break;
            }
        }
        b = (char*)needle->string.vector.data;
    }

    *found_index = -1;
    return ZYAN_STATUS_FALSE;
}

ZyanStatus ZyanStringLPosI(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index)
{
    if (!haystack)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringLPosIEx(haystack, needle, found_index, 0, haystack->string.vector.size - 1);
}

ZyanStatus ZyanStringLPosIEx(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
{
    // This solution assumes that characters are represented using ASCII representation, i.e.,
    // codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
    // 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.

    if (!haystack || !needle || !found_index)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if (index + count >= haystack->string.vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if ((haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
        (haystack->string.vector.size < needle->string.vector.size))
    {
        *found_index = -1;
        return ZYAN_STATUS_FALSE;
    }

    const char* s = (const char*)haystack->string.vector.data + index;
    const char* b = (const char*)needle->string.vector.data;
    for (; s + 1 < (const char*)haystack->string.vector.data + haystack->string.vector.size; ++s)
    {
        if ((*s != *b) && ((*s ^ 32) != *b))
        {
            continue;
        }
        const char* a = s;
        for (;;)
        {
            if ((ZyanUSize)(a - (const char*)haystack->string.vector.data) > index + count)
            {
                *found_index = -1;
                return ZYAN_STATUS_FALSE;
            }
            if (*b == 0)
            {
                *found_index = (ZyanISize)(s - (const char*)haystack->string.vector.data);
                return ZYAN_STATUS_TRUE;
            }
            const char c1 = *a++;
            const char c2 = *b++;
            if ((c1 != c2) && ((c1 ^ 32) != c2))
            {
                break;
            }
        }
        b = (char*)needle->string.vector.data;
    }

    *found_index = -1;
    return ZYAN_STATUS_FALSE;
}

ZyanStatus ZyanStringRPos(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index)
{
    if (!haystack)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringRPosEx(haystack, needle, found_index, haystack->string.vector.size - 1,
        haystack->string.vector.size - 1);
}

ZyanStatus ZyanStringRPosEx(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
{
    if (!haystack || !needle || !found_index)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if ((index >= haystack->string.vector.size) || (count > index))
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (!index || !count ||
        (haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
        (haystack->string.vector.size < needle->string.vector.size))
    {
        *found_index = -1;
        return ZYAN_STATUS_FALSE;
    }

    const char* s = (const char*)haystack->string.vector.data + index - 1;
    const char* b = (const char*)needle->string.vector.data + needle->string.vector.size - 2;
    for (; s >= (const char*)haystack->string.vector.data; --s)
    {
        if (*s != *b)
        {
            continue;
        }
        const char* a = s;
        for (;;)
        {
            if (b < (const char*)needle->string.vector.data)
            {
                *found_index = (ZyanISize)(a - (const char*)haystack->string.vector.data + 1);
                return ZYAN_STATUS_TRUE;
            }
            if (a < (const char*)haystack->string.vector.data + index - count)
            {
                *found_index = -1;
                return ZYAN_STATUS_FALSE;
            }
            if (*a-- != *b--)
            {
                break;
            }
        }
        b = (char*)needle->string.vector.data + needle->string.vector.size - 2;
    }

    *found_index = -1;
    return ZYAN_STATUS_FALSE;
}

ZyanStatus ZyanStringRPosI(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index)
{
    if (!haystack)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringRPosIEx(haystack, needle, found_index, haystack->string.vector.size - 1,
        haystack->string.vector.size - 1);
}

ZyanStatus ZyanStringRPosIEx(const ZyanStringView* haystack, const ZyanStringView* needle,
    ZyanISize* found_index, ZyanUSize index, ZyanUSize count)
{
    // This solution assumes that characters are represented using ASCII representation, i.e.,
    // codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
    // 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.

    if (!haystack || !needle || !found_index)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if ((index >= haystack->string.vector.size) || (count > index))
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    if (!index || !count ||
        (haystack->string.vector.size == 1) || (needle->string.vector.size == 1) ||
        (haystack->string.vector.size < needle->string.vector.size))
    {
        *found_index = -1;
        return ZYAN_STATUS_FALSE;
    }

    const char* s = (const char*)haystack->string.vector.data + index - 1;
    const char* b = (const char*)needle->string.vector.data + needle->string.vector.size - 2;
    for (; s >= (const char*)haystack->string.vector.data; --s)
    {
        if ((*s != *b) && ((*s ^ 32) != *b))
        {
            continue;
        }
        const char* a = s;
        for (;;)
        {
            if (b < (const char*)needle->string.vector.data)
            {
                *found_index = (ZyanISize)(a - (const char*)haystack->string.vector.data + 1);
                return ZYAN_STATUS_TRUE;
            }
            if (a < (const char*)haystack->string.vector.data + index - count)
            {
                *found_index = -1;
                return ZYAN_STATUS_FALSE;
            }
            const char c1 = *a--;
            const char c2 = *b--;
            if ((c1 != c2) && ((c1 ^ 32) != c2))
            {
                break;
            }
        }
        b = (char*)needle->string.vector.data + needle->string.vector.size - 2;
    }

    *found_index = -1;
    return ZYAN_STATUS_FALSE;
}

/* ---------------------------------------------------------------------------------------------- */
/* Comparing                                                                                      */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanStringCompare(const ZyanStringView* s1, const ZyanStringView* s2, ZyanI32* result)
{
    if (!s1 || !s2)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (s1->string.vector.size < s2->string.vector.size)
    {
        *result = -1;
        return ZYAN_STATUS_FALSE;
    }
    if (s1->string.vector.size > s2->string.vector.size)
    {
        *result =  1;
        return ZYAN_STATUS_FALSE;
    }

    const char* const a = (char*)s1->string.vector.data;
    const char* const b = (char*)s2->string.vector.data;
    ZyanUSize i;
    for (i = 0; (i + 1 < s1->string.vector.size) && (i + 1 < s2->string.vector.size); ++i)
    {
        if (a[i] == b[i])
        {
            continue;
        }
        break;
    }

    if (a[i] == b[i])
    {
        *result = 0;
        return ZYAN_STATUS_TRUE;
    }

    if ((a[i] | 32) < (b[i] | 32))
    {
        *result = -1;
        return ZYAN_STATUS_FALSE;
    }

    *result = 1;
    return ZYAN_STATUS_FALSE;
}

ZyanStatus ZyanStringCompareI(const ZyanStringView* s1, const ZyanStringView* s2, ZyanI32* result)
{
    // This solution assumes that characters are represented using ASCII representation, i.e.,
    // codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
    // 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.

    if (!s1 || !s2)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    if (s1->string.vector.size < s2->string.vector.size)
    {
        *result = -1;
        return ZYAN_STATUS_FALSE;
    }
    if (s1->string.vector.size > s2->string.vector.size)
    {
        *result =  1;
        return ZYAN_STATUS_FALSE;
    }

    const char* const a = (char*)s1->string.vector.data;
    const char* const b = (char*)s2->string.vector.data;
    ZyanUSize i;
    for (i = 0; (i + 1 < s1->string.vector.size) && (i + 1 < s2->string.vector.size); ++i)
    {
        if ((a[i] == b[i]) || ((a[i] ^ 32) == b[i]))
        {
            continue;
        }
        break;
    }

    if (a[i] == b[i])
    {
        *result = 0;
        return ZYAN_STATUS_TRUE;
    }

    if ((a[i] | 32) < (b[i] | 32))
    {
        *result = -1;
        return ZYAN_STATUS_FALSE;
    }

    *result = 1;
    return ZYAN_STATUS_FALSE;
}

/* ---------------------------------------------------------------------------------------------- */
/* Case conversion                                                                                */
/* ---------------------------------------------------------------------------------------------- */

ZyanStatus ZyanStringToLowerCase(ZyanString* string)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringToLowerCaseEx(string, 0, string->vector.size - 1);
}

ZyanStatus ZyanStringToLowerCaseEx(ZyanString* string, ZyanUSize index, ZyanUSize count)
{
    // This solution assumes that characters are represented using ASCII representation, i.e.,
    // codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
    // 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.

    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if (index + count >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    char* s = (char*)string->vector.data + index;
    for (ZyanUSize i = index; i < index + count; ++i)
    {
        const char c = *s;
        if ((c >= 'A') && (c <= 'Z'))
        {
            *s = c | 32;
        }
        ++s;
    }

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringToUpperCase(ZyanString* string)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanStringToUpperCaseEx(string, 0, string->vector.size - 1);
}

ZyanStatus ZyanStringToUpperCaseEx(ZyanString* string, ZyanUSize index, ZyanUSize count)
{
    // This solution assumes that characters are represented using ASCII representation, i.e.,
    // codes for 'a', 'b', 'c', .. 'z' are 97, 98, 99, .. 122 respectively. And codes for 'A',
    // 'B', 'C', .. 'Z' are 65, 66, .. 95 respectively.

    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    // Don't allow access to the terminating '\0' character
    if (index + count >= string->vector.size)
    {
        return ZYAN_STATUS_OUT_OF_RANGE;
    }

    char* s = (char*)string->vector.data + index;
    for (ZyanUSize i = index; i < index + count; ++i)
    {
        const char c = *s;
        if ((c >= 'a') && (c <= 'z'))
        {
            *s = c & ~32;
        }
        ++s;
    }

    return ZYAN_STATUS_SUCCESS;
}

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

ZyanStatus ZyanStringResize(ZyanString* string, ZyanUSize size)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_CHECK(ZyanVectorResize(&string->vector, size + 1));
    ZYCORE_STRING_NULLTERMINATE(string);

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringReserve(ZyanString* string, ZyanUSize capacity)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorReserve(&string->vector, capacity);
}

ZyanStatus ZyanStringShrinkToFit(ZyanString* string)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    return ZyanVectorShrinkToFit(&string->vector);
}

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

ZyanStatus ZyanStringGetCapacity(const ZyanString* string, ZyanUSize* capacity)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(string->vector.capacity >= 1);
    *capacity = string->vector.capacity - 1;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringGetSize(const ZyanString* string, ZyanUSize* size)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    ZYAN_ASSERT(string->vector.size >= 1);
    *size = string->vector.size - 1;

    return ZYAN_STATUS_SUCCESS;
}

ZyanStatus ZyanStringGetData(const ZyanString* string, const char** value)
{
    if (!string)
    {
        return ZYAN_STATUS_INVALID_ARGUMENT;
    }

    *value = string->vector.data;

    return ZYAN_STATUS_SUCCESS;
}

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

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