#include "test_qmarkdowntextedit.h"

#include <QApplication>
#include <QTextBlock>
#include <QTextLayout>
#include <QtTest>

#include "libraries/qmarkdowntextedit/qmarkdowntextedit.h"
#include "utils/gui.h"

namespace {
QTextCharFormat formatAt(const QTextBlock &block, int position) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    const auto formats = block.layout()->additionalFormats();
#else
    const auto formats = block.layout()->formats();
#endif
    for (const QTextLayout::FormatRange &range : formats) {
        if (position >= range.start && position < range.start + range.length) {
            return range.format;
        }
    }

    return {};
}
}    // namespace

void TestQMarkdownTextEdit::testUpFromStartOfSecondLineMovesToFirstLine() {
    QMarkdownTextEdit editor;
    editor.resize(400, 200);
    editor.setPlainText(QStringLiteral("first line\nsecond line"));
    editor.show();
    editor.setFocus();
    QApplication::processEvents();

    QTextCursor cursor = editor.textCursor();
    cursor.setPosition(editor.document()->findBlockByNumber(1).position());
    editor.setTextCursor(cursor);

    QTest::keyClick(&editor, Qt::Key_Up);

    QCOMPARE(editor.textCursor().position(), 0);
}

void TestQMarkdownTextEdit::testToPlainTextPreservesNoBreakSpaces() {
    QMarkdownTextEdit editor;
    const QString text = QStringLiteral("U+00A0") + QChar(0x00A0) + QStringLiteral("NBSP\n") +
                         QStringLiteral("U+202F") + QChar(0x202F) + QStringLiteral("NNBSP");

    editor.setPlainText(text);

    QCOMPARE(editor.toPlainText(), text);
}

void TestQMarkdownTextEdit::testRCodeBlockHighlighting() {
    QMarkdownTextEdit editor;
    editor.setPlainText(QStringLiteral("```r\nif (is.numeric(value)) print(TRUE) # result\n```"));

    const QTextBlock codeBlock = editor.document()->findBlockByNumber(1);
    QCOMPARE(editor.document()->firstBlock().userState(), int(MarkdownHighlighter::CodeR));
    QCOMPARE(codeBlock.userState(), int(MarkdownHighlighter::CodeR));

    const QTextCharFormat defaultFormat = formatAt(codeBlock, codeBlock.text().indexOf("value"));
    const QTextCharFormat keywordFormat = formatAt(codeBlock, codeBlock.text().indexOf("if"));
    const QTextCharFormat builtinFormat =
        formatAt(codeBlock, codeBlock.text().indexOf("is.numeric"));
    const QTextCharFormat literalFormat = formatAt(codeBlock, codeBlock.text().indexOf("TRUE"));
    const int commentPosition = codeBlock.text().indexOf('#');
    const QTextCharFormat commentFormat = formatAt(codeBlock, commentPosition);

    QVERIFY(keywordFormat.foreground() != defaultFormat.foreground());
    QVERIFY(builtinFormat.foreground() != defaultFormat.foreground());
    QVERIFY(literalFormat.foreground() != defaultFormat.foreground());
    QVERIFY(commentFormat.foreground() != defaultFormat.foreground());
    QCOMPARE(formatAt(codeBlock, commentPosition + 2).foreground(), commentFormat.foreground());
}

void TestQMarkdownTextEdit::testMultilineInlineHighlighting() {
    QMarkdownTextEdit editor;

    editor.setPlainText(QStringLiteral("~~first line\nsecond line\nthird line~~"));
    editor.highlighter()->rehighlight();
    QVERIFY(formatAt(editor.document()->findBlockByNumber(0), 2).fontStrikeOut());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(1), 0).fontStrikeOut());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(2), 0).fontStrikeOut());

    editor.setPlainText(QStringLiteral("**first line\nsecond line**"));
    editor.highlighter()->rehighlight();
    QCOMPARE(formatAt(editor.document()->findBlockByNumber(0), 2).fontWeight(), int(QFont::Bold));
    QCOMPARE(formatAt(editor.document()->findBlockByNumber(1), 0).fontWeight(), int(QFont::Bold));

    editor.setPlainText(QStringLiteral("*first line\nsecond line\nthird line*"));
    editor.highlighter()->rehighlight();
    QVERIFY(formatAt(editor.document()->findBlockByNumber(0), 1).fontItalic());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(1), 0).fontItalic());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(2), 0).fontItalic());

    editor.highlighter()->setHighlightingOptions(MarkdownHighlighter::Underline);
    editor.setPlainText(QStringLiteral("__first line\nsecond line__"));
    editor.highlighter()->rehighlight();
    QVERIFY(formatAt(editor.document()->findBlockByNumber(0), 2).fontUnderline());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(1), 0).fontUnderline());

    editor.setPlainText(QStringLiteral("#1 | ABC [*1]\n#2 | XYZ [*2]\n#3 | XXX [*3]"));
    editor.highlighter()->rehighlight();
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(0), 11).fontItalic());
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(1), 0).fontItalic());
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(2), 0).fontItalic());

    editor.setPlainText(
        QStringLiteral("!!_1) Text\ncontinued\n\n__Test__Broken - because of a missing space after "
                       "the closing \"__\" !!!\ncontinued"));
    editor.highlighter()->rehighlight();
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(0), 3).fontItalic());
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(1), 0).fontItalic());
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(3), 2).fontUnderline());
    QVERIFY(!formatAt(editor.document()->findBlockByNumber(3), 8).fontUnderline());
    QVERIFY(formatAt(editor.document()->findBlockByNumber(4), 0).fontWeight() != int(QFont::Bold));

    editor.setPlainText(QStringLiteral("__foo_bar__"));
    editor.highlighter()->rehighlight();
    QVERIFY(formatAt(editor.document()->firstBlock(), 2).fontUnderline());
    QVERIFY(formatAt(editor.document()->firstBlock(), 6).fontUnderline());
}

void TestQMarkdownTextEdit::testLinkedCheckBoxDetectionInReadOnlyEditor() {
    QMarkdownTextEdit editor;
    editor.setPlainText(
        QStringLiteral("- [ ] [Update contact information](https://example.com/tasks/123)"));
    editor.setReadOnly(true);

    QTextCursor cursor = editor.textCursor();
    cursor.setPosition(3);
    editor.setTextCursor(cursor);

    QVERIFY(Utils::Gui::isCheckBoxAtCursor(&editor));
    QVERIFY(editor.toPlainText().startsWith(QStringLiteral("- [ ]")));

    editor.setReadOnly(false);
    QVERIFY(Utils::Gui::toggleCheckBoxAtCursor(&editor));
    QVERIFY(editor.toPlainText().startsWith(QStringLiteral("- [x]")));
}

void TestQMarkdownTextEdit::testAutoFormatTableAtCursor() {
    QMarkdownTextEdit editor;
    editor.setPlainText(QStringLiteral("| Name|Value|\n|-|-|\n|one|123|\n|longer|4|"));

    QTextCursor cursor = editor.textCursor();
    cursor.setPosition(editor.document()->findBlockByNumber(2).position());
    editor.setTextCursor(cursor);

    QVERIFY(Utils::Gui::autoFormatTableAtCursor(&editor));
    QCOMPARE(editor.toPlainText(), QStringLiteral("| Name   | Value |\n"
                                                  "| ------ | ----- |\n"
                                                  "| one    | 123   |\n"
                                                  "| longer | 4     |"));
    QVERIFY(!Utils::Gui::autoFormatTableAtCursor(&editor));
}
