/***************************************************************************
 *   copyright       : (C) 2009-2022 by Pascal Brachet                     *
 *   https://www.xm1math.net/algobox/                                      *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   @license GPL-2.0+ <https://spdx.org/licenses/GPL-2.0+.html>           *
 ***************************************************************************/


#include <QtGui>

#include "jshighlighter.h"
#include "blockdata.h"

JSHighlighter::JSHighlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
ColorNormal = QColor("#000000");
ColorComment=QColor("#808080");
ColorNumber= QColor("#008000");
ColorString=QColor("#E20800");
ColorOperator=QColor("#8C0000");
ColorIdentifier=QColor("#800080");
ColorKeyword=QColor("#000000");
ColorBuiltIn=QColor("#000080");
    // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words
    m_keywords << "break";
    m_keywords << "case";
    m_keywords << "catch";
    m_keywords << "continue";
    m_keywords << "default";
    m_keywords << "delete";
    m_keywords << "do";
    m_keywords << "else";
    m_keywords << "finally";
    m_keywords << "for";
    m_keywords << "function";
    m_keywords << "if";
    m_keywords << "in";
    m_keywords << "instanceof";
    m_keywords << "new";
    m_keywords << "return";
    m_keywords << "switch";
    m_keywords << "this";
    m_keywords << "throw";
    m_keywords << "try";
    m_keywords << "typeof";
    m_keywords << "var";
    m_keywords << "void";
    m_keywords << "while";
    m_keywords << "with";

    m_keywords << "true";
    m_keywords << "false";
    m_keywords << "null";

    // built-in and other popular objects + properties
    m_knownIds << "Object";
    m_knownIds << "prototype";
    m_knownIds << "create";
    m_knownIds << "defineProperty";
    m_knownIds << "defineProperties";
    m_knownIds << "getOwnPropertyDescriptor";
    m_knownIds << "keys";
    m_knownIds << "getOwnPropertyNames";
    m_knownIds << "constructor";
    m_knownIds << "__parent__";
    m_knownIds << "__proto__";
    m_knownIds << "__defineGetter__";
    m_knownIds << "__defineSetter__";
    m_knownIds << "eval";
    m_knownIds << "hasOwnProperty";
    m_knownIds << "isPrototypeOf";
    m_knownIds << "__lookupGetter__";
    m_knownIds << "__lookupSetter__";
    m_knownIds << "__noSuchMethod__";
    m_knownIds << "propertyIsEnumerable";
    m_knownIds << "toSource";
    m_knownIds << "toLocaleString";
    m_knownIds << "toString";
    m_knownIds << "unwatch";
    m_knownIds << "valueOf";
    m_knownIds << "watch";

    m_knownIds << "Function";
    m_knownIds << "arguments";
    m_knownIds << "arity";
    m_knownIds << "caller";
    m_knownIds << "constructor";
    m_knownIds << "length";
    m_knownIds << "name";
    m_knownIds << "apply";
    m_knownIds << "bind";
    m_knownIds << "call";

    m_knownIds << "String";
    m_knownIds << "fromCharCode";
    m_knownIds << "length";
    m_knownIds << "charAt";
    m_knownIds << "charCodeAt";
    m_knownIds << "concat";
    m_knownIds << "indexOf";
    m_knownIds << "lastIndexOf";
    m_knownIds << "localCompare";
    m_knownIds << "match";
    m_knownIds << "quote";
    m_knownIds << "replace";
    m_knownIds << "search";
    m_knownIds << "slice";
    m_knownIds << "split";
    m_knownIds << "substr";
    m_knownIds << "substring";
    m_knownIds << "toLocaleLowerCase";
    m_knownIds << "toLocaleUpperCase";
    m_knownIds << "toLowerCase";
    m_knownIds << "toUpperCase";
    m_knownIds << "trim";
    m_knownIds << "trimLeft";
    m_knownIds << "trimRight";

    m_knownIds << "Array";
    m_knownIds << "isArray";
    m_knownIds << "index";
    m_knownIds << "input";
    m_knownIds << "pop";
    m_knownIds << "push";
    m_knownIds << "reverse";
    m_knownIds << "shift";
    m_knownIds << "sort";
    m_knownIds << "splice";
    m_knownIds << "unshift";
    m_knownIds << "concat";
    m_knownIds << "join";
    m_knownIds << "filter";
    m_knownIds << "forEach";
    m_knownIds << "every";
    m_knownIds << "map";
    m_knownIds << "some";
    m_knownIds << "reduce";
    m_knownIds << "reduceRight";

    m_knownIds << "RegExp";
    m_knownIds << "global";
    m_knownIds << "ignoreCase";
    m_knownIds << "lastIndex";
    m_knownIds << "multiline";
    m_knownIds << "source";
    m_knownIds << "exec";
    m_knownIds << "test";

    m_knownIds << "JSON";
    m_knownIds << "parse";
    m_knownIds << "stringify";

    m_knownIds << "decodeURI";
    m_knownIds << "decodeURIComponent";
    m_knownIds << "encodeURI";
    m_knownIds << "encodeURIComponent";
    m_knownIds << "eval";
    m_knownIds << "isFinite";
    m_knownIds << "isNaN";
    m_knownIds << "parseFloat";
    m_knownIds << "parseInt";
    m_knownIds << "Infinity";
    m_knownIds << "NaN";
    m_knownIds << "undefined";

    m_knownIds << "Math";
    m_knownIds << "E";
    m_knownIds << "LN2";
    m_knownIds << "LN10";
    m_knownIds << "LOG2E";
    m_knownIds << "LOG10E";
    m_knownIds << "PI";
    m_knownIds << "SQRT1_2";
    m_knownIds << "SQRT2";
    m_knownIds << "abs";
    m_knownIds << "acos";
    m_knownIds << "asin";
    m_knownIds << "atan";
    m_knownIds << "atan2";
    m_knownIds << "ceil";
    m_knownIds << "cos";
    m_knownIds << "exp";
    m_knownIds << "floor";
    m_knownIds << "log";
    m_knownIds << "max";
    m_knownIds << "min";
    m_knownIds << "pow";
    m_knownIds << "random";
    m_knownIds << "round";
    m_knownIds << "sin";
    m_knownIds << "sqrt";
    m_knownIds << "tan";

    m_knownIds << "document";
    m_knownIds << "window";
    m_knownIds << "navigator";
    m_knownIds << "userAgent";
}

JSHighlighter::~JSHighlighter(){
}

void JSHighlighter::highlightBlock(const QString &text)
{

BlockData *blockData = new BlockData;
int leftPos = text.indexOf( '(' );
while ( leftPos != -1 ) 
  {
  ParenthesisInfo *info = new ParenthesisInfo;
  info->character = '(';
  info->position = leftPos;

  blockData->insertPar( info );
  leftPos = text.indexOf( '(', leftPos+1 );
  }

int rightPos = text.indexOf(')');
while ( rightPos != -1 ) 
  {
  ParenthesisInfo *info = new ParenthesisInfo;
  info->character = ')';
  info->position = rightPos;

  blockData->insertPar( info );
  rightPos = text.indexOf( ')', rightPos+1 );
  }
leftPos = text.indexOf( '{' );
while ( leftPos != -1 ) 
  {
  ParenthesisInfo *info = new ParenthesisInfo;
  info->character = '{';
  info->position = leftPos;

  blockData->insertBrack( info );
  leftPos = text.indexOf( '{', leftPos+1 );
  }

rightPos = text.indexOf('}');
while ( rightPos != -1 ) 
  {
  ParenthesisInfo *info = new ParenthesisInfo;
  info->character = '}';
  info->position = rightPos;

  blockData->insertBrack( info );
  rightPos = text.indexOf( '}', rightPos+1 );
  }
setCurrentBlockUserData(blockData);
blockData->code.clear(); 

for (int j=0; j < text.length(); j++) blockData->code.append(0);

enum {
    Start = 0,
    Number = 1,
    Identifier = 2,
    String = 3,
    Comment = 4,
    Regex = 5
};

QTextCharFormat keywordFormat;
keywordFormat.setFontWeight(QFont::Bold);
keywordFormat.setForeground(ColorKeyword);

int blockState = previousBlockState();
int bracketLevel = blockState >> 4;
int state = blockState & 15;
if (blockState < 0) {
    bracketLevel = 0;
    state = Start;
}

int start = 0;
int i = 0;
while (i <= text.length()) {
    QChar ch = (i < text.length()) ? text.at(i) : QChar();
    QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar();

    switch (state) {

    case Start:
	start = i;
	if (ch.isSpace()) {
	    ++i;
	} else if (ch.isDigit()) {
	    ++i;
	    state = Number;
	} else if (ch.isLetter() || ch == '_') {
	    ++i;
	    state = Identifier;
	} else if (ch == '\'' || ch == '\"') {
	    ++i;
	    state = String;
	} else if (ch == '/' && next == '*') {
	    ++i;
	    ++i;
	    state = Comment;
	} else if (ch == '/' && next == '/') {
	    i = text.length();
	    setFormat(start, text.length(), ColorComment);
	} else if (ch == '/' && next != '*') {
	    ++i;
	    state = Regex;
	} else {
	    if (!QString("(){}[]").contains(ch))
		setFormat(start, 1, ColorOperator);
	    if (ch =='{' || ch == '}') {
		if (ch == '{')
		    bracketLevel++;
		else
		    bracketLevel--;
	    }
	    ++i;
	    state = Start;
	}
	break;

    case Number:
	if (ch.isSpace() || !ch.isDigit()) {
	    setFormat(start, i - start, ColorNumber);
	    state = Start;
	} else {
	    ++i;
	}
	break;

    case Identifier:
	if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) {
	    QString token = text.mid(start, i - start).trimmed();
	    if (m_keywords.contains(token))
		setFormat(start, i - start,keywordFormat );
	    else if (m_knownIds.contains(token))
		setFormat(start, i - start, ColorBuiltIn);
	    state = Start;
	} else {
	    ++i;
	}
	break;

    case String:
	if (ch == text.at(start)) {
	    QChar prev = (i > 0) ? text.at(i - 1) : QChar();
	    if (prev != '\\') {
		++i;
		setFormat(start, i - start, ColorString);
		state = Start;
	    } else {
		++i;
	    }
	} else {
	    ++i;
	}
	break;

    case Comment:
	if (ch == '*' && next == '/') {
	    ++i;
	    ++i;
	    setFormat(start, i - start, ColorComment);
	    state = Start;
	} else {
	    ++i;
	}
	break;

    case Regex:
	if (ch == '/') {
	    QChar prev = (i > 0) ? text.at(i - 1) : QChar();
	    if (prev != '\\') {
		++i;
		setFormat(start, i - start, ColorString);
		state = Start;
	    } else {
		++i;
	    }
	} else {
	    ++i;
	}
	break;

    default:
	state = Start;
	break;
    }
}

if (state == Comment)
    setFormat(start, text.length(), ColorComment);
else
    state = Start;


blockState = (state & 15) | (bracketLevel << 4);
setCurrentBlockState(blockState);
}



