// Scintilla source code edit control
/** @file LexPrism.cxx
 ** Lexers for the Prism language.
 **/
// Copyright 2010 by Florent Teichteil-Königsbuch <florent.teichteil@onera.fr>
// The License.txt file describes the conditions under which this software may be distributed.

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>

#include "Platform.h"

#include "PropSet.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "KeyWords.h"
#include "Scintilla.h"
#include "SciLexer.h"
#include "CharacterSet.h"
#include "CharClassify.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

static inline bool isPrismOperator(char ch) {
	if (isascii(ch) && isalnum(ch))
		return false;
	
	if ((ch == '!') || (ch == '&') || (ch == '|') || (ch == '=') || (ch == '-') ||
		(ch == '<') || (ch == '>') || (ch == ':') || (ch == ';') || (ch == ',') ||
		(ch == '.') || (ch == '(') || (ch == ')') || (ch == '[') || (ch == ']') ||
		(ch == '{') || (ch == '}') || (ch == '+') || (ch == '*') || (ch == '/') ||
		(ch == '\'') || (ch == '?') || (ch == '\\'))
		return true;
		
	return false;
}

static inline bool isPrismForbidden(char ch) {
	return !isascii(ch) || (ch == '#') || (ch == '$') || (ch == '%') ||
		   (ch == '@') || (ch == '^') || (ch == '`');
}

static inline bool isPrismWordStart(char ch) {
	return isascii(ch) && !isspacechar(ch) && !isPrismOperator(ch) &&
		   !isPrismForbidden(ch) && (ch != '\n') && (ch != '\r') && (ch != '\"');
}

static void classifyWordPrism(unsigned int start, unsigned int end,
							  WordList &keywords, WordList &functions,
							  WordList &types, Accessor &styler) {
	PLATFORM_ASSERT(end >= start);
	char s[100];
	
	for (unsigned int i = 0; (i < end - start + 1) && (i < 99); i++) {
		s[i] = styler[start + i];
		s[i + 1] = '\0';
	}
	
	if (keywords.InList(s))
		styler.ColourTo(end, SCE_PRISM_KEYWORD);
	else if (functions.InList(s))
		styler.ColourTo(end, SCE_PRISM_FUNCTION);
	else if (types.InList(s))
		styler.ColourTo(end, SCE_PRISM_TYPE);
	else
		styler.ColourTo(end, SCE_PRISM_IDENTIFIER);
}

static void ColourisePrismDoc(
	unsigned int startPos,
    int length,
    int initStyle,
    WordList* keywordlists[],
    Accessor& styler)
{
	WordList &keywords = *keywordlists[0];
	WordList &functions = *keywordlists[1];
	WordList &types = *keywordlists[2];
	
	styler.StartAt(startPos);

	int state = initStyle;
	char chNext = styler[startPos];
	unsigned int lengthDoc = startPos + length;
	styler.StartSegment(startPos);
	
	for (unsigned int i = startPos; i < lengthDoc; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);

		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
		
		if (styler.IsLeadByte(ch)) {
			chNext = styler.SafeGetCharAt(i + 2);
			i += 1;
			continue;
		}
		
		if (state == SCE_PRISM_DEFAULT) {
			if ((ch == '/') && (chNext == '/')) {
				i++;
				chNext = styler.SafeGetCharAt(i + 1);
				styler.ColourTo(i - 2, state);
				state = SCE_PRISM_COMMENT;
			}
			else if (isPrismOperator(ch)) {
				styler.ColourTo(i - 1, state);
				styler.ColourTo(i, SCE_PRISM_OPERATOR);
			}
			else if (isPrismWordStart(ch) && !isdigit(ch)) {
				styler.ColourTo(i - 1, state);
				state = SCE_PRISM_IDENTIFIER;
			}
			else if (isdigit(ch)) {
				styler.ColourTo(i - 1, state);
				state = SCE_PRISM_NUMBER;
			}
			else if (ch == '\"') {
				styler.ColourTo(i - 1, state);
				state = SCE_PRISM_STRING;
			}
			else if (isPrismForbidden(ch)) {
				styler.ColourTo(i - 1, state);
				styler.ColourTo(i, SCE_PRISM_FORBIDDEN);
			}
		}
		else if (state == SCE_PRISM_COMMENT) {
			if (atEOL) {
				styler.ColourTo(i - 1, state);
				state = SCE_PRISM_DEFAULT;
			}
		}
		else if (state == SCE_PRISM_IDENTIFIER) {
			if (!isPrismWordStart(ch)) {
				classifyWordPrism(styler.GetStartSegment(), i - 1, keywords, functions, types, styler);
				
				if ((ch == '/') && (chNext == '/')) {
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i - 2, state);
					state = SCE_PRISM_COMMENT;
				}
				else if (isPrismOperator(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_PRISM_OPERATOR);
					state = SCE_PRISM_DEFAULT;
				}
				else if (isPrismForbidden(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_PRISM_FORBIDDEN);
					state = SCE_PRISM_DEFAULT;
				}
				else
					state = SCE_PRISM_DEFAULT;
			}
		}
		else if (state == SCE_PRISM_NUMBER) {
			if (!isdigit(ch)) {
				styler.ColourTo(i - 1, state);
				
				if ((ch == '/') && (chNext == '/')) {
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i - 2, state);
					state = SCE_PRISM_COMMENT;
				}
				else if (isPrismOperator(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_PRISM_OPERATOR);
					state = SCE_PRISM_DEFAULT;
				}
				else if (isPrismForbidden(ch)) {
					styler.ColourTo(i, SCE_PRISM_FORBIDDEN);
					state = SCE_PRISM_DEFAULT;
				}
				else
					state = SCE_PRISM_DEFAULT;
			}
		}
		else if (state == SCE_PRISM_STRING) {
			if (ch == '\"') {
				styler.ColourTo(i, state);
				state = SCE_PRISM_DEFAULT;
			}
		}
	}
	
	styler.ColourTo(lengthDoc - 1, state);
}

static bool IsCommentLine(int line, Accessor& styler) {
	unsigned int pos = styler.LineStart(line);
	unsigned int eol_pos = styler.LineStart(line + 1) - 1;
	char chNext = styler[pos];
	for (unsigned int i = pos; i < eol_pos; i++) {
		char ch = chNext;
		chNext = styler[i+1];
		if ((ch == '/') && (chNext == '/'))
			return true;
		else if (ch != ' ' && ch != '\t')
			return false;
	}
	return false;
}

static bool IsFormulaOrLabel(unsigned int endPos, Accessor& styler) {
	bool wordEnd = false;
	unsigned int posWordEnd = 0;
	unsigned int pos = endPos;
	char s[20]; // Prism keywords have atmost 16 characters
	while (pos > 0) {
		int style = styler.StyleAt(pos-1);
		char ch = styler.SafeGetCharAt(pos-1);
		if (ch == ';') {
			return false;
		}
		else if (!wordEnd && (style == SCE_PRISM_KEYWORD)) {
			posWordEnd = pos;
			wordEnd = true;
		}
		else if (wordEnd && ((style != SCE_PRISM_KEYWORD) || (pos == 1))) {
			if (style == SCE_PRISM_KEYWORD) { // keyword begins at position 0
				pos--;
			}
			for (unsigned int i = 0; i < posWordEnd - pos && i < 19; i++) { // Prism keywords have atmost 16 characters
				s[i] = static_cast<char>(styler[ pos + i ]);
				s[i + 1] = '\0';
			}
			if ((strcmp(s, "formula") == 0) || (strcmp(s, "label") == 0)) {
				return true;
			}
			else if ((strcmp(s, "module") == 0) ||
					 (strcmp(s, "endmodule") == 0) ||
					 (strcmp(s, "rewards") == 0) ||
					 (strcmp(s, "endrewards") == 0)) {
				return false;
			} else { // not a fold keyword so continue searching backward
				wordEnd = false;
			}
		}
		pos--;
	}
	return false;
}

static void FoldPrismDoc(unsigned int startPos, int length, int initStyle,
                       WordList* keywordlists[], Accessor& styler) {
	if (styler.GetPropertyInt("fold") == 0) return;
	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
	int visibleChars = 0;
	int lineCurrent = styler.GetLine(startPos);
	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
	int levelCurrent = levelPrev;
	unsigned int endPos = startPos + length;
	char chNext = styler[startPos];
	int styleNext = styler.StyleAt(startPos);
	char prismKeyword[20]; // Prism keywords have atmost 16 characters
	bool wordStart = false;
	unsigned int posWordStart = 0;
	for (unsigned int i = startPos; i < endPos; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);
		int style = styleNext;
		styleNext = styler.StyleAt(i + 1);
		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
		if (atEOL && foldComment && IsCommentLine(lineCurrent, styler)) {
			if (((lineCurrent == 0) || !IsCommentLine(lineCurrent - 1, styler))
				&& IsCommentLine(lineCurrent + 1, styler))
				levelCurrent++;
			else if ((lineCurrent > 0) && IsCommentLine(lineCurrent - 1, styler)
					 && !IsCommentLine(lineCurrent + 1, styler))
				levelCurrent--;
		}
		else if (ch == ';') {
			if (IsFormulaOrLabel(i, styler)) {
				levelCurrent--;
			}
		}
		else if (!wordStart && (style == SCE_PRISM_KEYWORD)) {
			posWordStart = i;
			wordStart = true;
		}
		else if (wordStart && (style != SCE_PRISM_KEYWORD)) {
			for (unsigned int j = 0; j < i - posWordStart && j < 19; j++) { // Prism keywords have atmost 16 characters
				prismKeyword[j] = static_cast<char>(styler[ posWordStart + j ]);
				prismKeyword[j + 1] = '\0';
			}
			if (strcmp(prismKeyword, "module") == 0) {
				levelCurrent++;
			}
			else if (strcmp(prismKeyword, "endmodule") == 0) {
				levelCurrent--;
			}
			else if (strcmp(prismKeyword, "rewards") == 0) {
				levelCurrent++;
			}
			else if (strcmp(prismKeyword, "endrewards") == 0) {
				levelCurrent--;
			}
			else if (strcmp(prismKeyword, "formula") == 0) {
				levelCurrent++;
			}
			else if (strcmp(prismKeyword, "label") == 0) {
				levelCurrent++;
			}
			wordStart = false;
		}
		if (atEOL) {
			int lev = levelPrev;
			if (visibleChars == 0 && foldCompact)
				lev |= SC_FOLDLEVELWHITEFLAG;
			if ((levelCurrent > levelPrev) && (visibleChars > 0))
				lev |= SC_FOLDLEVELHEADERFLAG;
			if (lev != styler.LevelAt(lineCurrent)) {
				styler.SetLevel(lineCurrent, lev);
			}
			lineCurrent++;
			levelPrev = levelCurrent;
			visibleChars = 0;
		}
		if (!isspacechar(ch))
			visibleChars++;
	}
	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
}

static const char * const prismWordListDesc[] = {
	"keywords",
	"functions",
	"types",
	0,
};

LexerModule lmPrism(SCLEX_PRISM, ColourisePrismDoc, "prism", FoldPrismDoc, prismWordListDesc);
