#include <QDebug>
#include <QTextCursor>
#include "script_highlighter.h"
#include "script_analyzer.h"


ScriptHighlighter::ScriptHighlighter(QTextDocument * parent): QSyntaxHighlighter(parent) {
	nid = 1;
	HighlightingRule rule;

	functionFormat.setForeground(QColor(0, 0, 96));

	rule.format = functionFormat;
	rule.pattern = QRegularExpression("\\.\\b\\w+\\b(?=\\s*\\()");
	highlightingRules.append(rule);


	QStringList keywordPatterns;
	keywordPatterns << "function" << "var" << "let"<< "const"
					<< "class" << "typeof" << "this" << "undefined"
					<< "RegExp" << "Number" << "String"
					<< "Object" << "Error" << "TypeError"
					<< "Array" << "Number" << "String"
					<< "Date" << "Boolean" << "Map" << "Set";
	rule.format.setForeground(QColor(0, 87, 174));
	foreach (const QString & pattern, keywordPatterns) {
		rule.pattern = QRegularExpression("\\b" + pattern + "\\b");
		highlightingRules.append(rule);
	}

	keywordPatterns.clear();
	keywordPatterns << "true" << "false"
					<< "null" << "new" << "extends"
					<< "if" << "for" << "foreach"
					<< "do" << "while" << "until" << "else"
					<< "break" << "return" << "as" << "continue"
					<< "throw" << "constructor"
					<< "catch" << "switch" << "case"
					<< "instanceof" << "with" << "try"
					<< "in" << "of" << "default"
					<< "finally" << "ReadOnly" << "DontEnum"
					<< "DontDelete" << "Internal" << "include";
	rule.format.setForeground(Qt::black);
	rule.format.setFontWeight(QFont::Bold);
	foreach (const QString & pattern, keywordPatterns) {
		rule.pattern = QRegularExpression("\\b" + pattern + "\\b");
		highlightingRules.append(rule);
	}
	
	keywordPatterns.clear();
	keywordPatterns << "print" << "eval"
					<< "connect" << "disconnect" << "call"
					<< "usleep" << "msleep" << "sleep"
					<< "callee" << "server_started";
	rule.format = functionFormat;
	foreach (const QString & pattern, keywordPatterns) {
		rule.pattern = QRegularExpression("\\b" + pattern + "\\b");
		highlightingRules.append(rule);
	}

	rule.format.setForeground(QColor(177, 129, 2));
	rule.pattern = QRegularExpression("[-+]?(?:\\b[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+\\b)(?:[eE][-+]?[0-9]+(f)?\\b)?(f)?");
	highlightingRules.append(rule);
	rule.pattern = QRegularExpression("0x([0-9a-fA-F]*)[slL]?");
	highlightingRules.append(rule);
	rule.pattern = QRegularExpression("0b([0-1]*)?");
	highlightingRules.append(rule);

	quotationFormat.setForeground(QColor(192, 0, 0));
	quotationFormatE = quotationFormat;
	quotationFormatE.setFontWeight(QFont::Bold);

	macroFormat.setForeground(QColor(0, 110, 40));

	commentFormat.setForeground(QColor(60, 160, 60));
	commentFormat.setFontItalic(true);

	spaceFormat.setForeground(QColor(210, 210, 210));
	regexpFormat.setForeground(QColor(240, 40, 140));

	commentStartExpression = "/*";
	commentEndExpression = "*/";

	functionDeclarationFormat = functionFormat;
	functionDeclarationFormat.setFontWeight(QFont::Bold);
}


int ScriptHighlighter::addCustomRule(const QStringList & keywords, const QColor & color, const QTextCharFormat & format) {
	if (keywords.isEmpty()) return 0;
	HighlightingRule rule;
	rule.format = format;
	rule.format.setForeground(color);
	QVector<HighlightingRule> hrv;
	foreach (QString k, keywords) {
		if (k.isEmpty()) continue;
		if (k.contains('(')) {
			k.resize(k.indexOf('('));
			k = k.trimmed();
		}
		rule.pattern = QRegularExpression("\\b" + k + "\\b");
		hrv << rule;
	}
	customHighlightingRules[nid] = hrv;
	++nid;
	return nid - 1;
}


int ScriptHighlighter::addCustomRule(const QList<QCodeEdit::ACEntry> & entries, const QColor & color, const QTextCharFormat & format) {
	QStringList kwl;
	foreach (const QCodeEdit::ACEntry & e, entries) {kwl << e.name;}
	return addCustomRule(kwl, color, format);
}


void ScriptHighlighter::removeCustomRule(int id) {
	customHighlightingRules.remove(id);
}


void ScriptHighlighter::setCharFormat(const QString & text, const QString & char_br, bool ext) {
	QRegularExpression ext_expression = QRegularExpression("\\${[\\w.()+\\-\\*\\/ \\t\\[\\],]+}");
	auto ranges = ScriptAnalyzer::findRanges(text, char_br);
	for (const auto & r: ranges) {
		setFormat(r.first, r.second, quotationFormat);
		if (ext) {
			auto matchIterator = ext_expression.globalMatch(text.mid(r.first, r.second));
			while (matchIterator.hasNext()) {
				QRegularExpressionMatch match = matchIterator.next();
				setFormat(r.first + match.capturedStart(), match.capturedLength(), quotationFormatE);
			}
		}
	}
}


void ScriptHighlighter::highlightBlock(const QString & text) {
	if (!currentBlock().isValid()) return;
	QRegularExpression expression;
	QRegularExpressionMatchIterator matchIterator;

	
	foreach (const HighlightingRule & rule, rules_chars) {
		QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
		while (matchIterator.hasNext()) {
			QRegularExpressionMatch match = matchIterator.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}
	
	QList<QVector<HighlightingRule> > hrv = customHighlightingRules.values();
	for (int v = 0; v < hrv.size(); ++v) {
		foreach (const HighlightingRule & rule, hrv[v]) {
			QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
			while (matchIterator.hasNext()) {
				QRegularExpressionMatch match = matchIterator.next();
				setFormat(match.capturedStart(), match.capturedLength(), rule.format);
			}
		}
	}

	foreach (const HighlightingRule & rule, highlightingRules) {
		QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
		while (matchIterator.hasNext()) {
			QRegularExpressionMatch match = matchIterator.next();
			setFormat(match.capturedStart(), match.capturedLength(), rule.format);
		}
	}

	expression = QRegularExpression("(?<=\\bfunction\\b)\\s*\\b\\w+\\b(?=\\s*\\()");
	matchIterator = expression.globalMatch(text);
	while (matchIterator.hasNext()) {
		QRegularExpressionMatch match = matchIterator.next();
		setFormat(match.capturedStart(), match.capturedLength(), functionDeclarationFormat);
	}
	
	expression = QRegularExpression("#[^\\n]*");
	matchIterator = expression.globalMatch(text);
	while (matchIterator.hasNext()) {
		QRegularExpressionMatch match = matchIterator.next();
		setFormat(match.capturedStart(), match.capturedLength(), macroFormat);
	}
	
	setCharFormat(text, "\"");
	setCharFormat(text, "'");
	setCharFormat(text, "`", true);

	
	expression = QRegularExpression("(?<=\\(|=|,)\\s*\\/.*\\/\\w+");
	matchIterator = expression.globalMatch(text);
	while (matchIterator.hasNext()) {
		QRegularExpressionMatch match = matchIterator.next();
		setFormat(match.capturedStart(), match.capturedLength(), regexpFormat);
	}

	expression = QRegularExpression("//[^\\n]*");
	matchIterator = expression.globalMatch(text);
	while (matchIterator.hasNext()) {
		QRegularExpressionMatch match = matchIterator.next();
		setFormat(match.capturedStart(), match.capturedLength(), commentFormat);
	}

	int startIndex = 0;
	if (previousBlockState() != 1)
		startIndex = text.indexOf(commentStartExpression);

	setCurrentBlockState(0);
	while (startIndex >= 0) {
		int endIndex = text.indexOf(commentEndExpression, startIndex);
		int commentLength = 0;
		if (endIndex == -1) {
			setCurrentBlockState(1);
			commentLength = text.length() - startIndex;
		} else {
			commentLength = endIndex - startIndex + 2;
		}
		setFormat(startIndex, commentLength, commentFormat);
		startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
	}

	expression = QRegularExpression("[ \\t]");
	matchIterator = expression.globalMatch(text);
	while (matchIterator.hasNext()) {
		QRegularExpressionMatch match = matchIterator.next();
		setFormat(match.capturedStart(), match.capturedLength(), spaceFormat);
	}

}
