/*
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.

    ---
    Copyright (C) 2013 Filipe Saraiva <filipe@kde.org>
 */

#include "pythonhighlighter.h"
#include "pythonkeywords.h"
#include "pythonsession.h"

#include <QTextEdit>
#include <QDebug>

PythonHighlighter::PythonHighlighter(QObject* parent, PythonSession* session) : Cantor::DefaultHighlighter(parent, session)
{
    qDebug() << "PythonHighlighter constructor";
    addRule(QRegularExpression(QStringLiteral("\\b\\w+(?=\\()")), functionFormat());

    //Code highlighting the different keywords
    addKeywords(PythonKeywords::instance()->keywords());
    addFunctions(PythonKeywords::instance()->functions());
    addVariables(PythonKeywords::instance()->variables());
}

void PythonHighlighter::highlightBlock(const QString &text)
{
    if (skipHighlighting(text)) {
        return;
    }

    // Do some backend independent highlighting (brackets etc.)
    DefaultHighlighter::highlightBlock(text);

    const int IN_MULTILINE_COMMENT = 1;
    const int IN_SMALL_QUOTE_STRING = 2;
    const int IN_SINGLE_QUOTE_STRING = 4;
    const int IN_TRIPLE_QUOTE_STRING = 8;

    static const QRegularExpression multiLineCommentStartEnd(QStringLiteral("'''"));
    static const QRegularExpression smallQuoteStartEnd(QStringLiteral("'"));
    static const QRegularExpression singleQuoteStringStartEnd(QStringLiteral("\""));
    static const QRegularExpression tripleQuoteStringStartEnd(QStringLiteral("\"\"\""));
    static const QRegularExpression singleLineCommentStart(QStringLiteral("#"));

    int state = previousBlockState();
    if (state == -1) {
        state = 0;
    }

    QList<int> flags = {
        IN_TRIPLE_QUOTE_STRING,
        IN_SINGLE_QUOTE_STRING,
        IN_SMALL_QUOTE_STRING,
        IN_MULTILINE_COMMENT
    };
    const QVector<QRegularExpression> regexps = {
        tripleQuoteStringStartEnd,
        singleQuoteStringStartEnd,
        smallQuoteStartEnd,
        multiLineCommentStartEnd
    };
    QList<QTextCharFormat> formats = {
        stringFormat(),
        stringFormat(),
        stringFormat(),
        commentFormat()
    };

    int pos = 0;
    while (pos < text.length()) {
        // Trying to close current environments
        bool triggered = false;
        for (int i = 0; i < flags.size() && !triggered; i++) {
            int flag = flags[i];
            QTextCharFormat &format = formats[i];
            if (state & flag) {
                const QRegularExpressionMatch match = regexps.at(i).match(text, pos);
                int length;
                if (!match.hasMatch()) {
                    length = text.length() - pos;
                } else { // found a match
                    length = match.capturedStart(0) - pos + match.capturedLength(0);
                    state -= flag;
                }
                setFormat(pos, length, format);
                pos = pos + length;
                triggered = true;
            }
        }
        if (triggered) {
            continue;
        }

        QRegularExpressionMatch minMatch;
        int minPos = INT_MAX;
        int minIdx = -1;
        for (int i = 0; i < regexps.size(); i++) {
            const QRegularExpressionMatch match = regexps.at(i).match(text, pos);
            if (match.hasMatch()) {
                minPos = qMin(minPos, match.capturedStart(0));
                minIdx = i;
                minMatch = match;
            }
        }

        const int singleLineCommentStartPos = text.indexOf(singleLineCommentStart, pos);

        if (singleLineCommentStartPos != -1
            && singleLineCommentStartPos < minPos) {
            setFormat(singleLineCommentStartPos, text.length() - singleLineCommentStartPos, commentFormat());
        break;
            } else if (minMatch.hasMatch()) {
                state += flags[minIdx];
                pos = minPos + minMatch.capturedLength(0);
                setFormat(minPos, minMatch.capturedLength(0), formats[minIdx]);
            } else {
                break;
            }
    }

    setCurrentBlockState(state);
}
