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

#include "bool.h"
#include "mem.h"
#include "Throwback.h"

#include "script.h"

static const char mem_err[] = "Out of memory\n";

#define YYDEBUG 0
#define YYERROR_VERBOSE

#define YYPARSE_PARAM     ptree
#define YYPARSE_PARAMTYPE gr_tree*
#define YYLEX_PARAM       ptree
#define YYLEX_PARAMTYPE   gr_tree*

static void yyprint(const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	vfprintf(stderr, pformat, arg);
	va_end(arg);
}
%}

%pure_parser

%union{
gr_node*	pvar;
int		value;
char*		pstring;
}

%token <value> LEX_INT
%token <pstring> LEX_ID
%token <pstring> LEX_STRING

%type <pvar> input
%type <pvar> attr
%type <pvar> var
%type <pvar> params
%type <pvar> param
%type <pvar> id
%type <pvar> idlist

%%
input : var
		{
			ptree->m_ptop = $1;
		}
;
var: LEX_ID LEX_ID
		{
			$$ = var_new_var( @1.first_line
					, @1.first_column
					, $1
					, $2
					, 0);
			if (!$$) YYABORT;
		}
   | LEX_ID LEX_ID '(' params ')'
		{
			$$ = var_new_var(@4.first_line
					, @4.first_column
					, $1
					, $2
					, $4);
			if (!$$) YYABORT;
		}
;
id: LEX_ID
		{
			$$ = var_new_attr(@0.first_line
					, @0.first_column
					, $1
					, 0);
			if (!$$) YYABORT;
		}
;
attr: id
    | LEX_ID '(' params ')'
		{
			$$ = var_new_attr(@3.first_line
					, @3.first_column
					, $1
					, $3);
			if (!$$) YYABORT;
		}
    | LEX_ID '[' idlist ']'
		{
			$$ = var_new_list(@3.first_line
					, @3.first_column
					, $1
					, $3);
			if (!$$) YYABORT;
		}
;
idlist: id
      | id ',' idlist
		{
			$$ = $1;
			$1->pnext = $3;
		}
;
params: param
      | param ',' params
		{
			$$ = $1;
			$1->pnext = $3;
		}
;
param: var
     | attr
     | LEX_ID '=' LEX_INT
		{
			$$ = var_new_int_prop(@2.first_line
					, @2.first_column
					, $1
					, $3);
			if (!$$) YYABORT;
		}
     | LEX_ID '=' attr
		{
			$$ = var_new_attr_prop(@2.first_line
					, @2.first_column
					, $1
					, $3);
			if (!$$) YYABORT;
		}
     | LEX_ID '=' LEX_STRING
		{
			$$ = var_new_str_prop(@2.first_line
					, @2.first_column
					, $1
					, $3);
			if (!$$) YYABORT;
		}
;
%%

static int yy_get(gr_tree* ptree)
{
	ptree->prev_char = ptree->cur_char;

	if (ptree->prev_char == '\n')
	{
		ptree->cur_line++;
		ptree->prev_col = ptree->cur_col;
		ptree->cur_col = 1;
	}
	else if (ptree->prev_char == '\t')
		ptree->cur_col += 8;
	else
		ptree->cur_col++;

	ptree->cur_char = fgetc(ptree->m_file);

	return ptree->cur_char;
}

static void yy_unget(gr_tree* ptree)
{
	ungetc(ptree->cur_char, ptree->m_file);

	ptree->cur_char = ptree->prev_char;

	if (ptree->prev_char == '\n')
	{
		ptree->cur_line--;
		ptree->cur_col = ptree->prev_col;
	}
	else if (ptree->prev_char == '\t')
		ptree->cur_col -= 8;
	else
		ptree->cur_col--;
}

static bool yy_addtostring(gr_tree* ptree, int i, int c)
{
	// If buffer is full, make it bigger.
	if (i == ptree->length)
	{
		char* pstr;
		if (ptree->length)
		{
			ptree->symbuf[ptree->length] = 0;
			ptree->length *= 2;
		}
		else
			ptree->length = 40;
		pstr = (char*) mem_alloc(ptree->length + 1);
		if (!pstr) return false;
		if (ptree->symbuf)
		{
			strcpy(pstr, ptree->symbuf);
			mem_free(ptree->symbuf);
		}
		ptree->symbuf = pstr;
	}

	// Add this character to the buffer.
	ptree->symbuf[i] = c;

	return true;
}

int yylex(YYSTYPE* plval, YYLTYPE* pos, gr_tree* ptree)
{
	int c = ' ';
	int ret;

	// Ignore whitespace, get first nonwhite character.
	while ((c != EOF) && (c <= ' '))
		c = yy_get(ptree);

	pos->first_line = pos->last_line = ptree->cur_line;
	pos->first_column = pos->last_column = ptree->cur_col;

	if (c == EOF)
	{
#if YYDEBUG != 0
		yyprint("Line %4d, Column %3d: EOF:\n", pos->first_line, pos->first_column);
#endif
		return 0;
	}

	// Char starts a number => parse the number.
	if (isdigit(c))
	{
		int i = 0;

		do
		{
			// Add this character to the buffer.
			if (!yy_addtostring(ptree, i, c))
				yyerror(pos->first_line, pos->first_column, mem_err);
			i++;
			// Get another character.
			c = yy_get(ptree);
		}
		while (c != EOF && isdigit(c));

		yy_unget(ptree);
		ptree->symbuf[i] = '\0';

		plval->value = atoi(ptree->symbuf);

		ret = LEX_INT;

#if YYDEBUG != 0
		yyprint("Line %4d, Column %3d: LEX_INT: %d\n", pos->first_line, pos->first_column, plval->value);
#endif

		pos->last_line = ptree->cur_line;
		pos->last_column = ptree->cur_col;

		return ret;
	}

	// Char starts an identifier => read the name.
	if (isalpha(c) || (c == '_'))
	{
		int i = 0;

		do
		{
			// Add this character to the buffer.
			if (!yy_addtostring(ptree, i, c))
				yyerror(pos->first_line, pos->first_column, mem_err);
			i++;
			// Get another character.
			c = yy_get(ptree);
		}
		while (c != EOF && (isalnum(c) || (c == '_')));

		yy_unget(ptree);
		ptree->symbuf[i] = '\0';

		plval->pstring = mem_allocstring(ptree->symbuf);

		if (!plval->pstring)
		{
#if YYDEBUG != 0
			yyerror(pos->first_line, pos->first_column, mem_err);
#endif
			return 0; /* error */
		}

		ret = LEX_ID;

#if YYDEBUG != 0
		yyprint("Line %4d, Column %3d: LEX_ID: %s\n", pos->first_line, pos->first_column, plval->pstring);
#endif

		pos->last_line = ptree->cur_line;
		pos->last_column = ptree->cur_col;

		return ret;
	}

	// Char starts an identifier => read the name.
	if (c == '"')
	{
		int i = 0;

		while (((c = yy_get(ptree)) != EOF) && (c != '"'))
		{
			// Add this character to the buffer.
			if (!yy_addtostring(ptree, i, c))
				yyerror(pos->first_line, pos->first_column, mem_err);
			i++;
		}

		ptree->symbuf[i] = '\0';

		plval->pstring = mem_allocstring(ptree->symbuf);

		if (!plval->pstring)
		{
#if YYDEBUG != 0
			yyerror(pos->first_line, pos->first_column, mem_err);
#endif
			return 0; /* error */
		}

		ret = LEX_STRING;

#if YYDEBUG != 0
		yyprint("Line %4d, Column %3d: LEX_STRING: \"%s\"\n", pos->first_line, pos->first_column, plval->pstring);
#endif

		pos->last_line = ptree->cur_line;
		pos->last_column = ptree->cur_col;

		return ret;
	}

	// Any other character is a token by itself.
#if YYDEBUG != 0
	yyprint("Line %4d, Column %3d: CHAR: %c\n",pos->first_line, pos->first_column, c);
#endif

	return c;
}

void yyerror(int line, int column, const char* pformat, ...)
{
	va_list arg;

	va_start(arg, pformat);
	Throwback_VError(line, column, pformat, arg);
	va_end(arg);
}

static void yylog(const gr_node* pfirst, int level)
{
	int i;
	const gr_node* pvar = pfirst;

	while(pvar)
	{
		yyprint("Line: %4d Col: %3d ", pvar->line, pvar->column);
		for (i = 0; i < level; i++)
			yyprint("\t");
		switch(pvar->type)
		{
			case gr_type_var:
			{
				yyprint("var: %s %s\n", pvar->pname, pvar->pparams);
			}
			break;
			case gr_type_attr:
			{
				yyprint("attr: %s\n", pvar->pname);
			}
			break;
			case gr_type_int_prop:
			{
				yyprint("int prop: %s = %d\n", pvar->pname, pvar->ivalue);
			}
			break;
			case gr_type_attr_prop:
			{
				yyprint("attr prop: %s = %s\n", pvar->pname, pvar->pstring);
			}
			break;
			case gr_type_str_prop:
			{
				yyprint("id attr: %s = \"%s\"\n", pvar->pname, pvar->pstring);
			}
			break;
		}
			if (pvar->pparams)
				yylog(pvar->pparams, level + 1);
		pvar = pvar->pnext;
	}
}

gr_node* var_new_var(int line, int column, char* ptype, char* pname, gr_node* pparams)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_var;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = ptype;
	pvar->ivalue = 0;
	pvar->pstring = pname;
	pvar->pparams = pparams;

	return pvar;
}

gr_node* var_new_attr(int line, int column, char* pname, gr_node* pparams)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_attr;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = pname;
	pvar->ivalue = 0;
	pvar->pstring = NULL;
	pvar->pparams = pparams;

	return pvar;
}

gr_node* var_new_list(int line, int column, char* pname, gr_node* pparams)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_list;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = pname;
	pvar->ivalue = 0;
	pvar->pstring = NULL;
	pvar->pparams = pparams;

	return pvar;
}

gr_node* var_new_int_prop(int line, int column, char* pname, int value)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_int_prop;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = pname;
	pvar->ivalue = value;
	pvar->pstring = NULL;
	pvar->pparams = NULL;

	return pvar;
}

gr_node* var_new_attr_prop(int line, int column, char* pname, gr_node* pattr)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_attr_prop;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = pname;
	pvar->ivalue = 0;
	pvar->pstring = NULL;
	pvar->pparams = pattr;

	return pvar;
}

gr_node* var_new_str_prop(int line, int column, char* pname, char* pstring)
{
	gr_node* pvar = mem_alloc(sizeof(gr_node));

	if (!pvar)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	pvar->pnext = NULL;
	pvar->type = gr_type_str_prop;
	pvar->line = line;
	pvar->column  = column;
	pvar->pname = pname;
	pvar->ivalue = 0;
	pvar->pstring = pstring;
	pvar->pparams = NULL;

	return pvar;
}

void var_delete(gr_node** pvar)
{
	if (!*pvar) return;

	mem_free((*pvar)->pname);
	while ((*pvar)->pparams) var_delete(&((*pvar)->pparams));
	mem_free((*pvar)->pstring);

	*pvar = (*pvar)->pnext;
}

gr_tree* grtree_parse(const char* filename)
{
	gr_tree*	ptree = mem_alloc(sizeof(gr_tree));
	int		bErr;

	if (!ptree)
	{
		yyerror(0, 0, "Out of memory\n");
		return NULL;
	}

	ptree->m_filename = filename;
	ptree->m_file = fopen(filename, "rb");

	if (ptree->m_file)
	{
		ptree->m_ptop = 0;
		ptree->symbuf = 0;
		ptree->length = 0;
		ptree->cur_line = 1;
		ptree->cur_col = 0;
		ptree->cur_char = 0;
		ptree->prev_col = 0;
		ptree->prev_char = 0;

		Throwback_Start(filename);
		bErr = yyparse(ptree);
		Throwback_Stop();

		fclose(ptree->m_file);

		if (bErr)
		{
			grtree_delete(ptree);
			ptree = NULL;
		}
	}
	else
	{
		yyerror(0, 0, "Could not open file\n");
		free(ptree);
		return NULL;
	}

	return ptree;
}

void grtree_delete(gr_tree* ptree)
{
	while (ptree->m_ptop) var_delete(&ptree->m_ptop);
	mem_free(ptree->symbuf);
}
