/***************************** LICENSE START ***********************************

 Copyright 2020 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "DocHighlighter.h"
#include "MvMiscellaneous.h"
#include "MvQTheme.h"

#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextDocumentFragment>
#include <QTextLayout>
#include <QRegularExpressionMatch>

QString DocHighlighter::parFile_;


DocHighlighter::DocHighlighter(QTextDocument* parent, QString id) :
    QSyntaxHighlighter(parent)
{
    init();
    load(id);
}

DocHighlighter::HighlightingRule DocHighlighter::makeRule(QString pattern, QTextCharFormat format, bool caseSensitive)
{
    HighlightingRule rule;
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    rule.pattern = QRegularExpression(pattern);
    if (!caseSensitive) {
        rule.pattern.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
    }
#else
    rule.pattern = QRegExp(pattern, (caseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive);
#endif
    rule.format = format;
    return rule;
}

void DocHighlighter::highlightBlock(const QString& text)
{
    Q_FOREACH (HighlightingRule rule, rules_) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
        QRegularExpression expression(rule.pattern);
        QRegularExpressionMatch rmatch;
        int index = text.indexOf(expression, 0, &rmatch);
        while (index >= 0) {
            int length = rmatch.capturedLength();
            setFormat(index, length, rule.format);
            index = text.indexOf(expression, index + length, &rmatch);
        }
#else
        QRegExp expression(rule.pattern);
        int index = text.indexOf(expression);
        while (index >= 0) {
            int length = expression.matchedLength();
            setFormat(index, length, rule.format);
            index = text.indexOf(expression, index + length);
        }

#endif
    }

    setCurrentBlockState(0);

    if (!commentStartRule_.pattern.pattern().isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
        int startIndex = 0;
        QRegularExpressionMatch rmatch;
        if (previousBlockState() != 1)
            startIndex = text.indexOf(commentStartRule_.pattern, 0, &rmatch);
        while (startIndex >= 0) {
            int endIndex = text.indexOf(commentEndRule_.pattern, startIndex, &rmatch);
            int commentLength = 0;
            if (endIndex == -1) {
                setCurrentBlockState(1);
                commentLength = text.length() - startIndex;
            }
            else {
                commentLength = endIndex - startIndex + rmatch.capturedLength();
            }
            setFormat(startIndex, commentLength, commentStartRule_.format);
            startIndex = text.indexOf(commentStartRule_.pattern,
                                      startIndex + commentLength);
        }
#else
        int startIndex = 0;
        if (previousBlockState() != 1)
            startIndex = text.indexOf(commentStartRule_.pattern);

        while (startIndex >= 0) {
            int endIndex = text.indexOf(commentEndRule_.pattern, startIndex);
            int commentLength;
            if (endIndex == -1) {
                setCurrentBlockState(1);
                commentLength = text.length() - startIndex;
            }
            else {
                commentLength = endIndex - startIndex + commentEndRule_.pattern.matchedLength();
            }
            setFormat(startIndex, commentLength, commentStartRule_.format);
            startIndex = text.indexOf(commentStartRule_.pattern,
                                      startIndex + commentLength);
        }
#endif
    }
}

void DocHighlighter::init()
{
    parFile_ = MvQTheme::highlighterFile();
}

void DocHighlighter::load(QString id)
{
    QFile fIn(parFile_);
    if (!fIn.open(QIODevice::ReadOnly | QIODevice::Text)) {
        // marslog(LOG_WARN, "DocHughlighter::load() --> Could not open json config file: %s", fName.c_str());
        return;
    }

    QByteArray json = fIn.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(json);

    // This document is an array of groups
    Q_ASSERT(doc.isObject());

    // Iterate through the ojects in a group
    const QJsonObject& jsonob = doc.object();
    for (QJsonObject::const_iterator it = jsonob.constBegin(); it != jsonob.constEnd(); it++) {
        QString name = it.key();
        if (name == id) {
            Q_ASSERT(it.value().isArray());
            QJsonArray arr = it.value().toArray();
            for (int i = 0; i < arr.count(); i++) {
                QJsonObject parObj = arr[i].toObject();
                QString pattern, startPattern, endPattern, name;
                QTextCharFormat format;
                bool caseSensitive = true;
                for (QJsonObject::const_iterator itPar = parObj.constBegin();
                     itPar != parObj.constEnd(); itPar++) {
                    QString parName = itPar.key();

                    if (parName == "name") {
                        name = itPar.value().toString();
                    }
                    else if (parName == "colour") {
                        format.setForeground(rgb(itPar.value().toString()));
                    }
                    else if (parName == "pattern") {
                        pattern = itPar.value().toString();
                    }
                    else if (parName == "startPattern") {
                        startPattern = itPar.value().toString();
                    }
                    else if (parName == "endPattern") {
                        endPattern = itPar.value().toString();
                    }
                    else if (parName == "bold") {
                        if (itPar.value().toString().toLower() == "true")
                            format.setFontWeight(QFont::Bold);
                    }
                    else if (parName == "italic") {
                        if (itPar.value().toString().toLower() == "true")
                            format.setFontItalic(true);
                    }
                    else if (parName == "caseInsensitive") {
                        if (itPar.value().toString().toLower() == "true")
                            caseSensitive = false;
                    }
                }
                if (name != "multiLineComment") {
                    rules_.append(makeRule(pattern, format, caseSensitive));
                }
                else {
                    commentStartRule_ = makeRule(startPattern, format, false);
                    commentEndRule_ = makeRule(endPattern, format, false);
                }
            }
            return;
        }
    }
}

void DocHighlighter::toHtml(QString& html)
{
    // Create a new document from all the selected text document.
    QTextCursor cursor(document());
    cursor.select(QTextCursor::Document);

    auto* tmpDoc(new QTextDocument());
    Q_ASSERT(tmpDoc);
    QTextCursor tmpCursor(tmpDoc);
    tmpCursor.insertFragment(cursor.selection());
    tmpCursor.select(QTextCursor::Document);

    // Set the default foreground for the inserted characters.
    // QTextCharFormat textfmt = tmpCursor.charFormat();
    // textfmt.setForeground(Qt::black);
    // tmpCursor.setCharFormat(textfmt);

    // Apply the additional formats set by the syntax highlighter
    QTextBlock start = document()->findBlock(cursor.selectionStart());
    QTextBlock end = document()->findBlock(cursor.selectionEnd());
    end = end.next();

    const int selectionStart = cursor.selectionStart();
    const int endOfDocument = tmpDoc->characterCount() - 1;
    for (QTextBlock current = start; current.isValid() and current not_eq end; current = current.next()) {
        const QTextLayout* layout(current.layout());

#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
        Q_FOREACH (const QTextLayout::FormatRange& range, layout->formats()) {
#else
        Q_FOREACH (const QTextLayout::FormatRange& range, layout->additionalFormats()) {
#endif
            const int start = current.position() + range.start - selectionStart;
            const int end = start + range.length;
            if (end <= 0 or start >= endOfDocument)
                continue;
            tmpCursor.setPosition(qMax(start, 0));
            tmpCursor.setPosition(qMin(end, endOfDocument), QTextCursor::KeepAnchor);
            tmpCursor.setCharFormat(range.format);
        }
    }

    // Reset the user states since they are not interesting
    for (QTextBlock block = tmpDoc->begin(); block.isValid(); block = block.next())
        block.setUserState(-1);

    // Make sure the text appears pre-formatted, and set the background we want.
    tmpCursor.select(QTextCursor::Document);
    QTextBlockFormat blockFormat = tmpCursor.blockFormat();
    blockFormat.setNonBreakableLines(true);
    blockFormat.setBackground(Qt::black);
    tmpCursor.setBlockFormat(blockFormat);

    // Finally retreive the syntax higlighted and formatted html.
    html = tmpCursor.selection().toHtml();
    delete tmpDoc;
}

QColor DocHighlighter::rgb(QString name) const
{
    QColor col;
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    QRegularExpression rx(R"(rgb\((\d+),(\d+),(\d+),?(\d+)?)");
    auto match = rx.match(name);
    if (match.hasMatch()) {
        col = QColor(match.captured(1).toInt(),
                     match.captured(2).toInt(),
                     match.captured(3).toInt(),
                     match.captured(4).isEmpty() ? 255 : match.captured(4).toInt());
    }

#else
    QRegExp rx("rgb\\((\\d+),(\\d+),(\\d+),?(\\d+)?");

    if (rx.indexIn(name) > -1) {
        if (rx.captureCount() >= 3) {
            col = QColor(rx.cap(1).toInt(),
                         rx.cap(2).toInt(),
                         rx.cap(3).toInt(),
                         rx.cap(4).isEmpty() ? 255 : rx.cap(4).toInt());
        }
    }
#endif
    return col;
}
