/*
  Copyright (c) 2012-2015 Montel Laurent <montel@kde.org>

  based on code from qt-labs-graphics-dojo/htmleditor/highlighter.*

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

  This library 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 Library General Public
  License for more details.

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

*/

#include "htmlhighlighter.h"

namespace KPIMTextEdit
{

class HtmlHighlighterPrivate
{
public:
    enum Construct {
        DocType,
        Entity,
        Tag,
        Comment,
        AttributeName,
        AttributeValue
    };

    enum State {
        State_Text = -1,
        State_DocType,
        State_Comment,
        State_TagStart,
        State_TagName,
        State_InsideTag,
        State_AttributeName,
        State_SingleQuote,
        State_DoubleQuote,
        State_AttributeValue
    };

    HtmlHighlighterPrivate()
    {
        colors[DocType] = QColor(192, 192, 192);
        colors[Entity] = QColor(128, 128, 128);
        colors[Tag] = QColor(136, 18, 128);
        colors[Comment] = QColor(35, 110, 37);
        colors[AttributeName] = QColor(153, 69, 0);
        colors[AttributeValue] = QColor(36, 36, 170);
    }
    QHash<int, QColor> colors;
};

HtmlHighlighter::HtmlHighlighter(QTextDocument *document)
    : QSyntaxHighlighter(document), d(new HtmlHighlighterPrivate)
{
}

HtmlHighlighter::~HtmlHighlighter()
{
    delete d;
}

void HtmlHighlighter::highlightBlock(const QString &text)
{
    int state = previousBlockState();
    int len = text.length();
    int start = 0;
    int pos = 0;

    while (pos < len) {
        switch (state) {
        case HtmlHighlighterPrivate::State_Text:
        default:
            while (pos < len) {
                QChar ch = text.at(pos);
                if (ch == QLatin1Char('<')) {
                    if (text.mid(pos, 4) == QLatin1String("<!--")) {
                        state = HtmlHighlighterPrivate::State_Comment;
                    } else {
                        if (text.mid(pos, 9).toUpper() == QLatin1String("<!DOCTYPE")) {
                            state = HtmlHighlighterPrivate::State_DocType;
                        } else {
                            state = HtmlHighlighterPrivate::State_TagStart;
                        }
                    }
                    break;
                } else if (ch == QLatin1Char('&')) {
                    start = pos;
                    while (pos < len && text.at(pos++) != QLatin1Char(';'))
                        ;
                    setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::Entity]);
                } else {
                    ++pos;
                }
            }
            break;

        case HtmlHighlighterPrivate::State_Comment:
            start = pos;
            while (pos < len) {
                if (text.mid(pos, 3) == QLatin1String("-->")) {
                    pos += 3;
                    state = HtmlHighlighterPrivate::State_Text;
                    break;
                } else {
                    ++pos;
                }
            }
            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::Comment]);
            break;

        case HtmlHighlighterPrivate::State_DocType:
            start = pos;
            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;
                if (ch == QLatin1Char('>')) {
                    state = HtmlHighlighterPrivate::State_Text;
                    break;
                }
            }
            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::DocType]);
            break;

        // at '<' in e.g. "<span>foo</span>"
        case HtmlHighlighterPrivate::State_TagStart:
            start = pos + 1;
            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;
                if (ch == QLatin1Char('>')) {
                    state = HtmlHighlighterPrivate::State_Text;
                    break;
                }
                if (!ch.isSpace()) {
                    --pos;
                    state = HtmlHighlighterPrivate::State_TagName;
                    break;
                }
            }
            break;

        // at 'b' in e.g "<blockquote>foo</blockquote>"
        case HtmlHighlighterPrivate::State_TagName:
            start = pos;
            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;
                if (ch.isSpace()) {
                    --pos;
                    state = HtmlHighlighterPrivate::State_InsideTag;
                    break;
                }
                if (ch == QLatin1Char('>')) {
                    state = HtmlHighlighterPrivate::State_Text;
                    break;
                }
            }
            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::Tag]);
            break;

        // anywhere after tag name and before tag closing ('>')
        case HtmlHighlighterPrivate::State_InsideTag:
            start = pos;

            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;

                if (ch == QLatin1Char('/')) {
                    continue;
                }

                if (ch == QLatin1Char('>')) {
                    state = HtmlHighlighterPrivate::State_Text;
                    break;
                }

                if (!ch.isSpace()) {
                    --pos;
                    state = HtmlHighlighterPrivate::State_AttributeName;
                    break;
                }

            }

            break;

        // at 's' in e.g. <img src=bla.png/>
        case HtmlHighlighterPrivate::State_AttributeName:
            start = pos;

            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;

                if (ch == QLatin1Char('=')) {
                    state = HtmlHighlighterPrivate::State_AttributeValue;
                    break;
                }

                if (ch == QLatin1Char('>') || ch == QLatin1Char('/')) {
                    state = HtmlHighlighterPrivate::State_InsideTag;
                    break;
                }
            }

            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::AttributeName]);
            break;

        // after '=' in e.g. <img src=bla.png/>
        case HtmlHighlighterPrivate::State_AttributeValue:
            start = pos;

            // find first non-space character
            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;

                // handle opening single quote
                if (ch == QLatin1Char('\'')) {
                    state = HtmlHighlighterPrivate::State_SingleQuote;
                    break;
                }

                // handle opening double quote
                if (ch == QLatin1Char('"')) {
                    state = HtmlHighlighterPrivate::State_DoubleQuote;
                    break;
                }

                if (!ch.isSpace()) {
                    break;
                }
            }

            if (state == HtmlHighlighterPrivate::State_AttributeValue) {
                // attribute value without quote
                // just stop at non-space or tag delimiter
                start = pos;
                while (pos < len) {
                    QChar ch = text.at(pos);
                    if (ch.isSpace()) {
                        break;
                    }
                    if (ch == QLatin1Char('>') || ch == QLatin1Char('/')) {
                        break;
                    }
                    ++pos;
                }
                state = HtmlHighlighterPrivate::State_InsideTag;
                setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::AttributeValue]);
            }

            break;

        // after the opening single quote in an attribute value
        case HtmlHighlighterPrivate::State_SingleQuote:
            start = pos;

            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;
                if (ch == QLatin1Char('\'')) {
                    break;
                }
            }

            state = HtmlHighlighterPrivate::State_InsideTag;

            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::AttributeValue]);
            break;

        // after the opening double quote in an attribute value
        case HtmlHighlighterPrivate::State_DoubleQuote:
            start = pos;

            while (pos < len) {
                QChar ch = text.at(pos);
                ++pos;
                if (ch == QLatin1Char('"')) {
                    break;
                }
            }

            state = HtmlHighlighterPrivate::State_InsideTag;

            setFormat(start, pos - start, d->colors[HtmlHighlighterPrivate::AttributeValue]);
            break;

        }
    }

    setCurrentBlockState(state);
}

}

