#include "rive/simple_array.hpp"
#include "catch.hpp"
#include "rive/text_engine.hpp"
#include "rive/text/font_hb.hpp"
#include "rive/text/utf.hpp"
#include "hb.h"
#include "hb-ot.h"
#include <string>

using namespace rive;

static rive::TextRun append(std::vector<rive::Unichar>* unichars,
                            rive::rcp<rive::Font> font,
                            float size,
                            const char text[])
{
    const uint8_t* ptr = (const uint8_t*)text;
    uint32_t n = 0;
    while (*ptr)
    {
        unichars->push_back(rive::UTF::NextUTF8(&ptr));
        n += 1;
    }
    return {std::move(font), size, -1.0f, 0.0f, n, 0};
}

static rcp<Font> loadFont(const char* filename)
{
    FILE* fp = fopen(filename, "rb");
    REQUIRE(fp != nullptr);

    fseek(fp, 0, SEEK_END);
    const size_t length = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    std::vector<uint8_t> bytes(length);
    REQUIRE(fread(bytes.data(), 1, length, fp) == length);
    fclose(fp);

    return HBFont::Decode(bytes);
}

static std::vector<rive::rcp<rive::Font>> fallbackFonts;
static rive::rcp<rive::Font> pickFallbackFont(const rive::Unichar missing,
                                              const uint32_t fallbackIndex,
                                              const rive::Font*)
{
    if (fallbackIndex > 0)
    {
        return nullptr;
    }
    size_t length = fallbackFonts.size();
    for (size_t i = fallbackIndex; i < length; i++)
    {
        HBFont* font = static_cast<HBFont*>(fallbackFonts[i].get());
        if (font->hasGlyph(missing))
        {
            return fallbackFonts[i];
        }
    }
    return nullptr;
}

TEST_CASE("Inspect Font Styles", "[text_styles]")
{
    struct TestCaseData
    {
        const char* fontPath;
        uint16_t expectedWeight;
        bool expectedItalic;
    };

    std::vector<TestCaseData> testCases = {
        {"assets/fonts/AdventPro-VariableFont_wdth,wght.ttf", 400, false},
        {"assets/fonts/Inter_18pt-Regular.ttf", 400, false},
        {"assets/fonts/Inter_28pt-Bold.ttf", 700, false},
        {"assets/fonts/OpenSans-Italic.ttf", 400, true},
        {"assets/fonts/OpenSans-ExtraBoldItalic.ttf", 800, true},
    };

    for (const auto& testCase : testCases)
    {
        SECTION(testCase.fontPath)
        {
            rive::rcp<Font> font = loadFont(testCase.fontPath);
            HBFont* hbFont = static_cast<HBFont*>(font.get());

            REQUIRE(hbFont->getWeight() == testCase.expectedWeight);
            REQUIRE(hbFont->isItalic() == testCase.expectedItalic);
        }
    }
}

TEST_CASE("fallback glyphs are found", "[text_fallback]")
{
    REQUIRE(fallbackFonts.empty());
    auto font = loadFont("assets/RobotoFlex.ttf");
    REQUIRE(font != nullptr);
    auto fallbackFont = loadFont("assets/IBMPlexSansArabic-Regular.ttf");
    REQUIRE(fallbackFont != nullptr);
    fallbackFonts.push_back(fallbackFont);

    Font::gFallbackProc = pickFallbackFont;

    std::vector<rive::TextRun> truns;
    std::vector<rive::Unichar> unichars;
    truns.push_back(append(&unichars, font, 32.0f, "لمفاتيح ABC DEF"));

    auto paragraphs = font->shapeText(unichars, truns);
    REQUIRE(paragraphs.size() == 1);
    paragraphs = SimpleArray<Paragraph>();
    REQUIRE(paragraphs.size() == 0);
    fallbackFonts.clear();
    Font::gFallbackProc = nullptr;
}

TEST_CASE("variable axis values can be read", "[text]")
{
    REQUIRE(fallbackFonts.empty());
    auto font = loadFont("assets/RobotoFlex.ttf");
    REQUIRE(font != nullptr);

    auto count = font->getAxisCount();

    bool hasWeight = false;
    for (uint16_t i = 0; i < count; i++)
    {
        auto axis = font->getAxis(i);
        if (axis.tag == 2003265652)
        {
            REQUIRE(axis.def == 400.0f);
            hasWeight = true;
            break;
        }
    }

    REQUIRE(hasWeight);

    float value = font->getAxisValue(2003265652);
    REQUIRE(value == 400.0f);

    REQUIRE(font->getAxisValue(2003072104) == 100.0f);

    rive::Font::Coord coord = {2003265652, 800.0f};
    rive::rcp<rive::Font> vfont =
        font->makeAtCoords(rive::Span<HBFont::Coord>(&coord, 1));
    REQUIRE(vfont->getAxisValue(2003265652) == 800.0f);

    rive::Font::Coord coord2 = {2003072104, 122.0f};
    rive::rcp<rive::Font> vfont2 =
        vfont->makeAtCoords(rive::Span<HBFont::Coord>(&coord2, 1));
    REQUIRE(vfont2->getAxisValue(2003072104) == 122.0f);
    // Should also still have the first axis value we set.
    REQUIRE(vfont2->getAxisValue(2003265652) == 800.0f);
}

static std::string tagToString(uint32_t tag)
{
    std::string tag_name;
    tag_name += ((char)((tag & 0xff000000) >> 24));
    tag_name += ((char)((tag & 0x00ff0000) >> 16));
    tag_name += ((char)((tag & 0x0000ff00) >> 8));
    tag_name += ((char)((tag & 0x000000ff)));
    return tag_name;
}

static bool hasTag(std::vector<std::string> featureStrings, std::string tag)
{
    return std::find(std::begin(featureStrings),
                     std::end(featureStrings),
                     tag) != std::end(featureStrings);
}

TEST_CASE("font features load as expected", "[text]")
{
    REQUIRE(fallbackFonts.empty());
    auto font = loadFont("assets/RobotoFlex.ttf");
    REQUIRE(font != nullptr);

    rive::SimpleArray<uint32_t> features = font->features();
    std::vector<std::string> featureStrings;
    for (auto feature : features)
    {
        featureStrings.push_back(tagToString(feature));
    }
    REQUIRE(features.size() == 7);

    REQUIRE(hasTag(featureStrings, "mkmk"));
    REQUIRE(hasTag(featureStrings, "kern"));
    REQUIRE(hasTag(featureStrings, "rvrn"));
    REQUIRE(hasTag(featureStrings, "mark"));
    REQUIRE(hasTag(featureStrings, "locl"));
    REQUIRE(hasTag(featureStrings, "pnum"));
    REQUIRE(hasTag(featureStrings, "liga"));
}
