/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef SkUnicode_DEFINED
#define SkUnicode_DEFINED
#include "include/core/SkRefCnt.h"
#include "include/core/SkSpan.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkTArray.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkUTF.h"
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <vector>
namespace sknonstd { template <typename T> struct is_bitmask_enum; }

#if !defined(SKUNICODE_IMPLEMENTATION)
    #define SKUNICODE_IMPLEMENTATION 0
#endif

#if !defined(SKUNICODE_API)
    #if defined(SKUNICODE_DLL)
        #if defined(_MSC_VER)
            #if SKUNICODE_IMPLEMENTATION
                #define SKUNICODE_API __declspec(dllexport)
            #else
                #define SKUNICODE_API __declspec(dllimport)
            #endif
        #else
            #define SKUNICODE_API __attribute__((visibility("default")))
        #endif
    #else
        #define SKUNICODE_API
    #endif
#endif

class SKUNICODE_API SkBidiIterator {
public:
    typedef int32_t Position;
    typedef uint8_t Level;
    struct Region {
        Region(Position start, Position end, Level level)
            : start(start), end(end), level(level) { }
        Position start;
        Position end;
        Level level;
    };
    enum Direction {
        kLTR,
        kRTL,
    };
    SkBidiIterator() = default;
    SkBidiIterator(const SkBidiIterator&) = default;
    SkBidiIterator& operator=(const SkBidiIterator&) = default;
    virtual ~SkBidiIterator() = default;
    virtual Position getLength() = 0;
    virtual Level getLevelAt(Position) = 0;
};

class SKUNICODE_API SkBreakIterator {
public:
    typedef int32_t Position;
    typedef int32_t Status;
    SkBreakIterator() = default;
    SkBreakIterator(const SkBreakIterator&) = default;
    SkBreakIterator& operator=(const SkBreakIterator&) = default;
    virtual ~SkBreakIterator() = default;
    virtual Position first() = 0;
    virtual Position current() = 0;
    virtual Position next() = 0;
    virtual Status status() = 0;
    virtual bool isDone() = 0;
    virtual bool setText(const char utftext8[], int utf8Units) = 0;
    virtual bool setText(const char16_t utftext16[], int utf16Units) = 0;
};

class SKUNICODE_API SkUnicode : public SkRefCnt {
    public:
        enum CodeUnitFlags {
            kNoCodeUnitFlag = 0x00,
            kPartOfWhiteSpaceBreak = 0x01,
            kGraphemeStart = 0x02,
            kSoftLineBreakBefore = 0x04,
            kHardLineBreakBefore = 0x08,
            kPartOfIntraWordBreak = 0x10,
            kControl = 0x20,
            kTabulation = 0x40,
            kGlyphClusterStart = 0x80,
            kIdeographic = 0x100,
            kEmoji = 0x200,
            kWordBreak = 0x400,
            kSentenceBreak = 0x800,
        };
        enum class TextDirection {
            kLTR,
            kRTL,
        };
        typedef size_t Position;
        typedef uint8_t BidiLevel;
        struct BidiRegion {
            BidiRegion(Position start, Position end, BidiLevel level)
              : start(start), end(end), level(level) { }
            Position start;
            Position end;
            BidiLevel level;
        };
        enum class LineBreakType {
            kSoftLineBreak = 0,
            kHardLineBreak = 100,
        };

        enum class BreakType { kWords, kGraphemes, kLines, kSentences };
        struct LineBreakBefore {
            LineBreakBefore(Position pos, LineBreakType breakType)
              : pos(pos), breakType(breakType) { }
            Position pos;
            LineBreakType breakType;
        };

        ~SkUnicode() override = default;

        // deprecated
        virtual SkString toUpper(const SkString&) = 0;
        virtual SkString toUpper(const SkString&, const char* locale) = 0;

        virtual bool isControl(SkUnichar utf8) = 0;
        virtual bool isWhitespace(SkUnichar utf8) = 0;
        virtual bool isSpace(SkUnichar utf8) = 0;
        virtual bool isTabulation(SkUnichar utf8) = 0;
        virtual bool isHardBreak(SkUnichar utf8) = 0;
        /**
         * Returns if a code point may start an emoji sequence.
         * Returns true for '#', '*', and '0'-'9' since they may start an emoji sequence.
         * To determine if a list of code points begins with an emoji sequence, use
         * getEmojiSequence.
         **/
        virtual bool isEmoji(SkUnichar utf8) = 0;
        virtual bool isEmojiComponent(SkUnichar utf8) = 0;
        virtual bool isEmojiModifierBase(SkUnichar utf8) = 0;
        virtual bool isEmojiModifier(SkUnichar utf8) = 0;
        virtual bool isRegionalIndicator(SkUnichar utf8) = 0;
        virtual bool isIdeographic(SkUnichar utf8) = 0;

        // Methods used in SkShaper and SkText
        virtual std::unique_ptr<SkBidiIterator> makeBidiIterator
            (const uint16_t text[], int count, SkBidiIterator::Direction) = 0;
        virtual std::unique_ptr<SkBidiIterator> makeBidiIterator
            (const char text[], int count, SkBidiIterator::Direction) = 0;
        virtual std::unique_ptr<SkBreakIterator> makeBreakIterator
            (const char locale[], BreakType breakType) = 0;
        virtual std::unique_ptr<SkBreakIterator> makeBreakIterator(BreakType type) = 0;

        // Methods used in SkParagraph
        static bool hasTabulationFlag(SkUnicode::CodeUnitFlags flags);
        static bool hasHardLineBreakFlag(SkUnicode::CodeUnitFlags flags);
        static bool hasSoftLineBreakFlag(SkUnicode::CodeUnitFlags flags);
        static bool hasGraphemeStartFlag(SkUnicode::CodeUnitFlags flags);
        static bool hasControlFlag(SkUnicode::CodeUnitFlags flags);
        static bool hasPartOfWhiteSpaceBreakFlag(SkUnicode::CodeUnitFlags flags);

        static bool extractBidi(const char utf8[],
                                int utf8Units,
                                TextDirection dir,
                                std::vector<BidiRegion>* bidiRegions);
        virtual bool getBidiRegions(const char utf8[],
                                    int utf8Units,
                                    TextDirection dir,
                                    std::vector<BidiRegion>* results) = 0;
        // Returns results in utf16
        virtual bool getWords(const char utf8[], int utf8Units, const char* locale,
                              std::vector<Position>* results) = 0;
        virtual bool getUtf8Words(const char utf8[],
                                  int utf8Units,
                                  const char* locale,
                                  std::vector<Position>* results) = 0;
        virtual bool getSentences(const char utf8[],
                                  int utf8Units,
                                  const char* locale,
                                  std::vector<Position>* results) = 0;
        virtual bool computeCodeUnitFlags(
                char utf8[], int utf8Units, bool replaceTabs,
                skia_private::TArray<SkUnicode::CodeUnitFlags, true>* results) = 0;
        virtual bool computeCodeUnitFlags(
                char16_t utf16[], int utf16Units, bool replaceTabs,
                skia_private::TArray<SkUnicode::CodeUnitFlags, true>* results) = 0;

        static SkString convertUtf16ToUtf8(const char16_t * utf16, int utf16Units);
        static SkString convertUtf16ToUtf8(const std::u16string& utf16);
        static std::u16string convertUtf8ToUtf16(const char* utf8, int utf8Units);
        static std::u16string convertUtf8ToUtf16(const SkString& utf8);

        template <typename Appender8, typename Appender16>
        static bool extractUtfConversionMapping(SkSpan<const char> utf8, Appender8&& appender8, Appender16&& appender16) {
            size_t size8 = 0;
            size_t size16 = 0;
            auto ptr = utf8.data();
            auto end = ptr + utf8.size();
            while (ptr < end) {

                size_t index = SkToSizeT(ptr - utf8.data());
                SkUnichar u = SkUTF::NextUTF8(&ptr, end);

                // All UTF8 code units refer to the same codepoint
                size_t next = SkToSizeT(ptr - utf8.data());
                for (auto i = index; i < next; ++i) {
                    //fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
                    appender16(size8);
                    ++size16;
                }
                //SkASSERT(fUTF16IndexForUTF8Index.size() == next);
                SkASSERT(size16 == next);
                if (size16 != next) {
                    return false;
                }

                // One or two UTF16 code units refer to the same codepoint
                uint16_t buffer[2];
                size_t count = SkUTF::ToUTF16(u, buffer);
                //fUTF8IndexForUTF16Index.emplace_back(index);
                appender8(index);
                ++size8;
                if (count > 1) {
                    //fUTF8IndexForUTF16Index.emplace_back(index);
                    appender8(index);
                    ++size8;
                }
            }
            //fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
            appender16(size8);
            ++size16;
            //fUTF8IndexForUTF16Index.emplace_back(fText.size());
            appender8(utf8.size());
            ++size8;

            return true;
        }

        template <typename Callback>
        void forEachCodepoint(const char* utf8, int32_t utf8Units, Callback&& callback) {
            const char* current = utf8;
            const char* end = utf8 + utf8Units;
            while (current < end) {
                auto before = current - utf8;
                SkUnichar unichar = SkUTF::NextUTF8(&current, end);
                if (unichar < 0) unichar = 0xFFFD;
                auto after = current - utf8;
                uint16_t buffer[2];
                size_t count = SkUTF::ToUTF16(unichar, buffer);
                callback(unichar, before, after, count);
            }
        }

        template <typename Callback>
        void forEachCodepoint(const char16_t* utf16, int32_t utf16Units, Callback&& callback) {
            const char16_t* current = utf16;
            const char16_t* end = utf16 + utf16Units;
            while (current < end) {
                auto before = current - utf16;
                SkUnichar unichar = SkUTF::NextUTF16((const uint16_t**)&current, (const uint16_t*)end);
                auto after = current - utf16;
                callback(unichar, before, after);
            }
        }

        template <typename Callback>
        void forEachBidiRegion(const uint16_t utf16[], int utf16Units, SkBidiIterator::Direction dir, Callback&& callback) {
            auto iter = makeBidiIterator(utf16, utf16Units, dir);
            const uint16_t* start16 = utf16;
            const uint16_t* end16 = utf16 + utf16Units;
            SkBidiIterator::Level currentLevel = 0;

            SkBidiIterator::Position pos16 = 0;
            while (pos16 <= iter->getLength()) {
                const auto nextPos16 = SkTo<SkBidiIterator::Position>(start16 - utf16);
                // The pointer difference is bound by utf16Units, and cannot overflow nextPos16.
                static_assert(std::numeric_limits<decltype(utf16Units)>::max() <=
                              std::numeric_limits<decltype(nextPos16)>::max());
                auto level = iter->getLevelAt(nextPos16);
                if (nextPos16 == 0) {
                    currentLevel = level;
                } else if (level != currentLevel) {
                    callback(pos16, nextPos16, currentLevel);
                    currentLevel = level;
                    pos16 = nextPos16;
                }
                if (start16 == end16) {
                    if (pos16 != nextPos16) {
                        callback(pos16, nextPos16, currentLevel);
                    }
                    return;
                }
                SkUTF::NextUTF16(&start16, end16);
            }
        }

        template <typename Callback>
        void forEachBreak(const char16_t utf16[], int utf16Units, SkUnicode::BreakType type, Callback&& callback) {
            auto iter = makeBreakIterator(type);
            iter->setText(utf16, utf16Units);
            auto pos = iter->first();
            do {
                callback(pos, iter->status());
                pos = iter->next();
            } while (!iter->isDone());
        }

        virtual void reorderVisual(const BidiLevel runLevels[], int levelsCount, int32_t logicalFromVisual[]) = 0;
};

namespace sknonstd {
template <> struct is_bitmask_enum<SkUnicode::CodeUnitFlags> : std::true_type {};
}  // namespace sknonstd

#endif // SkUnicode_DEFINED
