#include "rive/text/text.hpp"
#include "rive/text/text_modifier_range.hpp"
#include "rive/text/text_modifier_group.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/text/glyph_lookup.hpp"
#include "rive/animation/cubic_interpolator_component.hpp"
#include "rive/core_context.hpp"

using namespace rive;

StatusCode TextModifierRange::onAddedDirty(CoreContext* context)
{
    StatusCode code = Super::onAddedDirty(context);
    if (code != StatusCode::Ok)
    {
        return code;
    }

    if (runId() != Core::emptyId)
    {
        auto coreObject = context->resolve(runId());
        if (coreObject == nullptr || !coreObject->is<TextValueRun>())
        {
            return StatusCode::MissingObject;
        }
        m_run = static_cast<TextValueRun*>(coreObject);
    }

    if (parent() != nullptr && parent()->is<TextModifierGroup>())
    {
        parent()->as<TextModifierGroup>()->addModifierRange(this);
        return StatusCode::Ok;
    }

    return StatusCode::MissingObject;
}

void TextModifierRange::addChild(Component* component)
{
    Super::addChild(component);
    if (component->is<CubicInterpolatorComponent>())
    {
        // mark this as the cubic interpolator.
        m_interpolator = component->as<CubicInterpolatorComponent>();
    }
}

void TextModifierRange::clearRangeMap() { m_rangeMapper.clear(); }

void TextModifierRange::computeRange(Span<const Unichar> text,
                                     const SimpleArray<Paragraph>& shape,
                                     const SimpleArray<SimpleArray<GlyphLine>>& lines,
                                     const GlyphLookup& glyphLookup)
{
    // Check if the range mapper is still valid.
    if (!m_rangeMapper.empty())
    {
        return;
    }
    uint32_t start = 0;
    uint32_t end = (uint32_t)text.size();
    if (m_run != nullptr)
    {
        start = m_run->offset();
        end = start + m_run->length();
    }
    switch (units())
    {
        case TextRangeUnits::charactersExcludingSpaces:
            m_rangeMapper.fromCharacters(text, start, end, glyphLookup, true);
            break;
        case TextRangeUnits::words:
            m_rangeMapper.fromWords(text, start, end);
            break;
        case TextRangeUnits::lines:
            m_rangeMapper.fromLines(text, start, end, shape, lines, glyphLookup);
            break;
        default:
            m_rangeMapper.fromCharacters(text, start, end, glyphLookup);
            break;
    }
}

float TextModifierRange::coverageAt(float t)
{
    float c = 0.0f;
    if (m_indexTo >= m_indexFrom)
    {
        if (t < m_indexFrom || t > m_indexTo)
        {
            c = 0.0f;
        }
        else if (t < m_indexFalloffFrom)
        {
            float range = std::max(0.0f, m_indexFalloffFrom - m_indexFrom);
            c = range == 0.0f ? 1.0f : std::max(0.0f, t - m_indexFrom) / range;
            if (m_interpolator != nullptr)
            {
                c = m_interpolator->transform(c);
            }
        }
        else if (t > m_indexFalloffTo)
        {
            float range = std::max(0.0f, m_indexTo - m_indexFalloffTo);
            c = range == 0.0f ? 1.0f : 1.0f - std::min(1.0f, (t - m_indexFalloffTo) / range);
            if (m_interpolator != nullptr)
            {
                c = m_interpolator->transform(c);
            }
        }
        else
        {
            c = 1.0f;
        }
    }
    return c;
}

void TextModifierRange::computeCoverage(Span<float> coverage)
{
    if (m_rangeMapper.empty())
    {
        return;
    }

    // Compute range mapped coverage.
    uint32_t count = m_rangeMapper.unitCount();
    switch (type())
    {
        case TextRangeType::unitIndex:
            m_indexFrom = offsetModifyFrom();
            m_indexTo = offsetModifyTo();
            m_indexFalloffFrom = offsetFalloffFrom();
            m_indexFalloffTo = offsetFalloffTo();
            break;
        case TextRangeType::percentage:
            m_indexFrom = count * offsetModifyFrom();
            m_indexTo = count * offsetModifyTo();
            m_indexFalloffFrom = count * offsetFalloffFrom();
            m_indexFalloffTo = count * offsetFalloffTo();
            break;
    }

    TextRangeMode rangeMode = mode();
    for (uint32_t unitIndex = 0; unitIndex < count; unitIndex++)
    {
        uint32_t unitLength = (uint32_t)m_rangeMapper.unitLength(unitIndex);
        uint32_t characterIndex = m_rangeMapper.unitCharacterIndex(unitIndex);
        float c = strength() * coverageAt(unitIndex + 0.5f);
        for (uint32_t i = 0; i < unitLength; i++)
        {
            assert((characterIndex + i) < (uint32_t)coverage.size());
            float current = coverage[characterIndex + i];
            switch (rangeMode)
            {
                case TextRangeMode::add:
                    current += c;
                    break;
                case TextRangeMode::subtract:
                    current -= c;
                    break;
                case TextRangeMode::max:
                    current = std::max(current, c);
                    break;
                case TextRangeMode::min:
                    current = std::min(current, c);
                    break;
                case TextRangeMode::multiply:
                    current *= c;
                    break;
                case TextRangeMode::difference:
                    current = std::abs(current - c);
                    break;
            }
            coverage[characterIndex + i] =
                clamp() ? std::max(0.0f, std::min(1.0f, current)) : current;
        }

        // Set space between units coverage to 0.
        if (unitIndex + 1 < m_rangeMapper.unitCharacterIndexCount())
        {
            uint32_t nextCharacterIndex = m_rangeMapper.unitCharacterIndex(unitIndex + 1);
            for (uint32_t i = characterIndex + unitLength; i < nextCharacterIndex; i++)
            {
                coverage[i] = 0;
            }
        }
    }
}

void TextModifierRange::modifyFromChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::modifyToChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::strengthChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::unitsValueChanged()
{
    parent()->as<TextModifierGroup>()->rangeTypeChanged();
}
void TextModifierRange::typeValueChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::modeValueChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::clampChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::falloffFromChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::falloffToChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }
void TextModifierRange::offsetChanged() { parent()->as<TextModifierGroup>()->rangeChanged(); }

bool TextModifierRange::needsShape() const { return units() == TextRangeUnits::lines; }

void RangeMapper::clear()
{
    m_unitCharacterIndices.clear();
    m_unitLengths.clear();
}

float RangeMapper::unitToCharacterRange(float word) const
{
    if (m_unitCharacterIndices.empty())
    {
        return 0.0f;
    }
    float clampedUnit = std::min(std::max(word, 0.0f), (float)(m_unitCharacterIndices.size() - 1));
    size_t intUnit = (size_t)clampedUnit;
    float characters = (float)m_unitCharacterIndices[intUnit];
    if (intUnit < m_unitLengths.size())
    {
        float fraction = clampedUnit - intUnit;
        characters += m_unitLengths[intUnit] * fraction;
    }
    return characters;
}

void RangeMapper::addRange(uint32_t indexFrom,
                           uint32_t indexTo,
                           uint32_t startOffset,
                           uint32_t endOffset)
{
    if (indexTo > startOffset && endOffset > indexFrom)
    {
        uint32_t actualStart = std::max(startOffset, indexFrom);
        uint32_t actualEnd = std::min(endOffset, indexTo);
        if (actualEnd > actualStart)
        {
            m_unitCharacterIndices.push_back(actualStart);
            m_unitLengths.push_back(actualEnd - actualStart);
        }
    }
}

void RangeMapper::fromWords(Span<const Unichar> text, uint32_t start, uint32_t end)
{
    if (text.empty())
    {
        return;
    }

    bool wantWhiteSpace = false;
    uint32_t characterCount = 0;
    uint32_t index = 0;
    uint32_t indexFrom = 0;
    for (Unichar unit : text)
    {
        if (wantWhiteSpace == isWhiteSpace(unit))
        {
            if (!wantWhiteSpace)
            {
                indexFrom = index;
            }
            else
            {
                addRange(indexFrom, indexFrom + characterCount, start, end);

                characterCount = 0;
            }
            wantWhiteSpace = !wantWhiteSpace;
        }
        if (wantWhiteSpace)
        {
            characterCount++;
        }
        index++;
    }
    if (characterCount > 0)
    {
        addRange(indexFrom, indexFrom + characterCount, start, end);
    }
    m_unitCharacterIndices.push_back(end);
}

void RangeMapper::fromCharacters(Span<const Unichar> text,
                                 uint32_t start,
                                 uint32_t end,
                                 const GlyphLookup& glyphLookup,
                                 bool withoutSpaces)
{
    if (text.empty())
    {
        return;
    }
    for (uint32_t i = start; i < end;)
    {
        Unichar unit = text[i];
        if (withoutSpaces && isWhiteSpace(unit))
        {
            i++;
            continue;
        }
        // Don't break ligated glyphs.
        uint32_t codePoints = glyphLookup.count(i);
        m_unitCharacterIndices.push_back(i);
        m_unitLengths.push_back(codePoints);
        i += codePoints;
    }

    m_unitCharacterIndices.push_back(end);
}

void RangeMapper::fromLines(Span<const Unichar> text,
                            uint32_t start,
                            uint32_t end,
                            const SimpleArray<Paragraph>& shape,
                            const SimpleArray<SimpleArray<GlyphLine>>& lines,
                            const GlyphLookup& glyphLookup)
{
    if (text.empty())
    {
        return;
    }
    uint32_t paragraphIndex = 0;
    for (const SimpleArray<GlyphLine>& paragraphLines : lines)
    {
        const Paragraph& paragraph = shape[paragraphIndex++];
        const SimpleArray<GlyphRun>& glyphRuns = paragraph.runs;
        for (const GlyphLine& line : paragraphLines)
        {
            const GlyphRun& rf = glyphRuns[line.startRunIndex];
            uint32_t indexFrom = rf.textIndices[line.startGlyphIndex];

            const GlyphRun& rt = glyphRuns[line.endRunIndex];
            uint32_t endGlyphIndex = line.endGlyphIndex == 0 ? 0 : line.endGlyphIndex - 1;
            uint32_t indexTo = rt.textIndices[endGlyphIndex];
            indexTo += glyphLookup.count(indexTo);

            addRange(indexFrom, indexTo, start, end);
        }
    }
    m_unitCharacterIndices.push_back(end);
}
