#pragma once

#include <string.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Math/MathUtils.h>
#include <Core/Memory/Utils.h>

namespace Juliet
{
    struct Arena;

#define ConstString(str) { const_cast<char*>((str)), sizeof(str) - 1 }
#define CStr(str) ((str).Data)
#define InplaceString(name, size)                                                                                      \
    char name##_[size];                                                                                                \
    MemSet(name##_, 0, sizeof(uint32));                                                                                \
    String name = { name##_, 0 }

    // Everything is Little Endian
    enum class StringEncoding : uint8
    {
        Unknown = 0,
        ASCII,
        LATIN1,
        UTF8,
        UTF16,
        UTF32,
        UCS2,
        UCS4,
    };

    // Represents a UTF-8 String.
    // Not null terminated.
    struct String
    {
        char*  Data;
        size_t Size;
    };

    struct StringBuffer : String
    {
        size_t Capacity;
    };

    struct StringListNode
    {
        StringListNode* Next;
        String          Content;
    };

    struct StringList
    {
        StringListNode* First;
        StringListNode* Last;
        size_t          NodeCount;
        size_t          Size;
    };

    constexpr uint32 kInvalidUTF8 = 0xFFFD;

    inline size_t StringLength(String str)
    {
        return str.Size;
    }

    inline size_t StringLength(const char* str)
    {
        size_t length = 0;
        if (str)
        {
            while (*str)
            {
                ++length;
                ++str;
            }
        }

        return length;
    }

    inline bool IsValid(String str)
    {
        return str.Size > 0 && str.Data != nullptr && *str.Data;
    }

    inline String WrapString(const char* str)
    {
        String result = {};
        result.Data   = const_cast<char*>(str);
        result.Size   = str ? strlen(str) : 0;
        return result;
    }

    inline String FindChar(String str, char c)
    {
        String result = str;
        while (result.Size)
        {
            if (*result.Data != c)
            {
                ++result.Data;
                --result.Size;
            }
            else
            {
                return result;
            }
        }
        return {};
    }

    inline bool ContainsChar(String str, char c)
    {
        return IsValid(FindChar(str, c));
    }

    // Return:
    // - < 0 if str1 < str2
    // - = 0 : Both strings are equals
    // - > 0 if str1 > str2
    inline int32 StringCompare(String str1, String str2)
    {
        size_t len1   = StringLength(str1);
        size_t len2   = StringLength(str2);
        size_t minLen = Min(len1, len2);
        int32  result = MemCompare(CStr(str1), CStr(str2), minLen);
        if (result == 0)
        {
            if (len1 > len2)
            {
                return 1;
            }
            if (len1 < len2)
            {
                return -1;
            }
            return 0;
        }
        return result;
    }

    JULIET_API uint32 StepUTF8(String& inStr);
    JULIET_API String FindString(String strLeft, String strRight);

    // Case insensitive compare. Supports ASCII only
    // TODO: Support UNICODE
    extern JULIET_API int8 StringCompareCaseInsensitive(String str1, String str2);

    // Do not allocate anything, you must allocate your out buffer yourself
    // TODO: Version taking arena that can allocate
    // Do not take String type because we dont know the string encoding we are going from/to
    // src and dst will be casted based on the encoding.
    // size will correspond to the number of characters
    // Will convert \0 character if present.
    extern JULIET_API bool ConvertString(StringEncoding from, StringEncoding to, String src, StringBuffer& dst, bool nullTerminate);
    extern JULIET_API bool ConvertString(String from, String to, String src, StringBuffer& dst, bool nullTerminate);

    JULIET_API String StringCopy(NonNullPtr<Arena> arena, String str);
} // namespace Juliet

#ifdef UNIT_TEST
namespace Juliet::UnitTest
{
    inline void TestFindChar()
    {
        String s1 = ConstString("");
        String s2 = ConstString("abcdefabcdef");
        String s3 = ConstString("11111111111111111111");

        Assert(FindChar(s1, 'x').Data == nullptr);
        Assert(FindChar(s2, 'y').Data == nullptr);
        Assert(FindChar(s2, 'a').Data - s2.Data == 0);
        Assert(FindChar(s2, 'd').Data - s2.Data == 3);
        Assert(FindChar(s2, 'f').Data - s2.Data == 5);
        Assert(FindChar(s3, '1').Data - s3.Data == 0);
    }
} // namespace Juliet::UnitTest
#endif
