#include <Core/Common/CoreUtils.h>
#include <Core/Common/String.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/Utils.h>

namespace Juliet
{
    namespace
    {
        constexpr int32 kUnknown_UNICODE = 0xFFFD;

        struct
        {
            String         Name;
            StringEncoding Format;
        } Encodings[] = {
            /* *INDENT-OFF* */ // clang-format off
            { ConstString("ASCII"), StringEncoding::ASCII },
            { ConstString("US-ASCII"), StringEncoding::ASCII },
            { ConstString("8859-1"), StringEncoding::LATIN1 },
            { ConstString("ISO-8859-1"), StringEncoding::LATIN1 },
        #if defined(JULIET_WIN32)
            { ConstString("WCHAR_T"), StringEncoding::UTF16 },
        #else
            { ConstString("WCHAR_T"), StringEncoding::UCS4 },
        #endif
            { ConstString("UTF8"), StringEncoding::UTF8 },
            { ConstString("UTF-8"), StringEncoding::UTF8 },
            { ConstString("UTF16"), StringEncoding::UTF16 },
            { ConstString("UTF-16"), StringEncoding::UTF16 },
            { ConstString("UTF32"), StringEncoding::UTF32 },
            { ConstString("UTF-32"), StringEncoding::UTF32 },
            { ConstString("UCS2"), StringEncoding::UCS2 },
            { ConstString("UCS-2"), StringEncoding::UCS2 },
            { ConstString("UCS-2-INTERNAL"), StringEncoding::UCS2 },
            { ConstString("UCS4"), StringEncoding::UCS4 },
            { ConstString("UCS-4"), StringEncoding::UCS4 },
            { ConstString("UCS-4-INTERNAL"), StringEncoding::UCS4 },
        /* *INDENT-ON* */ // clang-format on
        };

        // Returns the number of codepoint case folded (lowercase equivalent in the language)
        // Takes an UTF-8 codepoint (uint32) and codefold it to up to 3 uint32
        // TODO Supports more than low ASCI :)
        int8 CaseFoldUnicode(uint32 from, uint32* to)
        {
            if (from < 128)
            {
                // low-ASCII, easy!
                if ((from >= 'A') && (from <= 'Z'))
                {
                    *to = 'a' + (from - 'A');
                    return 1;
                }
            }
            *to = from;
            return 1;
        }
    } // namespace

    uint32 StepUTF8(String& inStr)
    {
        // From rfc3629, the UTF-8 spec:
        //  https://www.ietf.org/rfc/rfc3629.txt
        //
        //   Char. number range  |        UTF-8 octet sequence
        //      (hexadecimal)    |              (binary)
        //   --------------------+---------------------------------------------
        //   0000 0000-0000 007F | 0xxxxxxx
        //   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
        //   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
        //   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        //
        // The function checks character validity and overlong (using too many bytes to encode the character) and return invalid utf8 char if detected.

        // If string is empty then it's done.
        if (inStr.Size == 0)
        {
            return 0;
        }

        auto         str              = reinterpret_cast<const uint8*>(CStr(inStr));
        const uint32 octet            = *str;
        bool         isOverlong       = false;
        bool         isInvalid        = false;
        bool         isUTF16Surrogate = false;
        if (octet == 0)
        {
            return 0;
        }
        if ((octet & 0x80) == 0x0) // One byte code point: 0xxxxxxx
        {
            inStr.Data += 1;
            inStr.Size -= 1;
            return octet;
        }
        else if ((octet & 0xE0) == 0xC0) // Two bytes code point: 110xxxxx 10xxxxxx
        {
            const uint8 secondByte = str[1];
            if ((secondByte & 0xC0) == 0x80) // Make sure the trailing byte is correct
            {
                const uint32 result = ((octet & 0x1F) << 6) | (secondByte & 0x3F);
                if (result >= 0x80) // If the result is smaller than 0x80 its an overlong!
                {
                    inStr.Data += 2;
                    inStr.Size -= 1;
                    return result;
                }
                else
                {
                    isOverlong = true;
                }
            }
            else
            {
                isInvalid = true;
            }
        }
        else if (((octet & 0xF0) == 0xE0)) // Three bytes code point: 1110xxxx 10xxxxxx 10xxxxxx
        {
            const uint8 secondByte = str[1];
            const uint8 thirdByte  = str[2];
            if (((secondByte & 0xC0) == 0x80) && ((thirdByte & 0xC0) == 0x80)) // Make sure the trailing bytes are correct
            {
                const uint32 secondOctet = static_cast<uint32>(secondByte & 0x3F) << 6;
                const uint32 thirdOctet  = static_cast<uint32>(thirdByte & 0x3F);
                const uint32 result      = ((octet & 0x0F) << 12) | secondOctet | thirdOctet;
                if (result >= 0x0800) // if the result is smaller its an overlong.
                {
                    if ((result < 0xD800) || (result > 0xDFFF)) // If out of range its an UTF-16 surrogate
                    {
                        inStr.Data += 3;
                        inStr.Size -= 1;
                        return result;
                    }
                    else
                    {
                        isUTF16Surrogate = true;
                    }
                }
                else
                {
                    isOverlong = true;
                }
            }
            else
            {
                isInvalid = true;
            }
        }
        else if (((octet & 0xF8) == 0xF0))
        { // Four bytes code point: 11110xxxx 10xxxxxx 10xxxxxx 10xxxxxx
            const uint8 secondByte = str[1];
            const uint8 thirdByte  = str[2];
            const uint8 fourthByte = str[3];
            if (((secondByte & 0xC0) == 0x80) && ((thirdByte & 0xC0) == 0x80) &&
                ((fourthByte & 0xC0) == 0x80)) // // Make sure the trailing bytes are correct
            {
                const uint32 secondOctet = static_cast<uint32>(secondByte & 0x1F) << 12;
                const uint32 thirdOctet  = static_cast<uint32>(thirdByte & 0x3F) << 6;
                const uint32 fourthOctet = static_cast<uint32>(fourthByte & 0x3F);
                const uint32 result      = ((octet & 0x07) << 18) | secondOctet | thirdOctet | fourthOctet;
                if (result >= 0x10000) // If smaller its an overlong
                {
                    inStr.Data += 4;
                    inStr.Size -= 1;
                    return result;
                }
                else
                {
                    isOverlong = true;
                }
            }
            else
            {
                isInvalid = true;
            }
        }
        LogError(LogCategory::Core, "StepUTF8: Non supported codepoint. IsOverlong: %s. IsInvalid %s. IsUTF16Surrogate %s",
                 isOverlong ? "true" : "false", isInvalid ? "true" : "false", isUTF16Surrogate ? "true" : "false");
        inStr.Data += 1;
        return kInvalidUTF8;
    }

    String FindString(String haystack, String needle)
    {
        if (!IsValid(needle))
        {
            return haystack;
        }

        for (; IsValid(haystack); StepUTF8(haystack))
        {
            String tempHaystack = haystack;
            String testNeedle   = needle;

            while (IsValid(tempHaystack) && IsValid(testNeedle))
            {
                uint32 codepointHaystack = StepUTF8(tempHaystack);
                uint32 codepointNeedle   = StepUTF8(testNeedle);
                if (codepointHaystack != codepointNeedle)
                {
                    break;
                }
            }

            if (!IsValid(testNeedle))
            {
                return haystack;
            }
        }

        return {};
    }

    int8 StringCompareCaseInsensitive(String str1, String str2)
    {
        // TODO: Support UTF8. For now ASCII only.
        uint32 left  = 0;
        uint32 right = 0;
        while (true)
        {
            {
                uint32                leftFolded[3];
                [[maybe_unused]] int8 num_folded = CaseFoldUnicode(StepUTF8(str1), leftFolded);
                Assert(num_folded == 1); // Only one uint32 codepoint supported for now (low ascii)
                left = leftFolded[0];
            }
            {
                uint32                rightFolded[3];
                [[maybe_unused]] int8 num_folded = CaseFoldUnicode(StepUTF8(str2), rightFolded);
                Assert(num_folded == 1); // Only one uint32 codepoint supported for now (low ascii)
                right = rightFolded[0];
            }
            if (left < right)
            {
                return -1;
            }
            if (left > right)
            {
                return 1;
            }
            if (left == 0)
            {
                break;
            }
        }
        return 0;
    }

    bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst, bool nullTerminate)
    {
        Assert(IsValid(src));

        const char* srcStr            = src.Data;
        char*       dstStr            = dst.Data;
        size_t      remainingCapacity = dst.Capacity;

        uint32 character = 0;
        while (src.Size > 0)
        {
            // Decode in character
            switch (from)
            {
            case StringEncoding::UTF8: // Uses RFC 3629
                {
                    auto   p        = reinterpret_cast<const uint8*>(srcStr);
                    size_t left     = 0;
                    bool   overlong = false;
                    if (p[0] >= 0xF0)
                    {
                        if ((p[0] & 0xF8) != 0xF0)
                        {
                            character = kUnknown_UNICODE;
                        }
                        else
                        {
                            if (p[0] == 0xF0 && src.Size > 1 && (p[1] & 0xF0) == 0x80)
                            {
                                overlong = true;
                            }
                            character = static_cast<uint32>(p[0] & 0x07);
                            left      = 3;
                        }
                    }
                    else if (p[0] >= 0xE0)
                    {
                        if ((p[0] & 0xF0) != 0xE0)
                        {
                            character = kUnknown_UNICODE;
                        }
                        else
                        {
                            if (p[0] == 0xE0 && src.Size > 1 && (p[1] & 0xE0) == 0x80)
                            {
                                overlong = true;
                            }
                            character = static_cast<uint32>(p[0] & 0x0F);
                            left      = 2;
                        }
                    }
                    else if (p[0] >= 0xC0)
                    {
                        if ((p[0] & 0xE0) != 0xC0)
                        {
                            character = kUnknown_UNICODE;
                        }
                        else
                        {
                            if ((p[0] & 0xDE) == 0xC0)
                            {
                                overlong = true;
                            }
                            character = static_cast<uint32>(p[0] & 0x1F);
                            left      = 1;
                        }
                    }
                    else
                    {
                        if (p[0] & 0x80)
                        {
                            character = kUnknown_UNICODE;
                        }
                        else
                        {
                            character = static_cast<uint32>(p[0]);
                        }
                    }
                    ++srcStr;
                    --src.Size;
                    if (src.Size < left)
                    {
                        Log(LogLevel::Error, LogCategory::Core, "ConvertString: Failed to convert string. Incomplete input sequence");
                        return false;
                    }
                    while (left--)
                    {
                        ++p;
                        if ((p[0] & 0xC0) != 0x80)
                        {
                            character = kUnknown_UNICODE;
                            break;
                        }
                        character <<= 6;
                        character |= (p[0] & 0x3F);
                        ++srcStr;
                        --src.Size;
                    }
                    if (overlong)
                    {
                        character = kUnknown_UNICODE;
                    }
                    if ((character >= 0xD800 && character <= 0xDFFF) || (character == 0xFFFE || character == 0xFFFF) ||
                        character > 0x10FFFF)
                    {
                        character = kUnknown_UNICODE;
                    }
                    break;
                }
            case StringEncoding::Unknown: Assert(false, "ConvertString: Invalid Source Format: Unknown"); break;
            case StringEncoding::ASCII:
            case StringEncoding::LATIN1:
            case StringEncoding::UTF16:
            case StringEncoding::UTF32:
            case StringEncoding::UCS2:
            case StringEncoding::UCS4: Assert(false, "ConvertString: Unsupported Source Format"); break;
            }

            // Encode out character
            switch (to)
            {
            case StringEncoding::UTF16: // RFC 2781
                {
                    auto p = reinterpret_cast<uint8*>(dstStr);
                    if (character > 0x10FFFF)
                    {
                        character = kUnknown_UNICODE;
                    }
                    if (character < 0x10000)
                    {
                        if (remainingCapacity < 2)
                        {
                            Log(LogLevel::Error, LogCategory::Core, "ConvertString: Destination buffer too short to fit UTF16");
                            return false;
                        }
                        p[1] = static_cast<uint8>(character >> 8);
                        p[0] = static_cast<uint8>(character);

                        dstStr += 2;
                        dst.Size += 1;
                        remainingCapacity -= 2;
                    }
                    else
                    {
                        if (remainingCapacity < 4)
                        {
                            Log(LogLevel::Error, LogCategory::Core, "ConvertString: Destination buffer too short to fit UTF16");
                            return false;
                        }
                        character    = character - 0x10000;
                        uint16 word1 = 0xD800U | static_cast<uint16>((character >> 10) & 0x3FF);
                        uint16 word2 = 0xDC00U | static_cast<uint16>(character & 0x3FF);
                        p[1]         = static_cast<uint8>(word1 >> 8);
                        p[0]         = static_cast<uint8>(word1);
                        p[3]         = static_cast<uint8>(word2 >> 8);
                        p[2]         = static_cast<uint8>(word2);

                        dstStr += 4;
                        dst.Size += 1;
                        remainingCapacity -= 4;
                    }
                    break;
                }
            case StringEncoding::Unknown: Assert(false, "ConvertString: Invalid Source Format: Unknown"); break;
            case StringEncoding::ASCII:
            case StringEncoding::LATIN1:
            case StringEncoding::UTF8:
            case StringEncoding::UTF32:
            case StringEncoding::UCS2:
            case StringEncoding::UCS4: Assert(false, "ConvertString: Unsupported Destination Format"); break;
            }
        }

        if (nullTerminate)
        {
            switch (to)
            {
            case StringEncoding::UTF16:
                {
                    if (remainingCapacity < 2)
                    {
                        Log(LogLevel::Error, LogCategory::Core,
                            "ConvertString: Destination buffer too short to add null terminator into UTF16 buffer");
                        return false;
                    }
                    dstStr[0] = 0;
                    dstStr[1] = 0;
                    remainingCapacity -= 2;
                    break;
                }
            case StringEncoding::Unknown: Assert(false, "ConvertString: Invalid Source Format: Unknown"); break;
            case StringEncoding::ASCII:
            case StringEncoding::LATIN1:
            case StringEncoding::UTF8:
            case StringEncoding::UTF32:
            case StringEncoding::UCS2:
            case StringEncoding::UCS4: Assert(false, "ConvertString: Unsupported Destination Format"); break;
            }
        }

        return true;
    }

    bool ConvertString(String from, String to, String src, StringBuffer& dst, bool nullTerminate)
    {
        Assert(IsValid(from));
        Assert(IsValid(to));

        // First find the encoding of the strings
        auto sourceFormat = StringEncoding::Unknown;
        auto destFormat   = StringEncoding::Unknown;
        for (auto& encoding : Encodings)
        {
            if (StringCompareCaseInsensitive(from, encoding.Name) == 0)
            {
                sourceFormat = encoding.Format;
                if (destFormat != StringEncoding::Unknown)
                {
                    break;
                }
            }
            if (StringCompareCaseInsensitive(to, encoding.Name) == 0)
            {
                destFormat = encoding.Format;
                if (sourceFormat != StringEncoding::Unknown)
                {
                    break;
                }
            }
        }

        if (sourceFormat == StringEncoding::Unknown || destFormat == StringEncoding::Unknown)
        {
            return false;
        }

        return ConvertString(sourceFormat, destFormat, src, dst, nullTerminate);
    }

    String StringCopy(NonNullPtr<Arena> arena, String str)
    {
        String result;
        result.Size = str.Size;
        result.Data = static_cast<char*>(ArenaPush(arena, str.Size + 1, alignof(char), true JULIET_DEBUG_ONLY(, "String")));
        MemCopy(result.Data, str.Data, str.Size);
        result.Data[result.Size] = 0;
        return result;
    }

} // namespace Juliet
