// Scintilla source code edit control
/** @file LexDMIS.cxx
 ** Lexer for DMIS.
  **/
// Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
// Copyright 2013-2014 by Andreas Tscharner <andy@vis.ethz.ch>
// The License.txt file describes the conditions under which this software may be distributed.


#include <cstdlib>
#include <cassert>
#include <cstring>
#include <cctype>

#include <string>
#include <string_view>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"
#include "DefaultLexer.h"

using namespace Lexilla;


static const char *const DMISWordListDesc[] = {
	"DMIS Major Words",
	"DMIS Minor Words",
	"Unsupported DMIS Major Words",
	"Unsupported DMIS Minor Words",
	"Keywords for code folding start",
	"Corresponding keywords for code folding end",
	0
};


class LexerDMIS : public DefaultLexer
{
	private:
		char *m_wordListSets;
		WordList m_majorWords;
		WordList m_minorWords;
		WordList m_unsupportedMajor;
		WordList m_unsupportedMinor;
		WordList m_codeFoldingStart;
		WordList m_codeFoldingEnd;

		char * SCI_METHOD UpperCase(char *item);
		void SCI_METHOD InitWordListSets(void);

	public:
		LexerDMIS(void);
		virtual ~LexerDMIS(void);

		int SCI_METHOD Version() const override {
			return Scintilla::lvRelease5;
		}

		void SCI_METHOD Release() override {
			delete this;
		}

		const char * SCI_METHOD PropertyNames() override {
			return NULL;
		}

		int SCI_METHOD PropertyType(const char *) override {
			return -1;
		}

		const char * SCI_METHOD DescribeProperty(const char *) override {
			return NULL;
		}

		Sci_Position SCI_METHOD PropertySet(const char *, const char *) override {
			return -1;
		}

		const char * SCI_METHOD PropertyGet(const char *) override {
			return NULL;
		}

		Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;

		void * SCI_METHOD PrivateCall(int, void *) override {
			return NULL;
		}

		static ILexer5 *LexerFactoryDMIS() {
			return new LexerDMIS;
		}

		const char * SCI_METHOD DescribeWordListSets() override;
		void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, Scintilla::IDocument *pAccess) override;
		void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, Scintilla::IDocument *pAccess) override;
};


char * SCI_METHOD LexerDMIS::UpperCase(char *item)
{
	char *itemStart;


	itemStart = item;
	while (item && *item) {
		*item = toupper(*item);
		item++;
	};
	return itemStart;
}

void SCI_METHOD LexerDMIS::InitWordListSets(void)
{
	size_t totalLen = 0;


	for (int i=0; DMISWordListDesc[i]; i++) {
		totalLen += strlen(DMISWordListDesc[i]);
		totalLen++;
	};

	totalLen++;
	this->m_wordListSets = new char[totalLen];
	memset(this->m_wordListSets, 0, totalLen);

	for (int i=0; DMISWordListDesc[i]; i++) {
		strcat(this->m_wordListSets, DMISWordListDesc[i]);
		strcat(this->m_wordListSets, "\n");
	};
}


LexerDMIS::LexerDMIS(void) : DefaultLexer("DMIS", SCLEX_DMIS) {
	this->InitWordListSets();

	this->m_majorWords.Clear();
	this->m_minorWords.Clear();
	this->m_unsupportedMajor.Clear();
	this->m_unsupportedMinor.Clear();
	this->m_codeFoldingStart.Clear();
	this->m_codeFoldingEnd.Clear();
}

LexerDMIS::~LexerDMIS(void) {
	delete[] this->m_wordListSets;
}

Sci_Position SCI_METHOD LexerDMIS::WordListSet(int n, const char *wl)
{
	switch (n) {
		case 0:
			this->m_majorWords.Clear();
			this->m_majorWords.Set(wl);
			break;
		case 1:
			this->m_minorWords.Clear();
			this->m_minorWords.Set(wl);
			break;
		case 2:
			this->m_unsupportedMajor.Clear();
			this->m_unsupportedMajor.Set(wl);
			break;
		case 3:
			this->m_unsupportedMinor.Clear();
			this->m_unsupportedMinor.Set(wl);
			break;
		case 4:
			this->m_codeFoldingStart.Clear();
			this->m_codeFoldingStart.Set(wl);
			break;
		case 5:
			this->m_codeFoldingEnd.Clear();
			this->m_codeFoldingEnd.Set(wl);
			break;
		default:
			return -1;
			break;
	}

	return 0;
}

const char * SCI_METHOD LexerDMIS::DescribeWordListSets()
{
	return this->m_wordListSets;
}

void SCI_METHOD LexerDMIS::Lex(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, Scintilla::IDocument *pAccess)
{
	const Sci_PositionU MAX_STR_LEN = 100;

	LexAccessor styler(pAccess);
	StyleContext scCTX(startPos, lengthDoc, initStyle, styler);
	CharacterSet setDMISNumber(CharacterSet::setDigits, ".-+eE");
	CharacterSet setDMISWordStart(CharacterSet::setAlpha, "-234", 0x80, true);
	CharacterSet setDMISWord(CharacterSet::setAlpha);


	bool isIFLine = false;

	for (; scCTX.More(); scCTX.Forward()) {
		if (scCTX.atLineEnd) {
			isIFLine = false;
		};

		switch (scCTX.state) {
			case SCE_DMIS_DEFAULT:
				if (scCTX.Match('$', '$')) {
					scCTX.SetState(SCE_DMIS_COMMENT);
					scCTX.Forward();
				};
				if (scCTX.Match('\'')) {
					scCTX.SetState(SCE_DMIS_STRING);
				};
				if (IsADigit(scCTX.ch) || ((scCTX.Match('-') || scCTX.Match('+')) && IsADigit(scCTX.chNext))) {
					scCTX.SetState(SCE_DMIS_NUMBER);
					break;
				};
				if (setDMISWordStart.Contains(scCTX.ch)) {
					scCTX.SetState(SCE_DMIS_KEYWORD);
				};
				if (scCTX.Match('(') && (!isIFLine)) {
					scCTX.SetState(SCE_DMIS_LABEL);
				};
				break;

			case SCE_DMIS_COMMENT:
				if (scCTX.atLineEnd) {
					scCTX.SetState(SCE_DMIS_DEFAULT);
				};
				break;

			case SCE_DMIS_STRING:
				if (scCTX.Match('\'')) {
					scCTX.SetState(SCE_DMIS_DEFAULT);
				};
				break;

			case SCE_DMIS_NUMBER:
				if (!setDMISNumber.Contains(scCTX.ch)) {
					scCTX.SetState(SCE_DMIS_DEFAULT);
				};
				break;

			case SCE_DMIS_KEYWORD:
				if (!setDMISWord.Contains(scCTX.ch)) {
					char tmpStr[MAX_STR_LEN];
					memset(tmpStr, 0, MAX_STR_LEN*sizeof(char));
					scCTX.GetCurrent(tmpStr, (MAX_STR_LEN-1));
					// The following strncpy is copying from a string back onto itself which is weird and causes warnings
					// but is harmless so turn off the warning
#if defined(__GNUC__) && !defined(__clang__)
// Disable warning for strncpy
#pragma GCC diagnostic ignored "-Wrestrict"
#endif
					strncpy(tmpStr, this->UpperCase(tmpStr), (MAX_STR_LEN-1));

					if (this->m_minorWords.InList(tmpStr)) {
						scCTX.ChangeState(SCE_DMIS_MINORWORD);
					};
					if (this->m_majorWords.InList(tmpStr)) {
						isIFLine = (strcmp(tmpStr, "IF") == 0);
						scCTX.ChangeState(SCE_DMIS_MAJORWORD);
					};
					if (this->m_unsupportedMajor.InList(tmpStr)) {
						scCTX.ChangeState(SCE_DMIS_UNSUPPORTED_MAJOR);
					};
					if (this->m_unsupportedMinor.InList(tmpStr)) {
						scCTX.ChangeState(SCE_DMIS_UNSUPPORTED_MINOR);
					};

					if (scCTX.Match('(') && (!isIFLine)) {
						scCTX.SetState(SCE_DMIS_LABEL);
					} else {
						scCTX.SetState(SCE_DMIS_DEFAULT);
					};
				};
				break;

			case SCE_DMIS_LABEL:
				if (scCTX.Match(')')) {
					scCTX.SetState(SCE_DMIS_DEFAULT);
				};
				break;
		};
	};
	scCTX.Complete();
}

void SCI_METHOD LexerDMIS::Fold(Sci_PositionU startPos, Sci_Position lengthDoc, int, Scintilla::IDocument *pAccess)
{
	const int MAX_STR_LEN = 100;

	LexAccessor styler(pAccess);
	Sci_PositionU endPos = startPos + lengthDoc;
	char chNext = styler[startPos];
	Sci_Position lineCurrent = styler.GetLine(startPos);
	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
	int levelCurrent = levelPrev;
	int strPos = 0;
	bool foldWordPossible = false;
	CharacterSet setDMISFoldWord(CharacterSet::setAlpha);
	char *tmpStr;


	tmpStr = new char[MAX_STR_LEN];
	memset(tmpStr, 0, MAX_STR_LEN*sizeof(char));

	for (Sci_PositionU i=startPos; i<endPos; i++) {
		char ch = chNext;
		chNext = styler.SafeGetCharAt(i+1);

		bool atEOL = ((ch == '\r' && chNext != '\n') || (ch == '\n'));

		if (strPos >= (MAX_STR_LEN-1)) {
			strPos = MAX_STR_LEN-1;
		};

		int style = styler.StyleAt(i);
		bool noFoldPos = ((style == SCE_DMIS_COMMENT) || (style == SCE_DMIS_STRING));

		if (foldWordPossible) {
			if (setDMISFoldWord.Contains(ch)) {
				tmpStr[strPos++] = ch;
			} else {
				tmpStr = this->UpperCase(tmpStr);
				if (this->m_codeFoldingStart.InList(tmpStr) && (!noFoldPos)) {
					levelCurrent++;
				};
				if (this->m_codeFoldingEnd.InList(tmpStr) && (!noFoldPos)) {
					levelCurrent--;
				};
				memset(tmpStr, 0, MAX_STR_LEN*sizeof(char));
				strPos = 0;
				foldWordPossible = false;
			};
		} else {
			if (setDMISFoldWord.Contains(ch)) {
				tmpStr[strPos++] = ch;
				foldWordPossible = true;
			};
		};

		if (atEOL || (i == (endPos-1))) {
			int lev = levelPrev;

			if (levelCurrent > levelPrev) {
				lev |= SC_FOLDLEVELHEADERFLAG;
			};
			if (lev != styler.LevelAt(lineCurrent)) {
				styler.SetLevel(lineCurrent, lev);
			};
			lineCurrent++;
			levelPrev = levelCurrent;
		};
	};
	delete[] tmpStr;
}


extern const LexerModule lmDMIS(SCLEX_DMIS, LexerDMIS::LexerFactoryDMIS, "DMIS", DMISWordListDesc);
