/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


//**************************************************************************
//**
//** token.c
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "token.h"
#include "inout.h"

// MACROS ------------------------------------------------------------------

// TYPES -------------------------------------------------------------------

typedef enum
{
	CHR_EOF,
	CHR_LETTER,
	CHR_NUMBER,
	CHR_QUOTE,
	CHR_SPECIAL
} chr_t;

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void ProcessLetterToken(void);
static void ProcessNumberToken(void);
static void ProcessQuoteToken(void);
static void ProcessSpecialToken(void);
static qboolean CheckForKeyword(void);
static void NextChr(void);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

tokenType_t tk_Token;
int tk_Line;
int tk_IntNumber;
float tk_FloatNumber;
char *tk_String;
char tk_SourceName[MAX_FILE_NAME_LENGTH];

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static char Chr;
static char *FileStart;
static char *FilePtr;
static char *FileEnd;
static qboolean SourceOpen;
static char ASCIIToChrCode[256];
static char TokenStringBuffer[MAX_QUOTED_LENGTH];
static qboolean IncLineNumber;
static char TempBuffer[2048];

static struct
{
	char *name;
	tokenType_t token;
} Keywords[] =
{
	"model",			TK_MODEL,
	"mesh",				TK_MESH,
	"vertices",			TK_VERTICES,
	"edges",			TK_EDGES,
	"position",			TK_POSITION,
	"polygons",			TK_POLYGONS,
	"nodes",			TK_NODES,
	"rotation",			TK_ROTATION,
	"scaling",			TK_SCALING,
	"translation",		TK_TRANSLATION,
	"vertex",			TK_VERTEX,
	"HRCH",				TK_HRCH,
	"Softimage",		TK_SOFTIMAGE,
	"material",			TK_MATERIAL,
	"spline",			TK_SPLINE,

	"Named",			TK_C_NAMED,
	"object",			TK_OBJECT,
	"Tri",				TK_C_TRI,
	"Vertices",			TK_C_VERTICES,
	"Faces",			TK_C_FACES,
	"Vertex",			TK_C_VERTEX,
	"list",				TK_LIST,
	"Face",				TK_C_FACE,

	"Hexen",			TK_C_HEXEN,
	"Triangles",		TK_C_TRIANGLES,
	"Version",			TK_C_VERSION,
	"faces",			TK_FACES,
	"face",				TK_FACE,
	"origin",			TK_ORIGIN,

	"DK_clusters",		TK_CLUSTERS,
	"DK_cluster_ncvs",	TK_NUM_CLUSTER_VERTICES,
	"name",				TK_NAME,
	"DK_cluster_name",	TK_CLUSTER_NAME,
	"DK_cluster_state",	TK_CLUSTER_STATE,

	"actor_data",		TK_ACTOR_DATA,
	"uvTexture",		TK_UVTEXTURE,

	NULL,				-1
};

static char *TokenNames[] =
{
	"<nothing>",
	"<unknown_char>",
	"<EOF>",
	"<identifier>",
	"<string>",
	"<int_number>",
	"<float_number>",
	"(",
	")",
	"{",
	"}",
	"[",
	"]",
	":",
	"mesh",
	"model",
	"nodes",
	"rotation",
	"scaling",
	"translation",
	"polygons",
	"position",
	"vertex",
	"vertices",
	"HRCH",
	"Softimage"
};

// CODE --------------------------------------------------------------------

//==========================================================================
//
// TK_Init
//
//==========================================================================

void TK_Init(void)
{
	int i;

	for(i = 0; i < 256; i++)
	{
		ASCIIToChrCode[i] = CHR_SPECIAL;
	}
	for(i = '0'; i <= '9'; i++)
	{
		ASCIIToChrCode[i] = CHR_NUMBER;
	}
	for(i = 'A'; i <= 'Z'; i++)
	{
		ASCIIToChrCode[i] = CHR_LETTER;
	}
	for(i = 'a'; i <= 'z'; i++)
	{
		ASCIIToChrCode[i] = CHR_LETTER;
	}
	ASCIIToChrCode[ASCII_QUOTE] = CHR_QUOTE;
	ASCIIToChrCode[ASCII_UNDERSCORE] = CHR_LETTER;
	ASCIIToChrCode[EOF_CHARACTER] = CHR_EOF;
	tk_String = TokenStringBuffer;
	IncLineNumber = FALSE;
	SourceOpen = FALSE;
}

//==========================================================================
//
// TK_OpenSource
//
//==========================================================================

void TK_OpenSource(char *fileName)
{
	int size;

	TK_CloseSource();
	size = LoadFile(fileName, (void **)&FileStart);
	strcpy(tk_SourceName, fileName);
	SourceOpen = TRUE;
	FileEnd = FileStart+size;
	FilePtr = FileStart;
	tk_Line = 1;
	tk_Token = TK_NONE;
	NextChr();
}

//==========================================================================
//
// TK_CloseSource
//
//==========================================================================

void TK_CloseSource(void)
{
	if(SourceOpen)
	{
		free(FileStart);
		SourceOpen = FALSE;
	}
}

//==========================================================================
//
// TK_Fetch
//
//==========================================================================

tokenType_t TK_Fetch(void)
{
	while(Chr == ASCII_SPACE)
	{
		NextChr();
	}
	if(Chr == '-')
	{
		ProcessNumberToken();
	}
	else switch(ASCIIToChrCode[(byte)Chr])
	{
		case CHR_EOF:
			tk_Token = TK_EOF;
			break;
		case CHR_LETTER:
			ProcessLetterToken();
			break;
		case CHR_NUMBER:
			ProcessNumberToken();
			break;
		case CHR_QUOTE:
			ProcessQuoteToken();
			break;
		default:
			ProcessSpecialToken();
			break;
	}
	return tk_Token;
}

//==========================================================================
//
// TK_Require
//
//==========================================================================

void TK_Require(tokenType_t tokType)
{
	if(tokType == TK_FLOATNUMBER && tk_Token == TK_INTNUMBER)
	{
		tk_FloatNumber = (float)tk_IntNumber;
		tk_Token = TK_FLOATNUMBER;
		return;
	}
	if(tk_Token != tokType)
	{
		Error("File '%s', line %d:\nExpected '%s', found '%s'.\n",
			tk_SourceName, tk_Line, TokenNames[tokType],
			TokenNames[tk_Token]);
	}
}

void TK_FetchRequire(tokenType_t tokType)
{
	TK_Fetch();
	TK_Require(tokType);
}

tokenType_t TK_RequireFetch(tokenType_t tokType)
{
	TK_Require(tokType);
	return TK_Fetch();
}

tokenType_t TK_FetchRequireFetch(tokenType_t tokType)
{
	TK_Fetch();
	TK_Require(tokType);
	return TK_Fetch();
}

tokenType_t TK_Beyond(tokenType_t tokType)
{
	while(tk_Token != tokType)
	{
		if(TK_Fetch() == TK_EOF)
		{
			Error("File '%s':\nCould not find token '%s'.\n",		// FIXME: TokenNames table not big enuff 
				tk_SourceName, TokenNames[tokType]);
		}
	}
	return TK_Fetch();
}

void TK_BeyondRequire(tokenType_t bTok, tokenType_t rTok)
{
	TK_Beyond(bTok);
	TK_Require(rTok);
}

tokenType_t TK_Search(tokenType_t tokType)
{
	while(tk_Token != tokType)
	{
		if(TK_Fetch() == TK_EOF)
		{
			return TK_EOF;
		}
	}
	return TK_Fetch();
}

tokenType_t TK_Get(tokenType_t tokType)
{
	while(tk_Token != tokType)
	{
		if(TK_Fetch() == TK_EOF)
		{
			Error("File '%s':\nCould not find token '%s'.\n",
				tk_SourceName, TokenNames[tokType]);
		}
	}
	return tk_Token;
}

//==========================================================================
//
// ProcessLetterToken
//
//==========================================================================

static void ProcessLetterToken(void)
{
	int i;
	char *text;

	i = 0;
	text = TokenStringBuffer;
	while(ASCIIToChrCode[(byte)Chr] == CHR_LETTER
		|| ASCIIToChrCode[(byte)Chr] == CHR_NUMBER)
	{
		if(++i == MAX_IDENTIFIER_LENGTH)
		{
			Error("File '%s', line %d:\nIdentifier too long.\n",
				tk_SourceName, tk_Line);
		}
		*text++ = Chr;
		NextChr();
	}
	*text = 0;
	if(CheckForKeyword() == FALSE)
	{
		tk_Token = TK_IDENTIFIER;
	}
}

//==========================================================================
//
// CheckForKeyword
//
//==========================================================================

static qboolean CheckForKeyword(void)
{
	int i;

	for(i = 0; Keywords[i].name != NULL; i++)
	{
		if(strcmp(tk_String, Keywords[i].name) == 0)
		{
			tk_Token = Keywords[i].token;
			return TRUE;
		}
	}
	return FALSE;
}

//==========================================================================
//
// ProcessNumberToken
//
//==========================================================================

static void ProcessNumberToken(void)
{
	char *buffer;

	buffer = TempBuffer;
	*buffer++ = Chr;
	NextChr();
	while(ASCIIToChrCode[(byte)Chr] == CHR_NUMBER)
	{
		*buffer++ = Chr;
		NextChr();
	}
	if(Chr == '.')
	{ // Float
		*buffer++ = Chr;
		NextChr(); // Skip period
		while(ASCIIToChrCode[(byte)Chr] == CHR_NUMBER)
		{
			*buffer++ = Chr;
			NextChr();
		}
		*buffer = 0;
		tk_FloatNumber = (float)atof(TempBuffer);
		tk_Token = TK_FLOATNUMBER;
		return;
	}

	// Integer
	*buffer = 0;
	tk_IntNumber = atoi(TempBuffer);
	tk_Token = TK_INTNUMBER;
}

//==========================================================================
//
// ProcessQuoteToken
//
//==========================================================================

static void ProcessQuoteToken(void)
{
	int i;
	char *text;

	i = 0;
	text = TokenStringBuffer;
	NextChr();
	while(Chr != ASCII_QUOTE)
	{
		if(Chr == EOF_CHARACTER)
		{
			Error("File '%s', line %d:\n<EOF> inside string.\n",
				tk_SourceName, tk_Line);
		}
		if(++i > MAX_QUOTED_LENGTH-1)
		{
			Error("File '%s', line %d:\nString literal too long.\n",
				tk_SourceName, tk_Line);
		}
		*text++ = Chr;
		NextChr();
	}
	*text = 0;
	NextChr();
	tk_Token = TK_STRING;
}

//==========================================================================
//
// ProcessSpecialToken
//
//==========================================================================

static void ProcessSpecialToken(void)
{
	char c;

	c = Chr;
	NextChr();
	switch(c)
	{
		case '(':
			tk_Token = TK_LPAREN;
			break;
		case ')':
			tk_Token = TK_RPAREN;
			break;
		case '{':
			tk_Token = TK_LBRACE;
			break;
		case '}':
			tk_Token = TK_RBRACE;
			break;
		case '[':
			tk_Token = TK_LBRACKET;
			break;
		case ']':
			tk_Token = TK_RBRACKET;
			break;
		case ':':
			tk_Token = TK_COLON;
			break;
		default:
			tk_Token = TK_UNKNOWNCHAR;
			break;
	}
}

//==========================================================================
//
// NextChr
//
//==========================================================================

static void NextChr(void)
{
	if(FilePtr >= FileEnd)
	{
		Chr = EOF_CHARACTER;
		return;
	}
	if(IncLineNumber == TRUE)
	{
		tk_Line++;
		IncLineNumber = FALSE;
	}
	Chr = *FilePtr++;
	if(Chr < ASCII_SPACE)
	{
		if(Chr == '\n')
		{
			IncLineNumber = TRUE;
		}
		Chr = ASCII_SPACE;
	}
}
