// Modified from original
// Copyright 2006 Nemanja Trifunovic

/*
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/


#pragma once

#include <AzCore/std/string/utf8/core.h>

namespace Utf8::Unchecked
{
    // Octet iterator class which wraps a source iterator
    template <typename Iterator>
    class octet_iterator
    {
        using iterator_category = AZStd::bidirectional_iterator_tag;
        using value_type = AZ::u32;
        using difference_type = ptrdiff_t;
        using pointer = value_type*;
        using reference = value_type&;
        Iterator it;
    public:
        octet_iterator() = default;
        explicit octet_iterator(const Iterator& sourceIt)
            : it(sourceIt) {}
        // the default "big three" are OK
        Iterator base() const
        {
            return it;
        }
        value_type operator*() const
        {
            return from_utf8_sequence(it);
        }
        bool operator==(const octet_iterator& rhs) const
        {
            return it == rhs.it;
        }
        bool operator!=(const octet_iterator& rhs) const
        {
            return !operator==(rhs);
        }
        octet_iterator& operator++()
        {
            // Skip past the current unicode codepoint
            AZStd::advance(it, Internal::sequence_length(it));
            return *this;
        }
        octet_iterator operator++(int)
        {
            octet_iterator temp = *this;
            // Skip past the current unicode codepoint
            AZStd::advance(it, Internal::sequence_length(it));
            return temp;
        }
        octet_iterator& operator--()
        {
            while (Utf8::Internal::is_trail(*(--it)))
            {
            }
            return *this;
        }
        octet_iterator operator--(int)
        {
            octet_iterator temp = *this;
            while (Utf8::Internal::is_trail(*(--it)))
            {
            }
            return temp;
        }

        static Iterator to_utf8_sequence(AZ::u32 cp, Iterator& result, size_t& resultMaxSize)
        {
            if (cp < 0x80)
            {
                // one octet
                resultMaxSize -= 1; // no need to check the calling function will exit the loop
                *(result++) = static_cast<AZ::u8>(cp);
            }
            else if (cp < 0x800)
            {
                // two octets
                if (resultMaxSize >= 2)
                {
                    *(result++) = static_cast<AZ::u8>((cp >> 6) | 0xc0);
                    *(result++) = static_cast<AZ::u8>((cp & 0x3f) | 0x80);
                    resultMaxSize -= 2;
                }
                else
                {
                    resultMaxSize = 0;
                }
            }
            else if (cp < 0x10000)
            {
                // three octets
                if (resultMaxSize >= 3)
                {
                    *(result++) = static_cast<AZ::u8>((cp >> 12) | 0xe0);
                    *(result++) = static_cast<AZ::u8>(((cp >> 6) & 0x3f) | 0x80);
                    *(result++) = static_cast<AZ::u8>((cp & 0x3f) | 0x80);
                    resultMaxSize -= 3;
                }
                else
                {
                    resultMaxSize = 0;
                }
            }
            else
            {
                // four octets
                if (resultMaxSize >= 4)
                {
                    *(result++) = static_cast<AZ::u8>((cp >> 18) | 0xf0);
                    *(result++) = static_cast<AZ::u8>(((cp >> 12) & 0x3f) | 0x80);
                    *(result++) = static_cast<AZ::u8>(((cp >> 6) & 0x3f) | 0x80);
                    *(result++) = static_cast<AZ::u8>((cp & 0x3f) | 0x80);
                    resultMaxSize -= 4;
                }
                else
                {
                    resultMaxSize = 0;
                }
            }
            return result;
        }

        static AZ::u32 from_utf8_sequence(Iterator it)
        {
            AZ::u32 cp = Utf8::Internal::mask8(*it);
            switch (Utf8::Internal::sequence_length(it))
            {
            case 1:
                break;
            case 2:
                it++;
                cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
                break;
            case 3:
                ++it;
                cp = ((cp << 12) & 0xffff) + ((Utf8::Internal::mask8(*it) << 6) & 0xfff);
                ++it;
                cp += (*it) & 0x3f;
                break;
            case 4:
                ++it;
                cp = ((cp << 18) & 0x1fffff) + ((Utf8::Internal::mask8(*it) << 12) & 0x3ffff);
                ++it;
                cp += (Utf8::Internal::mask8(*it) << 6) & 0xfff;
                ++it;
                cp += (*it) & 0x3f;
                break;
            }
            ++it;
            return cp;
        }
    };

    template <typename u16bit_iterator, typename Utf8Iterator>
    Utf8Iterator utf16to8(u16bit_iterator start, u16bit_iterator end, Utf8Iterator result, size_t resultMaxSize)
    {
        while (start != end && resultMaxSize > 0)
        {
            AZ::u32 cp = Utf8::Internal::mask16(*start++);
            // Take care of surrogate pairs first
            if (Utf8::Internal::is_lead_surrogate(cp))
            {
                AZ::u32 trail_surrogate = Utf8::Internal::mask16(*start++);
                cp = (cp << 10) + trail_surrogate + Internal::SURROGATE_OFFSET;
            }
            octet_iterator<Utf8Iterator>::to_utf8_sequence(cp, result, resultMaxSize);
        }
        return result;
    }

    template <typename u16bit_iterator, typename Utf8Iterator>
    u16bit_iterator utf8to16(Utf8Iterator start, Utf8Iterator end, u16bit_iterator result, size_t resultMaxSize)
    {
        octet_iterator utf8Start(start);
        octet_iterator utf8Last(end);
        while (utf8Start != utf8Last && resultMaxSize > 0)
        {
            AZ::u32 cp = *utf8Start++;
            if (cp > 0xffff)
            {
                //make a surrogate pair
                if (resultMaxSize >= 2)
                {
                    *result++ = static_cast<AZ::u16>((cp >> 10) + Internal::LEAD_OFFSET);
                    *result++ = static_cast<AZ::u16>((cp & 0x3ff) + Internal::TRAIL_SURROGATE_MIN);
                    resultMaxSize -= 2;
                }
                else
                {
                    resultMaxSize = 0;
                }
            }
            else
            {
                *result++ = static_cast<AZ::u16>(cp);
                resultMaxSize -= 1;
            }
        }
        return result;
    }

    template <typename Utf8Iterator, typename u32bit_iterator>
    Utf8Iterator utf32to8(u32bit_iterator start, u32bit_iterator end, Utf8Iterator result, size_t resultMaxSize)
    {
        while (start != end && resultMaxSize > 0)
        {
            octet_iterator<Utf8Iterator>::to_utf8_sequence(*start++, result, resultMaxSize);
        }

        return result;
    }

    template <typename Utf8Iterator, typename u32bit_iterator>
    u32bit_iterator utf8to32(Utf8Iterator start, Utf8Iterator end, u32bit_iterator result, size_t resultMaxSize)
    {
        octet_iterator utf8Start(start);
        octet_iterator utf8Last(end);
        while (utf8Start != utf8Last && resultMaxSize > 0)
        {
            *result++ = *utf8Start++;
            --resultMaxSize;
        }

        return result;
    }

    static constexpr size_t utf8_codepoint_length(AZ::u32 cp)
    {
        if (cp < 0x80)
        {
            return 1;
        }
        else if (cp < 0x800)
        {
            return 2;
        }
        else if (cp < 0x10000)
        {
            return 3;
        }
        return 4;
    }

    template <typename u16bit_iterator>
    size_t utf16ToUtf8BytesRequired(u16bit_iterator start, u16bit_iterator end)
    {
        size_t bytesRequired = 0;
        while (start != end)
        {
            AZ::u32 cp = Utf8::Internal::mask16(*start++);
            // Take care of surrogate pairs first
            if (Utf8::Internal::is_lead_surrogate(cp))
            {
                AZ::u32 trail_surrogate = Utf8::Internal::mask16(*start++);
                cp = (cp << 10) + trail_surrogate + Internal::SURROGATE_OFFSET;
            }
            bytesRequired += utf8_codepoint_length(cp);
        }
        return bytesRequired;
    }

    template <typename u32bit_iterator>
    size_t utf32ToUtf8BytesRequired(u32bit_iterator start, u32bit_iterator end)
    {
        size_t bytesRequired = 0;
        while (start != end)
        {
            bytesRequired += utf8_codepoint_length(*start++);
        }
        return bytesRequired;
    }


} // namespace Utf8::Unchecked


