#if defined(WITH_RIVE_TEXT) && !defined(RIVE_NO_CORETEXT)

// #if defined(TARGET_OS_IPHONE) || defined(TARGET_OS_SIMULATOR)
// #else
#include <CoreText/CoreText.h>
#include <CoreText/CTFontManager.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
// #endif

#include "rive/math/math_types.hpp"
#include "rive/text_engine.hpp"
#include "rive/text/font_hb.hpp"
#include "rive/text/utf.hpp"

#include "hb-coretext.h"
#include "hb-ot.h"

constexpr int kStdScale = 2048;
constexpr float gInvScale = 1.0f / kStdScale;

class CoreTextHBFont : public HBFont
{
private:
    bool m_useSystemShaper;
    uint16_t m_weight;
    uint8_t m_width;

public:
    CoreTextHBFont(hb_font_t* font,
                   bool useSystemShaper,
                   uint16_t weight,
                   uint8_t width);

    void shapeFallbackRun(rive::SimpleArrayBuilder<rive::GlyphRun>& gruns,
                          const rive::Unichar text[],
                          const unsigned textStart,
                          const rive::TextRun& textRun,
                          const rive::TextRun& originalTextRun,
                          const uint32_t fallbackIndex) override;

    rive::RawPath getPath(rive::GlyphID glyph) const override;
};

rive::rcp<rive::Font> HBFont::FromSystem(void* systemFont,
                                         bool useSystemShaper,
                                         uint16_t weight,
                                         uint8_t width)
{
    auto ctFont = (CTFontRef)systemFont;
    bool isCopy = false;
    if (CTFontGetSize(ctFont) != kStdScale)
    {
        // Need the font sized at our magic scale so we can extract normalized
        // path data.
        ctFont =
            CTFontCreateCopyWithAttributes(ctFont, kStdScale, nullptr, nullptr);
        isCopy = true;
    }
    auto font = hb_coretext_font_create(ctFont);
    if (isCopy)
    {
        CFRelease(ctFont);
    }
    if (font)
    {
        return rive::rcp<rive::Font>(
            new CoreTextHBFont(font, useSystemShaper, weight, width));
    }
    return nullptr;
}

template <size_t N, typename T> class AutoSTArray
{
    T m_storage[N];
    T* m_ptr;
    const size_t m_count;

public:
    AutoSTArray(size_t n) : m_count(n)
    {
        m_ptr = m_storage;
        if (n > N)
        {
            m_ptr = new T[n];
        }
    }
    ~AutoSTArray()
    {
        if (m_ptr != m_storage)
        {
            delete[] m_ptr;
        }
    }

    T* data() const { return m_ptr; }

    T& operator[](size_t index)
    {
        assert(index < m_count);
        return m_ptr[index];
    }
};
template <typename T> class AutoCF
{
    T m_obj;

public:
    AutoCF(T obj = nullptr) : m_obj(obj) {}
    AutoCF(const AutoCF& other)
    {
        if (other.m_obj)
        {
            CFRetain(other.m_obj);
        }
        m_obj = other.m_obj;
    }
    AutoCF(AutoCF&& other)
    {
        m_obj = other.m_obj;
        other.m_obj = nullptr;
    }
    ~AutoCF()
    {
        if (m_obj)
        {
            CFRelease(m_obj);
        }
    }

    AutoCF& operator=(const AutoCF& other)
    {
        if (m_obj != other.m_obj)
        {
            if (other.m_obj)
            {
                CFRetain(other.m_obj);
            }
            if (m_obj)
            {
                CFRelease(m_obj);
            }
            m_obj = other.m_obj;
        }
        return *this;
    }

    void reset(T obj)
    {
        if (obj != m_obj)
        {
            if (m_obj)
            {
                CFRelease(m_obj);
            }
            m_obj = obj;
        }
    }

    operator T() const { return m_obj; }
    operator bool() const { return m_obj != nullptr; }
    T get() const { return m_obj; }
};

struct AutoUTF16
{
    std::vector<uint16_t> array;

    AutoUTF16(const rive::Unichar uni[], int count)
    {
        array.reserve(count);
        for (int i = 0; i < count; ++i)
        {
            uint16_t tmp[2];
            int n = rive::UTF::ToUTF16(uni[i], tmp);

            for (int i = 0; i < n; ++i)
            {
                array.push_back(tmp[i]);
            }
        }
    }
};

static void apply_element(void* ctx, const CGPathElement* element)
{
    auto path = (rive::RawPath*)ctx;
    const CGPoint* points = element->points;

    switch (element->type)
    {
        case kCGPathElementMoveToPoint:
            path->moveTo((float)points[0].x * gInvScale,
                         (float)-points[0].y * gInvScale);
            break;

        case kCGPathElementAddLineToPoint:
            path->lineTo((float)points[0].x * gInvScale,
                         (float)-points[0].y * gInvScale);
            break;

        case kCGPathElementAddQuadCurveToPoint:

            path->quadToCubic((float)points[0].x * gInvScale,
                              (float)-points[0].y * gInvScale,
                              (float)points[1].x * gInvScale,
                              (float)-points[1].y * gInvScale);
            break;

        case kCGPathElementAddCurveToPoint:

            path->cubicTo((float)points[0].x * gInvScale,
                          (float)-points[0].y * gInvScale,
                          (float)points[1].x * gInvScale,
                          (float)-points[1].y * gInvScale,
                          (float)points[2].x * gInvScale,
                          (float)-points[2].y * gInvScale);
            break;

        case kCGPathElementCloseSubpath:
            path->close();
            break;

        default:
            assert(false);
            break;
    }
}

CoreTextHBFont::CoreTextHBFont(hb_font_t* font,
                               bool useSystemShaper,
                               uint16_t weight,
                               uint8_t width) :
    m_useSystemShaper(useSystemShaper),
    m_weight(weight),
    m_width(width),
    HBFont(font)
{
    hb_variation_t variation_data[2];
    variation_data[0].tag = HB_OT_TAG_VAR_AXIS_WEIGHT;
    variation_data[0].value = weight;
    variation_data[1].tag = HB_OT_TAG_VAR_AXIS_WIDTH;
    variation_data[1].value = width;
    hb_font_set_variations(font, variation_data, 2);
}

void CoreTextHBFont::shapeFallbackRun(
    rive::SimpleArrayBuilder<rive::GlyphRun>& gruns,
    const rive::Unichar text[],
    const unsigned textStart,
    const rive::TextRun& textRun,
    const rive::TextRun& originalTextRun,
    const uint32_t fallbackIndex)
{
    if (!m_useSystemShaper)
    {
        HBFont::shapeFallbackRun(
            gruns, text, textStart, textRun, originalTextRun, fallbackIndex);
        return;
    }

    CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font);

    AutoUTF16 utf16(&text[textStart], textRun.unicharCount);

    assert(utf16.array.size() == textRun.unicharCount);

    AutoCF<CFStringRef> string = CFStringCreateWithCharactersNoCopy(
        nullptr, utf16.array.data(), utf16.array.size(), kCFAllocatorNull);

    AutoCF<CFMutableDictionaryRef> attr =
        CFDictionaryCreateMutable(kCFAllocatorDefault,
                                  0,
                                  &kCFTypeDictionaryKeyCallBacks,
                                  &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(attr.get(), kCTFontAttributeName, ctFont);

    AutoCF<CFMutableAttributedStringRef> attrString =
        CFAttributedStringCreateMutable(kCFAllocatorDefault,
                                        textRun.unicharCount);
    CFAttributedStringReplaceString(
        attrString.get(), CFRangeMake(0, 0), string.get());

    AutoCF<CFNumberRef> level_number =
        CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &textRun.level);
    AutoCF<CFDictionaryRef> options = CFDictionaryCreate(
        kCFAllocatorDefault,
        (const void**)&kCTTypesetterOptionForcedEmbeddingLevel,
        (const void**)&level_number,
        1,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);

    AutoCF<CTTypesetterRef> typesetter =
        CTTypesetterCreateWithAttributedStringAndOptions(attrString.get(),
                                                         options.get());

    AutoCF<CTLineRef> line = CTTypesetterCreateLine(typesetter.get(), {0, 0});

    CFArrayRef run_array = CTLineGetGlyphRuns(line.get());
    CFIndex runCount = CFArrayGetCount(run_array);
    bool isEvenLevel = textRun.level % 2 == 0;
    for (CFIndex runIndex = 0; runIndex < runCount; runIndex++)
    {
        CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, runIndex);

        if (CFIndex count = CTRunGetGlyphCount(run))
        {
            rive::GlyphRun gr(count);

            // Because CoreText will automatically do its own font fallbacks
            // we need to detect that it's trying to use a different font.
            CFDictionaryRef attributes = CTRunGetAttributes(run);
            CTFontRef runCtFont = static_cast<CTFontRef>(
                CFDictionaryGetValue(attributes, kCTFontAttributeName));
            const float scale = textRun.size / (float)CTFontGetSize(runCtFont);
            if (!CFEqual(runCtFont, ctFont))
            {
                // Get the original font's traits and create a fallback font
                // with the same traits. In some cases, CoreText will use a
                // different font for the fallback, but the fallback font will
                // not have the same traits (e.g weight) as the original font.
                CTFontSymbolicTraits originalTraits =
                    CTFontGetSymbolicTraits(ctFont);
                CTFontRef fallbackFont =
                    CTFontCreateCopyWithSymbolicTraits(runCtFont,
                                                       CTFontGetSize(runCtFont),
                                                       nullptr,
                                                       originalTraits,
                                                       originalTraits);
                if (!fallbackFont)
                {
                    fallbackFont = runCtFont;
                }

                gr.font = HBFont::FromSystem(
                    (void*)fallbackFont, true, m_weight, m_width);

                // If the fallback font is not the same as the original run
                // font, we need to release it since we made a copy of it
                if ((void*)fallbackFont != (void*)runCtFont)
                {
                    CFRelease(fallbackFont);
                }
            }
            else
            {
                gr.font = textRun.font;
            }
            gr.size = textRun.size;
            gr.lineHeight = textRun.lineHeight;
            gr.letterSpacing = textRun.letterSpacing;
            gr.styleId = textRun.styleId;
            gr.level = textRun.level;

            CTRunGetGlyphs(run, {0, count}, gr.glyphs.data());
            if (!isEvenLevel)
            {
                std::reverse(gr.glyphs.begin(), gr.glyphs.end());
            }

            AutoSTArray<1024, CFIndex> indices(count);
            AutoSTArray<1024, CGSize> advances(count);

            CTRunGetAdvances(run, {0, count}, advances.data());
            CTRunGetStringIndices(run, {0, count}, indices.data());

            CFIndex reverseIndex = count - 1;
            for (CFIndex i = 0; i < count; ++i)
            {
                CFIndex glyphIndex = isEvenLevel ? i : reverseIndex;
                float advance =
                    (float)(advances[i].width * scale) + textRun.letterSpacing;
                gr.xpos[glyphIndex] = gr.advances[glyphIndex] = advance;
                gr.textIndices[glyphIndex] =
                    rive::math::lossless_numeric_cast<uint32_t>(
                        textStart +
                        indices[i]); // utf16 offsets, will fix-up later

                gr.offsets[glyphIndex] = rive::Vec2D(0.0f, 0.0f);
                reverseIndex--;
            }
            gr.xpos[count] = 0;

            gruns.add(std::move(gr));
        }
    }
}

rive::RawPath CoreTextHBFont::getPath(rive::GlyphID glyph) const
{
    // When we use the system shaper (coretext) get the glyphs from there
    // too.
    if (m_useSystemShaper)
    {
        CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font);
        if (ctFont)
        {
            AutoCF<CGPathRef> cgPath =
                CTFontCreatePathForGlyph(ctFont, glyph, nullptr);

            if (cgPath)
            {
                rive::RawPath rpath;
                CGPathApply(cgPath.get(), &rpath, apply_element);
                return rpath;
            }
        }
    }

    rive::RawPath rpath = HBFont::getPath(glyph);

    // We didn't use the system shaper, but harfbuzz failed to extract the
    // glyphs. Try getting them from the system.
    if (rpath.empty() && !m_useSystemShaper)
    {
        CTFontRef ctFont = hb_coretext_font_get_ct_font(m_font);
        if (ctFont)
        {
            AutoCF<CGPathRef> cgPath =
                CTFontCreatePathForGlyph(ctFont, glyph, nullptr);

            if (cgPath)
            {
                rive::RawPath rpath;
                CGPathApply(cgPath.get(), &rpath, apply_element);
                return rpath;
            }
        }
    }
    return rpath;
}
#endif
