#include "global.h"
#include "clexical.h"
#include <stdlib.h>

CLexicalAnalyzer::CLexicalAnalyzer(void* mem)
{
	_input = (char*)mem;
	token = (char*)mallocx(LEXICAL_MAX_TOKEN_LENGTH+1);
	Restart();
}

CLexicalAnalyzer::~CLexicalAnalyzer()
{
	if (token)
		freex(token);
}

LEXICAL_HILIGHTED_ATOMS *CLexicalAnalyzer::HilightsWords(CStringTable *atomsIDB)
{
	LEXICAL_HILIGHTED_ATOMS_POS hap;
	CMemory *m = _NEW(CMemory());
	DWORD count = 0;

	//priprav EDB atomy
	const CDbStructure* dbst = app.GetDatabase()->GetStructure();

	//zrestartuj lex. an.
	Restart();
	const char* token;

	DWORD lastTokenType, tokenType = LEXICAL_TOKEN_EOL;
	
	while (!EndOfInput())
	{
		try
		{
			//string otestujeme zvlast, lebo pri chybajucich koncovych zatvorkach sa o tom nedozvieme
			if (LookAheadToken() == LEXICAL_TOKEN_STRING)
			{
				hap.pos_begin = GetCharPos();

				//readtoken moze hodit exception, ak chybaju koncove "
				try {
					tokenType = ReadToken(&token);
				} catch (char *str)
				{
					str;//kvoli warningu
				}
				hap.pos_end = GetCharPos();//ak bol exception, sme na novom riadku
				hap.type = LEXICAL_HILIGHT_STRING;
				m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
				count++;

				continue;
			}

			if (tokenType != LEXICAL_TOKEN_WHITESPACE)//preskocme medzery
				lastTokenType = tokenType;
			tokenType = ReadToken(&token);

			/*
			if ( (typeToken == LEXICAL_TOKEN_EOL) && (!strcmp(token,"//")))
			{
				//komentar
			}
			*/

			switch (tokenType)
			{
			case LEXICAL_TOKEN_STRING:
				hap.pos_begin = GetCharPos() - strlen(token);
				hap.pos_end = GetCharPos();
				hap.type = LEXICAL_HILIGHT_STRING;
				m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
				count++;
				break;
			case LEXICAL_TOKEN_NUMBER:
				hap.pos_begin = GetCharPos() - strlen(token);
				hap.pos_end = GetCharPos();
				hap.type = LEXICAL_HILIGHT_NUMBER;
				m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
				count++;
				break;
			case LEXICAL_TOKEN_KEYWORD:
				hap.pos_begin = GetCharPos() - strlen(token);
				hap.pos_end = GetCharPos();
				hap.type = LEXICAL_HILIGHT_KEYWORD;
				m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
				count++;
				break;
			case LEXICAL_TOKEN_IDENTIFIER:
				//EDB atom?
				for (DWORD i=0; i<dbst->nTables; i++)
				{
					if (!stricmp(dbst->tables[i].name, token))
					{
						//nasli sme EDB atom v texte
						//zvyrazni ho len vtedy, ak ide za nim (, alebo je tam koniec riadka (aby nezvyraznoval premenne rovnakeho nazvu)
						if ( (LookAheadToken() == LEXICAL_TOKEN_LEFTPARENTHESIS))// ||
//								(LookAheadToken() == LEXICAL_TOKEN_EOL) )
						{
							hap.pos_begin = GetCharPos() - strlen(token);
							hap.pos_end = GetCharPos();
							hap.type = LEXICAL_HILIGHT_EDBATOM;
							m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
							count++;
							break;
						}
					}
				}

				//EDB atom to nie je
				//IDB atom?
				if (i == dbst->nTables)
				{
					if (atomsIDB->Index((char*)token) != -1 )
					{
						//nasli sme IDB atom v texte
						//zvyrazni ho len vtedy, ak ide za nim ( /*, alebo je tam koniec riadka (aby nezvyraznoval premenne rovnakeho nazvu)*/
						if ( (LookAheadToken() == LEXICAL_TOKEN_LEFTPARENTHESIS) /*||
								(LookAheadToken() == LEXICAL_TOKEN_EOL)*/ )
						{
							hap.pos_begin = GetCharPos() - strlen(token);
							hap.pos_end = GetCharPos();
							hap.type = LEXICAL_HILIGHT_IDBATOM;
							m->AddData(&hap, sizeof(LEXICAL_HILIGHTED_ATOMS_POS));
							count++;
							break;
						}
					}
				}
			}
		} catch (char *err)
		{
			//dlzka tokenu presiahla maximum
			err;
		}
	}
	//vytvor navratovu strukturu
	LEXICAL_HILIGHTED_ATOMS *ret =(LEXICAL_HILIGHTED_ATOMS*)mallocx(sizeof(LEXICAL_HILIGHTED_ATOMS) + m->GetSize() );
	//skopiruj data
	if (m->GetSize())
		::CopyMemory(&ret->item, m->GetPointer(), m->GetSize() );
	ret->count = count;

	_DELETE(m);

	return ret;
}

bool CLexicalAnalyzer::IsKeyword(const char *str)
{
	return (!stricmp(str, "and") || !stricmp(str, "not") || !stricmp(str, "answer") ||
			!stricmp(str, "avg") || !stricmp(str, "sum") || !stricmp(str, "min") || !stricmp(str, "max") || !stricmp(str, "count") ||
			!stricmp(str, "groupby") );
}

void CLexicalAnalyzer::Restart()
{
	input = _input;
	lineNum = 1;
	charPos = charPosInLine = 1;
	flagEOL = false;
	charReaded = 0;
	inputIndex = 0;
}

bool CLexicalAnalyzer::EndOfInput()
{
	if (input[inputIndex] == 0)
		return true;
	else
		return false;
}

DWORD CLexicalAnalyzer::GetLineNumber()
{
	return lineNum;
}

DWORD CLexicalAnalyzer::GetCharPos()
{
	return charPos;
}

DWORD CLexicalAnalyzer::GetCharPosInLine()
{
	return charPosInLine;
}

char CLexicalAnalyzer::GetChar()
{
	return charReaded;
}

void CLexicalAnalyzer::RollbackToken()
{
	inputIndex = inputIndexOld;
	charPosInLine = charPosInLineOld;
	charPos = charPosOld;
	lineNum = lineNumOld;
	charReaded = charReadedOld;
	token[0] = 0;
}

char CLexicalAnalyzer::ReadChar()
{
	if (input[inputIndex] == 0)
		//koniec vstupu
		return 0;
	if (charReaded == 13)
	{
		lineNum++;
		charPosInLine = 1;
	}

	charReaded = input[inputIndex++];
	if (charReaded != 13)
		charPosInLine++;//ak je novy riadok (13), tak nezarataj tento znak
	charPos++;

	return charReaded;
}

char CLexicalAnalyzer::LookAheadChar()
{
	return input[inputIndex];//este nenacitany znak
}

DWORD CLexicalAnalyzer::LookAheadToken()
{
	char c = LookAheadChar();

	if ( (c>='0') && (c<='9') )
		return LEXICAL_TOKEN_NUMBER;
	if ( (c==' ') || (c=='\t') )
		return LEXICAL_TOKEN_WHITESPACE;
	if ( ( (c>='A') && (c<='Z') ) || ( (c>='a') && (c<='z') ))// || (c=='_') )
		return LEXICAL_TOKEN_IDENTIFIER;

	switch (c)
	{
	case '!':
		return LEXICAL_TOKEN_EXCLAMATION;
	case '+':
		return LEXICAL_TOKEN_PLUS;
	case '-':
		return LEXICAL_TOKEN_MINUS;
	case '*':
		return LEXICAL_TOKEN_MULTIPLY;
	case '/':
		if (input[inputIndex+1] == '/') //lookaheadaheadchar
			return LEXICAL_TOKEN_EOL;
		return LEXICAL_TOKEN_DIVIDE;
	case '(':
		return LEXICAL_TOKEN_LEFTPARENTHESIS;
	case ')':
		return LEXICAL_TOKEN_RIGHTPARENTHESIS;
	case '"':
	case '\'':
		return LEXICAL_TOKEN_STRING;
	case '<':
		return LEXICAL_TOKEN_LESS;
	case '>':
		return LEXICAL_TOKEN_GREATER;
	case '=':
		return LEXICAL_TOKEN_EQUAL;
	case '.':
		return LEXICAL_TOKEN_DOT;
	case ',':
		return LEXICAL_TOKEN_COMMA;
	case ';':
		return LEXICAL_TOKEN_SEMICOLON;
	case '_':
		return LEXICAL_TOKEN_UNDERSCORE;
	case 13:
	case 0:
		return LEXICAL_TOKEN_EOL;
	//neznamy:
	default:
		return LEXICAL_TOKEN_UNDEFINED;
	}
}

DWORD CLexicalAnalyzer::GetToken(const char **str)
{
	if (str == NULL)
		return tokenType;

	*str = (const char*)token;
	return tokenType;
}

void CLexicalAnalyzer::GoToNextLine()
{
	if (charReaded==13)
		return;//sme na zaciatku este nenacitaneho riadku

	while (true)
	{
		char c = ReadChar();
		if ( (c==0) || (c==13) )
			return;
	}
}

void CLexicalAnalyzer::SkipEmptyLines()
{
	const char *token;
	while (!EndOfInput())
	{
		DWORD tokenType = LookAheadToken();
		if (tokenType == LEXICAL_TOKEN_WHITESPACE)	//aby sme preskocili tento riadok musi nasledovat jedine EOL
		{
			tokenType = ReadToken(&token);//nacitaj whitespaces
			if (LookAheadToken() == LEXICAL_TOKEN_EOL)
				ReadToken(&token);//docitaj EOL (precital cely riadok)
			else
			{
				RollbackToken();//vrat spat whitespaces
				return;
			}
		}
		else
		if (tokenType == LEXICAL_TOKEN_EOL)
			ReadToken(&token);//EOL
		else
			break;
	}
}

DWORD CLexicalAnalyzer::ReadToken_NoWhiteSpaces(const char **str)
{
	DWORD ret = ReadToken(str);
	if (ret == LEXICAL_TOKEN_WHITESPACE)
		ret = ReadToken(str);
	return ret;
}


DWORD CLexicalAnalyzer::ReadToken(const char **str)
{
	*str = (const char*)token;
	DWORD i;
	token[0] = 0;
	char c;

	inputIndexOld = inputIndex;//kvoli RollbackToken (aby sme sa vedeli vratit spat)
	lineNumOld = lineNum;
	charPosOld = charPos;
	charPosInLineOld = charPosInLine;
	charReadedOld = charReaded;

	c = ReadChar();

	if ( (c==0) || (c==13) )
	{
		tokenType = LEXICAL_TOKEN_EOL;
		return LEXICAL_TOKEN_EOL;
	}

	//------------------------------------------------------------
	//whitespaces?
	if ( (c==' ') || (c=='\t') )
	{
		while (true)
		{
			c = LookAheadChar();
			if ( (c==' ') || (c=='\t') )
				ReadChar();
			else
				break;
		}
		tokenType = LEXICAL_TOKEN_WHITESPACE;
		return LEXICAL_TOKEN_WHITESPACE;
	}

	//------------------------------------------------------------
	//cislo?
	if ( (c>='0') && (c<='9') )
	{
		bool dot = false;
		token[0] = c; i = 1;
		while (true)
		{
			c = LookAheadChar();

			if (dot && c == '.')
				break;//jedna desatinna bodka hadam staci,nie?

			if (c == '.')
				dot = true;
			if ( ((c >= '0') && (c <= '9')) || (c == '.') )
			{
				if (i == LEXICAL_MAX_TOKEN_LENGTH)//jeden znak na ukoncenie
					throw ("Token prekročil max. povolenú dĺku!");
				token[i++] = c; ReadChar();
			}
			else
				break;
		}
		token[i] = 0;
		tokenType = LEXICAL_TOKEN_NUMBER;
		return LEXICAL_TOKEN_NUMBER;
	}

	//------------------------------------------------------------
	//identifikator?
	if ( ( (c>='A') && (c<='Z') ) || ( (c>='a') && (c<='z') ) )//|| (c=='_') )
	{
/*		bool nonSliderChar = false;
		if (c!='_')
			nonSliderChar = true;*/

		token[0] = c; i = 1;
		while (true)
		{
			c = LookAheadChar();
			if ( ( (c>='A') && (c<='Z') ) ||
				  ( (c>='a') && (c<='z') ) ||
				  ( (c>='0') && (c<='9') ) )/*||
				    (c=='_') )*/
			{
/*				if (c!='_')
					nonSliderChar = true;*/
				if (i == LEXICAL_MAX_TOKEN_LENGTH)//jeden znak na ukoncenie
					throw ("Token prekročil max. povolenú dĺku!");
				token[i++] = c; ReadChar();
			}
			else
				break;
		}
		token[i] = 0;

		if (IsKeyword(token))
		{
			tokenType = LEXICAL_TOKEN_KEYWORD;
			return LEXICAL_TOKEN_KEYWORD;
		}
		tokenType = LEXICAL_TOKEN_IDENTIFIER;
		return LEXICAL_TOKEN_IDENTIFIER;
	}

	token[0] = c;//najnovsie nacitany znak
	token[1] = 0;

	switch (token[0])
	{
	case '!':
		tokenType = LEXICAL_TOKEN_EXCLAMATION;
		break;
	case '+':
		tokenType = LEXICAL_TOKEN_PLUS;
		break;
	case '-':
		tokenType = LEXICAL_TOKEN_MINUS;
		break;
	case '*':
		tokenType = LEXICAL_TOKEN_MULTIPLY;
		break;
	case '/':
		if (LookAheadChar() == '/')
		{
			//komentar
			token[1] = '/'; token[2] = 0;
			GoToNextLine();
			tokenType = LEXICAL_TOKEN_EOL;
		}
		else
			tokenType = LEXICAL_TOKEN_DIVIDE;
		break;
	case '(':
		tokenType = LEXICAL_TOKEN_LEFTPARENTHESIS;
		break;
	case ')':
		tokenType = LEXICAL_TOKEN_RIGHTPARENTHESIS;
		break;
	case '"':
	case '\'':
	{
		char endingChar = token[0];
		//nacitaj string (hladaj koncovy znak)
		char c;
		i = 1;
		while (true)
		{
			c = ReadChar();
			if (c == 0)
#ifdef _DEBUG
				throw "CLexicalAnalyzer::ReadToken : Neočakávaný koniec vstupu - nedokončený reazec";
#else
				throw "Neočakávaný koniec vstupu - nedokončený reazec";
#endif
			if (c == 13)
#ifdef _DEBUG
				throw "CLexicalAnalyzer::ReadToken : Neočakávaný koniec riadku - nedokončený reazec";
#else
				throw "Neočakávaný koniec riadku - nedokončený reazec";
#endif
			if (i == LEXICAL_MAX_TOKEN_LENGTH)//jeden znak na ukoncenie
				throw ("Token prekročil max. povolenú dĺku!");

			//dva pociatocne znaky za sebou znamenaju zapisanie jedneho do stringu
			if (c == endingChar)
			{
				if (input[inputIndex] == endingChar)
				{
					//dva rovnake pociatocne znaky, chceme len jeden
					c = ReadChar();
				}
				else
				{
					//inak koniec retazca
					token[i++] = c;//zapis koniec stringu
					token[i] = 0;
					tokenType = LEXICAL_TOKEN_STRING;
					//prepis uvodzovky na apostrofy (SQL chce len apostrofy)
					token[0]='\'';
					token[i-1]='\'';
					break;
				}
			}

			if (c=='\'')
			{
				//zapis dva znaky do vystupu
				token[i++] = c;
				if (i == LEXICAL_MAX_TOKEN_LENGTH)
					throw ("Token prekročil max. povolenú dĺku!");
				token[i++] = c;
			}
			else
				token[i++] = c;//zapis znak
		}
		break;
	}
	case '<':
		tokenType = LEXICAL_TOKEN_LESS;
		break;
	case '>':
		tokenType = LEXICAL_TOKEN_GREATER;
		break;
	case '=':
		tokenType = LEXICAL_TOKEN_EQUAL;
		break;
	case '.':
		tokenType = LEXICAL_TOKEN_DOT;
		break;
	case ',':
		tokenType = LEXICAL_TOKEN_COMMA;
		break;
	case ';':
		tokenType = LEXICAL_TOKEN_SEMICOLON;
		break;
	case '_':
		tokenType = LEXICAL_TOKEN_UNDERSCORE;
		break;
	case 13:
		tokenType = LEXICAL_TOKEN_EOL;
		break;
	//neznamy
	default:
		tokenType = LEXICAL_TOKEN_UNDEFINED;
		break;
	}
	return tokenType;
}