/*
 * Copyright (c) 2020-2022, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGUI/TextEditor.h>
#include <LibGfx/Color.h>
#include <LibSyntax/Highlighter.h>

namespace Syntax {

void Highlighter::highlight_matching_token_pair()
{
    auto& document = m_client->get_document();

    enum class Direction {
        Forward,
        Backward,
    };

    auto find_span_of_type = [&](auto i, u64 type, u64 not_type, Direction direction) -> Optional<size_t> {
        size_t nesting_level = 0;
        bool forward = direction == Direction::Forward;

        if (forward) {
            ++i;
            if (i >= document.spans().size())
                return {};
        } else {
            if (i == 0)
                return {};
            --i;
        }

        for (;;) {
            auto& span = document.spans().at(i);
            auto span_token_type = span.data;
            if (token_types_equal(span_token_type, not_type)) {
                ++nesting_level;
            } else if (token_types_equal(span_token_type, type)) {
                if (nesting_level-- <= 0)
                    return i;
            }

            if (forward) {
                ++i;
                if (i >= document.spans().size())
                    return {};
            } else {
                if (i == 0)
                    return {};
                --i;
            }
        }

        return {};
    };

    auto make_buddies = [&](int index0, int index1) {
        auto& buddy0 = document.spans()[index0];
        auto& buddy1 = document.spans()[index1];
        m_has_brace_buddies = true;
        m_brace_buddies[0].index = index0;
        m_brace_buddies[1].index = index1;
        m_brace_buddies[0].span_backup = buddy0;
        m_brace_buddies[1].span_backup = buddy1;
        buddy0.attributes.background_color = Color::DarkCyan;
        buddy1.attributes.background_color = Color::DarkCyan;
        buddy0.attributes.color = Color::White;
        buddy1.attributes.color = Color::White;
        m_client->do_update();
    };

    auto pairs = matching_token_pairs();

    for (size_t i = 0; i < document.spans().size(); ++i) {
        auto& span = const_cast<GUI::TextDocumentSpan&>(document.spans().at(i));
        auto token_type = span.data;

        for (auto& pair : pairs) {
            if (token_types_equal(token_type, pair.open) && span.range.start() == m_client->get_cursor()) {
                auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward);
                if (buddy.has_value())
                    make_buddies(i, buddy.value());
                return;
            }
        }

        for (auto& pair : pairs) {
            if (token_types_equal(token_type, pair.close) && span.range.end() == m_client->get_cursor()) {
                auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward);
                if (buddy.has_value())
                    make_buddies(i, buddy.value());
                return;
            }
        }
    }
}

void Highlighter::attach(HighlighterClient& client)
{
    VERIFY(!m_client);
    m_client = &client;
}

void Highlighter::detach()
{
    m_client = nullptr;
}

void Highlighter::cursor_did_change()
{
    auto& document = m_client->get_document();
    if (m_has_brace_buddies) {
        if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < static_cast<int>(document.spans().size()))
            document.set_span_at_index(m_brace_buddies[0].index, m_brace_buddies[0].span_backup);
        if (m_brace_buddies[1].index >= 0 && m_brace_buddies[1].index < static_cast<int>(document.spans().size()))
            document.set_span_at_index(m_brace_buddies[1].index, m_brace_buddies[1].span_backup);
        m_has_brace_buddies = false;
        m_client->do_update();
    }
    highlight_matching_token_pair();
}

Vector<Highlighter::MatchingTokenPair> Highlighter::matching_token_pairs() const
{
    auto own_pairs = matching_token_pairs_impl();
    own_pairs.ensure_capacity(own_pairs.size() + m_nested_token_pairs.size());
    for (auto& nested_pair : m_nested_token_pairs)
        own_pairs.append(nested_pair);
    return own_pairs;
}

void Highlighter::register_nested_token_pairs(Vector<MatchingTokenPair> pairs)
{
    for (auto& pair : pairs)
        m_nested_token_pairs.set(pair);
}

StringView language_to_string(Language language)
{
    switch (language) {
    case Language::Cpp:
        return "C++"sv;
    case Language::CSS:
        return "CSS"sv;
    case Language::GitCommit:
        return "Git"sv;
    case Language::GML:
        return "GML"sv;
    case Language::HTML:
        return "HTML"sv;
    case Language::INI:
        return "INI"sv;
    case Language::JavaScript:
        return "JavaScript"sv;
    case Language::PlainText:
        return "Plain Text"sv;
    case Language::Shell:
        return "Shell"sv;
    case Language::SQL:
        return "SQL"sv;
    }
    VERIFY_NOT_REACHED();
}

StringView common_language_extension(Language language)
{
    switch (language) {
    case Language::Cpp:
        return "cpp"sv;
    case Language::CSS:
        return "css"sv;
    case Language::GitCommit:
        return {};
    case Language::GML:
        return "gml"sv;
    case Language::HTML:
        return "html"sv;
    case Language::INI:
        return "ini"sv;
    case Language::JavaScript:
        return "js"sv;
    case Language::PlainText:
        return "txt"sv;
    case Language::Shell:
        return "sh"sv;
    case Language::SQL:
        return "sql"sv;
    }
    VERIFY_NOT_REACHED();
}

}