// Scintilla source code edit control
/** @file LexAltaRica.cxx
 ** Lexers for the AltaRica 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 isAltaRicaOperator(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 == ':'))
		return true;
		
	return false;
}

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

static inline bool isAltaRicaWordStart(char ch) {
	return isascii(ch) && !isspacechar(ch) && !isAltaRicaOperator(ch) &&
		   !isAltaRicaForbidden(ch) && (ch != '\n') && (ch != '\r');
}

static void classifyWordAltaRica(unsigned int start, unsigned int end, WordList &keywords,
								 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] = MakeUpperCase(styler[start + i]);
		s[i + 1] = '\0';
	}
	
	if (keywords.InList(s))
		styler.ColourTo(end, SCE_ALTARICA_KEYWORD);
	else if (types.InList(s))
		styler.ColourTo(end, SCE_ALTARICA_TYPE);
	else
		styler.ColourTo(end, SCE_ALTARICA_IDENTIFIER);
}

static void ColouriseAltaRicaDoc(
	unsigned int startPos,
    int length,
    int initStyle,
    WordList* keywordlists[],
    Accessor& styler)
{
	WordList &keywords = *keywordlists[0];
	WordList &types = *keywordlists[1];
	
	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_ALTARICA_DEFAULT) {
			if ((ch == '/') && (chNext == '/')) {
				i++;
				chNext = styler.SafeGetCharAt(i + 1);
				styler.ColourTo(i - 2, state);
				state = SCE_ALTARICA_COMMENTLINE;
			}
			else if ((ch == '/') && (chNext == '*')) {
				i++;
				chNext = styler.SafeGetCharAt(i + 1);
				styler.ColourTo(i - 2, state);
				state = SCE_ALTARICA_COMMENT;
			}
			else if (isAltaRicaOperator(ch)) {
				styler.ColourTo(i - 1, state);
				styler.ColourTo(i, SCE_ALTARICA_OPERATOR);
			}
			else if (isAltaRicaWordStart(ch) && !isdigit(ch)) {
				styler.ColourTo(i - 1, state);
				state = SCE_ALTARICA_IDENTIFIER;
			}
			else if (isdigit(ch)) {
				styler.ColourTo(i - 1, state);
				state = SCE_ALTARICA_NUMBER;
			}
			else if (isAltaRicaForbidden(ch)) {
				styler.ColourTo(i - 1, state);
				styler.ColourTo(i, SCE_ALTARICA_FORBIDDEN);
			}
		}
		else if (state == SCE_ALTARICA_COMMENTLINE) {
			if (atEOL) {
				styler.ColourTo(i - 1, state);
				state = SCE_ALTARICA_DEFAULT;
			}
		}
		else if (state == SCE_ALTARICA_COMMENT) {
			if ((ch == '*') && (chNext == '/')) {
				i++;
				chNext = styler.SafeGetCharAt(i + 1);
				styler.ColourTo(i, state);
				state = SCE_ALTARICA_DEFAULT;
			}
		}
		else if (state == SCE_ALTARICA_IDENTIFIER) {
			if (!isAltaRicaWordStart(ch)) {
				classifyWordAltaRica(styler.GetStartSegment(), i - 1, keywords, types, styler);
				
				if ((ch == '/') && (chNext == '/')) {
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i - 2, state);
					state = SCE_ALTARICA_COMMENTLINE;
				}
				else if ((ch == '/') && (chNext == '*')) {
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i - 2, state);
					state = SCE_ALTARICA_COMMENT;
				}
				else if (isAltaRicaOperator(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_ALTARICA_OPERATOR);
					state = SCE_ALTARICA_DEFAULT;
				}
				else if (isAltaRicaForbidden(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_ALTARICA_FORBIDDEN);
					state = SCE_ALTARICA_DEFAULT;
				}
				else
					state = SCE_ALTARICA_DEFAULT;
			}
		}
		else if (state == SCE_ALTARICA_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_ALTARICA_COMMENTLINE;
				}
				else if ((ch == '/') && (chNext == '*')) {
					i++;
					chNext = styler.SafeGetCharAt(i + 1);
					styler.ColourTo(i - 2, state);
					state = SCE_ALTARICA_COMMENT;
				}
				else if (isAltaRicaOperator(ch)) {
					styler.ColourTo(i - 1, state);
					styler.ColourTo(i, SCE_ALTARICA_OPERATOR);
					state = SCE_ALTARICA_DEFAULT;
				}
				else if (isAltaRicaForbidden(ch)) {
					styler.ColourTo(i, SCE_ALTARICA_FORBIDDEN);
					state = SCE_ALTARICA_DEFAULT;
				}
				else
					state = SCE_ALTARICA_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;
	if (pos == eol_pos) {
		int style = styler.StyleAt(pos);
		return (style == SCE_ALTARICA_COMMENT);
	}
	for (unsigned int i = pos; i < eol_pos; i++) {
		int style = styler.StyleAt(i);
		if (style == SCE_ALTARICA_COMMENTLINE)
			return true;
		else if (style == SCE_ALTARICA_COMMENT) {
			if (i+1 == eol_pos) {
				return true;
			}
		} else {
			char ch = styler[i];
			if ((ch != ' ') && (ch != '\t')) {
				return false;
			}
		}
	}
	return false;
}

static bool PreviousFoldKeywordIsInsideNode(unsigned int endPos, Accessor& styler) {
	unsigned int pos = endPos;
	bool wordEnd = false;
	unsigned int posWordEnd = 0;
	char s[10]; // AltaRica keywords have atmost 9 characters
	while (pos > 0) {
		int style = styler.StyleAt(pos-1);
		if (!wordEnd && (style == SCE_ALTARICA_KEYWORD)) {
			posWordEnd = pos;
			wordEnd = true;
		}
		else if (wordEnd && ((style != SCE_ALTARICA_KEYWORD) || (pos == 1))) {
			if (style == SCE_ALTARICA_KEYWORD) { // keyword begins at position 0
				pos--;
			}
			for (unsigned int i = 0; i < posWordEnd - pos && i < 9; i++) { // AltaRica keywords have atmost 9 characters
				s[i] = static_cast<char>(styler[ pos + i ]);
				s[i + 1] = '\0';
			}
			if ((CompareCaseInsensitive(s, "node") == 0) ||
				(CompareCaseInsensitive(s, "edon") == 0) ||
				(CompareCaseInsensitive(s, "struct") == 0) ||
				(CompareCaseInsensitive(s, "tcurts") == 0) ||
				(CompareCaseInsensitive(s, "domain") == 0) ||
				(CompareCaseInsensitive(s, "const") == 0)) {
				return false;
			}
			else if ((CompareCaseInsensitive(s, "sub") == 0) ||
					 (CompareCaseInsensitive(s, "flow") == 0) ||
					 (CompareCaseInsensitive(s, "state") == 0) ||
					 (CompareCaseInsensitive(s, "event") == 0) ||
					 (CompareCaseInsensitive(s, "trans") == 0) ||
					 (CompareCaseInsensitive(s, "assert") == 0) ||
					 (CompareCaseInsensitive(s, "init") == 0)) {
				return true;
			} else { // not a fold keyword so continue searching backward
				wordEnd = false;
			}
		}
		pos--;
	}
	return false;
}

static bool NextLineIsFoldKeyword(int line, Accessor& styler) {
	unsigned int pos = styler.LineStart(line + 1);
	unsigned int eol_pos = styler.LineStart(line + 2) - 1;
	bool wordStart = false;
	unsigned int posWordStart = 0;
	char s[10]; // AltaRica keywords have atmost 9 characters
	for (unsigned int i = pos; i <= eol_pos; i++) {
		int style = styler.StyleAt(i);
		if (!wordStart && (style == SCE_ALTARICA_KEYWORD)) {
			posWordStart = i;
			wordStart = true;
		}
		else if (wordStart && ((style != SCE_ALTARICA_KEYWORD) || (i == eol_pos))) {
			for (unsigned int j = 0; j < i - posWordStart && j < 9; j++) { // AltaRica keywords have atmost 9 characters
				s[j] = static_cast<char>(styler[ posWordStart + j ]);
				s[j + 1] = '\0';
			}
			if ((CompareCaseInsensitive(s, "node") == 0) ||
				(CompareCaseInsensitive(s, "edon") == 0) ||
				(CompareCaseInsensitive(s, "struct") == 0) ||
				(CompareCaseInsensitive(s, "tcurts") == 0) ||
				(CompareCaseInsensitive(s, "sub") == 0) ||
			    (CompareCaseInsensitive(s, "flow") == 0) ||
			    (CompareCaseInsensitive(s, "state") == 0) ||
			    (CompareCaseInsensitive(s, "event") == 0) ||
			    (CompareCaseInsensitive(s, "trans") == 0) ||
			    (CompareCaseInsensitive(s, "assert") == 0) ||
			    (CompareCaseInsensitive(s, "init") == 0) ||
				(CompareCaseInsensitive(s, "domain") == 0) ||
				(CompareCaseInsensitive(s, "const") == 0)) {
				return true;
			}
			else
				return false;
		}
		else if(!wordStart) {
			char ch = styler[i];
			if ((ch != ' ') && (ch != '\t')) {
				return false;
			}
		}
	}
	return false;
}

static void FoldAltaRicaDoc(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];
	bool wordStart = false;
	unsigned int posWordStart = 0;
	char altaricaKeyword[10]; // AltaRica keywords have atmost 9 characters
	for (unsigned int i = startPos; i < endPos; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i + 1);
		int style = styler.StyleAt(i);
		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
		if (!wordStart && (style == SCE_ALTARICA_KEYWORD)) {
			posWordStart = i;
			wordStart = true;
		}
		else if (wordStart && (style != SCE_ALTARICA_KEYWORD)) {
			for (unsigned int j = 0; j < i - posWordStart && j < 9; j++) { // AltaRica keywords have atmost 9 characters
				altaricaKeyword[j] = static_cast<char>(styler[ posWordStart + j ]);
				altaricaKeyword[j + 1] = '\0';
			}
			if (CompareCaseInsensitive(altaricaKeyword, "node") == 0) {
				levelCurrent++;
			}
			else if (CompareCaseInsensitive(altaricaKeyword, "edon") == 0) {
				levelCurrent--;
			}
			else if (CompareCaseInsensitive(altaricaKeyword, "struct") == 0) {
				levelCurrent++;
			}
			else if (CompareCaseInsensitive(altaricaKeyword, "tcurts") == 0) {
				levelCurrent--;
			}
			else if ((CompareCaseInsensitive(altaricaKeyword, "sub") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "flow") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "state") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "event") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "trans") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "assert") == 0) ||
					 (CompareCaseInsensitive(altaricaKeyword, "init") == 0)) {
				levelCurrent++;
			}
			wordStart = false;
		}
		if (atEOL) {
			// Comment folding
			if (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 (NextLineIsFoldKeyword(lineCurrent, styler) && PreviousFoldKeywordIsInsideNode(i, styler)) {
					levelCurrent--;
			}
			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 altaricaWordListDesc[] = {
	"keywords",
	"types",
	0,
};

LexerModule lmAltaRica(SCLEX_ALTARICA, ColouriseAltaRicaDoc, "altarica", FoldAltaRicaDoc, altaricaWordListDesc);
