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

  Zyan Disassembler Library (Zydis)

  Original Author : Florian Bernd, Joel Hoener

 * 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.

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

/**
 * @file
 * @brief   Provides some internal, more performant, but unsafe helper functions for the
 *          `ZyanString` datatype.
 *
 * Most of these functions are very similar to the ones in `Zycore/String.h`, but inlined and
 * without optional overhead like parameter-validation checks, etc ...
 *
 * The `ZyanString` datatype is able to dynamically allocate memory on the heap, but as `Zydis` is
 * designed to be a non-'malloc'ing library, all functions in this file assume that the instances
 * they are operating on are created with a user-defined static-buffer.
 */

#ifndef ZYDIS_INTERNAL_STRING_H
#define ZYDIS_INTERNAL_STRING_H

#include <Zycore/LibC.h>
#include <Zycore/String.h>
#include <Zycore/Types.h>
#include <Zydis/ShortString.h>
#include <Zydis/Status.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ============================================================================================== */
/* Enums and types                                                                                */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Letter Case                                                                                    */
/* ---------------------------------------------------------------------------------------------- */

/**
 * @brief   Defines the `ZydisLetterCase` enum.
 */
typedef enum ZydisLetterCase_
{
    /**
     * @brief   Uses the given text "as is".
     */
    ZYDIS_LETTER_CASE_DEFAULT,
    /**
     * @brief   Converts the given text to lowercase letters.
     */
    ZYDIS_LETTER_CASE_LOWER,
    /**
     * @brief   Converts the given text to uppercase letters.
     */
    ZYDIS_LETTER_CASE_UPPER,

    /**
     * @brief   Maximum value of this enum.
     */
    ZYDIS_LETTER_CASE_MAX_VALUE = ZYDIS_LETTER_CASE_UPPER,
    /**
     * @brief   The minimum number of bits required to represent all values of this enum.
     */
    ZYDIS_LETTER_CASE_REQUIRED_BITS = ZYAN_BITS_TO_REPRESENT(ZYDIS_LETTER_CASE_MAX_VALUE)
} ZydisLetterCase;

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

/* ============================================================================================== */
/* Macros                                                                                         */
/* ============================================================================================== */

/* ---------------------------------------------------------------------------------------------- */
/* Internal macros                                                                                */
/* ---------------------------------------------------------------------------------------------- */

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

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

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

/* ============================================================================================== */
/* Internal Functions                                                                             */
/* ============================================================================================== */

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

/**
 * @brief   Appends the content of the source string to the end of the destination string.
 *
 * @param   destination The destination string.
 * @param   source      The source string.
 *
 * @return  A zyan status code.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppend(ZyanString* destination, const ZyanStringView* source)
{
    ZYAN_ASSERT(destination && source);
    ZYAN_ASSERT(!destination->vector.allocator);
    ZYAN_ASSERT(destination->vector.size && source->string.vector.size);

    if (destination->vector.size + source->string.vector.size - 1 > destination->vector.capacity)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_MEMCPY((char*)destination->vector.data + destination->vector.size - 1,
        source->string.vector.data, source->string.vector.size - 1);

    destination->vector.size += source->string.vector.size - 1;
    ZYDIS_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

/**
 * @brief   Appends the content of the source string to the end of the destination string,
 *          converting the characters to the specified letter-case.
 *
 * @param   destination The destination string.
 * @param   source      The source string.
 * @param   letter_case The desired letter-case.
 *
 * @return  A zyan status code.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppendCase(ZyanString* destination, const ZyanStringView* source,
    ZydisLetterCase letter_case)
{
    ZYAN_ASSERT(destination && source);
    ZYAN_ASSERT(!destination->vector.allocator);
    ZYAN_ASSERT(destination->vector.size && source->string.vector.size);

    if (destination->vector.size + source->string.vector.size - 1 > destination->vector.capacity)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_MEMCPY((char*)destination->vector.data + destination->vector.size - 1,
        source->string.vector.data, source->string.vector.size - 1);

    switch (letter_case)
    {
    case ZYDIS_LETTER_CASE_DEFAULT:
        break;
    case ZYDIS_LETTER_CASE_LOWER:
    {
        const ZyanUSize index = destination->vector.size - 1;
        const ZyanUSize count = source->string.vector.size - 1;
        char* s = (char*)destination->vector.data + index;
        for (ZyanUSize i = index; i < index + count; ++i)
        {
            const char c = *s;
            if ((c >= 'A') && (c <= 'Z'))
            {
                *s = c | 32;
            }
            ++s;
        }
        break;
    }
    case ZYDIS_LETTER_CASE_UPPER:
    {
        const ZyanUSize index = destination->vector.size - 1;
        const ZyanUSize count = source->string.vector.size - 1;
        char* s = (char*)destination->vector.data + index;
        for (ZyanUSize i = index; i < index + count; ++i)
        {
            const char c = *s;
            if ((c >= 'a') && (c <= 'z'))
            {
                *s = c & ~32;
            }
            ++s;
        }
        break;
    }
    default:
        ZYAN_UNREACHABLE;
    }

    destination->vector.size += source->string.vector.size - 1;
    ZYDIS_STRING_NULLTERMINATE(destination);

    return ZYAN_STATUS_SUCCESS;
}

/**
 * @brief   Appends the content of the source short-string to the end of the destination string.
 *
 * @param   destination The destination string.
 * @param   source      The source string.
 *
 * @return  A zyan status code.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppendShort(ZyanString* destination,
    const ZydisShortString* source)
{
    ZYAN_ASSERT(destination && source);
    ZYAN_ASSERT(!destination->vector.allocator);
    ZYAN_ASSERT(destination->vector.size && source->size);

    if (destination->vector.size + source->size > destination->vector.capacity)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_MEMCPY((char*)destination->vector.data + destination->vector.size - 1, source->data,
        source->size + 1);

    destination->vector.size += source->size;
    ZYDIS_STRING_ASSERT_NULLTERMINATION(destination);

    return ZYAN_STATUS_SUCCESS;
}

/**
 * @brief   Appends the content of the source short-string to the end of the destination string,
 *          converting the characters to the specified letter-case.
 *
 * @param   destination The destination string.
 * @param   source      The source string.
 * @param   letter_case The desired letter-case.
 *
 * @return  A zyan status code.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppendShortCase(ZyanString* destination,
    const ZydisShortString* source, ZydisLetterCase letter_case)
{
    ZYAN_ASSERT(destination && source);
    ZYAN_ASSERT(!destination->vector.allocator);
    ZYAN_ASSERT(destination->vector.size && source->size);

    if (destination->vector.size + source->size > destination->vector.capacity)
    {
        return ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE;
    }

    ZYAN_MEMCPY((char*)destination->vector.data + destination->vector.size - 1, source->data,
        source->size + 1);

    switch (letter_case)
    {
    case ZYDIS_LETTER_CASE_DEFAULT:
        break;
    case ZYDIS_LETTER_CASE_LOWER:
    {
        const ZyanUSize index = destination->vector.size - 1;
        const ZyanUSize count = source->size;
        char* s = (char*)destination->vector.data + index;
        for (ZyanUSize i = index; i < index + count; ++i)
        {
            const char c = *s;
            if ((c >= 'A') && (c <= 'Z'))
            {
                *s = c | 32;
            }
            ++s;
        }
        break;
    }
    case ZYDIS_LETTER_CASE_UPPER:
    {
        const ZyanUSize index = destination->vector.size - 1;
        const ZyanUSize count = source->size;
        char* s = (char*)destination->vector.data + index;
        for (ZyanUSize i = index; i < index + count; ++i)
        {
            const char c = *s;
            if ((c >= 'a') && (c <= 'z'))
            {
                *s = c & ~32;
            }
            ++s;
        }
        break;
    }
    default:
        ZYAN_UNREACHABLE;
    }

    destination->vector.size += source->size;
    ZYDIS_STRING_ASSERT_NULLTERMINATION(destination);

    return ZYAN_STATUS_SUCCESS;
}

/* ---------------------------------------------------------------------------------------------- */
/* Formatting                                                                                     */
/* ---------------------------------------------------------------------------------------------- */

/**
 * @brief   Formats the given unsigned ordinal `value` to its decimal text-representation and
 *          appends it to the `string`.
 *
 * @param   string          A pointer to the `ZyanString` instance.
 * @param   value           The value.
 * @param   padding_length  Padds the converted value with leading zeros, if the number of chars is
 *                          less than the `padding_length`.
 * @param   prefix          The string to use as prefix or `ZYAN_NULL`, if not needed.
 * @param   suffix          The string to use as suffix or `ZYAN_NULL`, if not needed.
 *
 * @return  A zyan status code.
 *
 * This function will fail, if the `ZYAN_STRING_IS_IMMUTABLE` flag is set for the specified
 * `ZyanString` instance.
 */
ZyanStatus ZydisStringAppendDecU(ZyanString* string, ZyanU64 value, ZyanU8 padding_length,
    const ZyanStringView* prefix, const ZyanStringView* suffix);

/**
 * @brief   Formats the given signed ordinal `value` to its decimal text-representation and
 *          appends it to the `string`.
 *
 * @param   string          A pointer to the `ZyanString` instance.
 * @param   value           The value.
 * @param   padding_length  Padds the converted value with leading zeros, if the number of chars is
 *                          less than the `padding_length`.
 * @param   force_sign      Set `ZYAN_TRUE`, to force printing of the `+` sign for positive numbers.
 * @param   prefix          The string to use as prefix or `ZYAN_NULL`, if not needed.
 * @param   suffix          The string to use as suffix or `ZYAN_NULL`, if not needed.
 *
 * @return  A zyan status code.
 *
 * This function will fail, if the `ZYAN_STRING_IS_IMMUTABLE` flag is set for the specified
 * `ZyanString` instance.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppendDecS(ZyanString* string, ZyanI64 value,
    ZyanU8 padding_length, ZyanBool force_sign, const ZyanStringView* prefix,
    const ZyanStringView* suffix)
{
    static const ZydisShortString str_add = ZYDIS_MAKE_SHORTSTRING("+");
    static const ZydisShortString str_sub = ZYDIS_MAKE_SHORTSTRING("-");

    if (value < 0)
    {
        ZYAN_CHECK(ZydisStringAppendShort(string, &str_sub));
        if (prefix)
        {
            ZYAN_CHECK(ZydisStringAppend(string, prefix));
        }
        return ZydisStringAppendDecU(string, -value, padding_length, ZYAN_NULL, suffix);
    }

    if (force_sign)
    {
        ZYAN_ASSERT(value >= 0);
        ZYAN_CHECK(ZydisStringAppendShort(string, &str_add));
    }
    return ZydisStringAppendDecU(string, value, padding_length, prefix, suffix);
}

/**
 * @brief   Formats the given unsigned ordinal `value` to its hexadecimal text-representation and
 *          appends it to the `string`.
 *
 * @param   string          A pointer to the `ZyanString` instance.
 * @param   value           The value.
 * @param   padding_length  Padds the converted value with leading zeros, if the number of chars is
 *                          less than the `padding_length`.
 * @param   uppercase       Set `ZYAN_TRUE` to use uppercase letters ('A'-'F') instead of lowercase
 *                          ones ('a'-'f').
 * @param   prefix          The string to use as prefix or `ZYAN_NULL`, if not needed.
 * @param   suffix          The string to use as suffix or `ZYAN_NULL`, if not needed.
 *
 * @return  A zyan status code.
 *
 * This function will fail, if the `ZYAN_STRING_IS_IMMUTABLE` flag is set for the specified
 * `ZyanString` instance.
 */
ZyanStatus ZydisStringAppendHexU(ZyanString* string, ZyanU64 value, ZyanU8 padding_length,
    ZyanBool uppercase, const ZyanStringView* prefix, const ZyanStringView* suffix);

/**
 * @brief   Formats the given signed ordinal `value` to its hexadecimal text-representation and
 *          appends it to the `string`.
 *
 * @param   string          A pointer to the string.
 * @param   value           The value.
 * @param   padding_length  Padds the converted value with leading zeros, if the number of chars is
 *                          less than the `padding_length` (the sign char is ignored).
 * @param   uppercase       Set `ZYAN_TRUE` to print the hexadecimal value in uppercase letters
 *                          instead of lowercase ones.
 * @param   force_sign      Set to `ZYAN_TRUE`, to force printing of the `+` sign for positive
 *                          numbers.
 * @param   prefix          The string to use as prefix or `NULL`, if not needed.
 * @param   suffix          The string to use as suffix or `NULL`, if not needed.
 *
 * @return  `ZYAN_STATUS_SUCCESS`, if the function succeeded, or
 *          `ZYAN_STATUS_INSUFFICIENT_BUFFER_SIZE`, if the size of the buffer was not
 *          sufficient to append the given `value`.
 *
 * The string-buffer pointer is increased by the number of chars written, if the call was
 * successfull.
 */
ZYAN_INLINE ZyanStatus ZydisStringAppendHexS(ZyanString* string, ZyanI64 value,
    ZyanU8 padding_length, ZyanBool uppercase, ZyanBool force_sign, const ZyanStringView* prefix,
    const ZyanStringView* suffix)
{
    static const ZydisShortString str_add = ZYDIS_MAKE_SHORTSTRING("+");
    static const ZydisShortString str_sub = ZYDIS_MAKE_SHORTSTRING("-");

    if (value < 0)
    {
        ZYAN_CHECK(ZydisStringAppendShort(string, &str_sub));
        if (prefix)
        {
            ZYAN_CHECK(ZydisStringAppend(string, prefix));
        }
        return ZydisStringAppendHexU(string, -value, padding_length, uppercase, ZYAN_NULL, suffix);
    }

    if (force_sign)
    {
        ZYAN_ASSERT(value >= 0);
        ZYAN_CHECK(ZydisStringAppendShort(string, &str_add));
    }
    return ZydisStringAppendHexU(string, value, padding_length, uppercase, prefix, suffix);
}

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

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

#ifdef __cplusplus
}
#endif

#endif // ZYDIS_INTERNAL_STRING_H
