/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "script_squirrel/squirrel_analyzer.h"
		#include "ascii/parser.h"
		#include "filesystem/filesystem.h"
		#include "platform.h"

		using namespace GS;
		using namespace GS::SquirrelAnalyzer;
		using namespace GS::AsciiParser;


//------------------------------------------------------------------------------
/* static bool	SymbolFindByNameFunctor(Symbol *s, const char *n) { return s->name == n; } */
static bool	SourceFindByNameFunctor(Source *s, const char *n) { return s->name == n; }
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
struct	ParserContext
{
	const char *s, *e, *o;
	SharedList <Symbol *> *symbols;
	Symbol::Type type;

	Offset	CurrentOffset() const
	{
		Offset offset(1, 0);
		for	(const char *p = o; p < e; ++offset.line)
		{
			const char *eol = RunToEOL(p, e);

			if	(eol > s)
			{
				offset.column = s - p;
				break;
			}
			if	(eol >= e)
				return Offset(1, 0);

			p = SkipEOL(eol, e);
		}
		return offset;
	}

	ParserContext(const char *_s, const char *_e, const char *_o, SharedList <Symbol *> *_symbols, Symbol::Type _type) : s(_s), e(_e), o(_o), symbols(_symbols), type(_type) {}
};
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				ParseContext(ParserContext &);
bool				ParseMemberVariable(ParserContext &ctx)
{
	AutoPtr <Variable> _var(new Variable);
	_var->offset = ctx.CurrentOffset();
	_var->var_type = Variable::VarMember;

	const char *e = SkipEntry(ctx.s, ctx.e);
	_var->name.Set(ctx.s, e);

	ctx.s = NextEntry(ctx.s, ctx.e) + 1;
	ctx.symbols->Add(_var.Detach());
	return true;
}
bool				ParseGlobalVariable(ParserContext &ctx)
{
	AutoPtr <Variable> _var(new Variable);
	_var->offset = ctx.CurrentOffset();
	_var->var_type = Variable::VarGlobal;

	const char *e = SkipEntry(ctx.s, ctx.e);
	_var->name.Set(ctx.s, e);

	ctx.s = NextEntry(ctx.s, ctx.e) + 2;
	ctx.symbols->Add(_var.Detach());
	return true;
}
bool				ParseLocalVariable(ParserContext &ctx)
{
	AutoPtr <Variable> _var(new Variable);
	_var->offset = ctx.CurrentOffset();
	_var->var_type = Variable::VarLocal;

	const char *s = NextEntry(ctx.s, ctx.e);
	_var->name.Set(s, SkipEntry(s, ctx.e));
	ctx.symbols->Add(_var.Detach());

	// parse subsequent declarations.
	s = NextEntry(s, ctx.e);

	forever
	{
/*
		// TODO a naive test won't do as there is no mandatory end-of-statement in Squirrel
		if	(s[0] == '=')
		{
			// check if there are more variables declared by this statement
		}
*/
		if (s[0] == ',')
		{
			s = NextEntry(s + 1, ctx.e);
			ctx.s = s;

			_var = new Variable;
			_var->offset = ctx.CurrentOffset();
			_var->var_type = Variable::VarLocal;

			_var->name.Set(s, SkipEntry(s, ctx.e));
			ctx.symbols->Add(_var.Detach());

			s = NextEntry(s, ctx.e);
		}
		else
			break;
	}

	ctx.s = s;
	return true;
}
bool				ParseFunction(ParserContext &ctx)
{
	AutoPtr <Function> _function(new Function);
	_function->offset = ctx.CurrentOffset();

	const char *s = NextEntry(ctx.s, ctx.e);
	_function->name.Set(s, SkipEntry(s, ctx.e));

	s = NextEntry(s, ctx.e);

	// Parse prototype.
	if	(s[0] != '(')
		return false;

	const char *eoproto = RunToEOG(s, ctx.e, '(', ')');
	if	(eoproto == NULL)
		return false;

	_function->prototype.Set(s + 1, eoproto);
	s = NextEntry(eoproto + 1, ctx.e);

	// Parse function content.
	if	(s[0] != '{')
		return false;

	const char *eofunc = RunToEOG(s, ctx.e, '{', '}');
	if	(eofunc == NULL)
		eofunc = ctx.e;
	ParserContext func_ctx(s + 1, eofunc, ctx.o, &_function->symbols, _function->type);

	ParseContext(func_ctx);

	ctx.s = eofunc + 1;
	ctx.symbols->Add(_function.Detach());
	return true;
}
bool				ParseClass(ParserContext &ctx)
{
	AutoPtr <Class> _class(new Class);
	_class->offset = ctx.CurrentOffset();

	ctx.s = NextEntry(ctx.s, ctx.e);
	_class->name.Set(ctx.s, SkipEntry(ctx.s, ctx.e));

	const char *s = NextEntry(ctx.s, ctx.e);

	if	(!String::strccmp("extends", s))
	{
		s = NextEntry(s, ctx.e);
		_class->extends.Set(s, SkipEntry(s, ctx.e));

		ctx.s = SkipEntry(s, ctx.e);

		s = NextEntry(s, ctx.e);
		if	(s == ctx.e)
			return false;
	}

	// Parse class content.
	if	(s[0] != '{')
		return false;

	const char *eoclass = RunToEOG(s, ctx.e, '{', '}');
	if	(eoclass == NULL)
		eoclass = ctx.e;

	ParserContext class_ctx(s + 1, eoclass, ctx.o, &_class->symbols, _class->type);
	ParseContext(class_ctx);

	ctx.s = eoclass + 1;
	ctx.symbols->Add(_class.Detach());
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				ParseContext(ParserContext &ctx)
{
	for (bool r = true; r && (ctx.s < ctx.e); )
	{
		if	(!String::strccmp("class", ctx.s))
			r = ParseClass(ctx);
		else	if	(!String::strccmp("function", ctx.s))
			r = ParseFunction(ctx);
		else	if	(!String::strccmp("local", ctx.s))
			r = ParseLocalVariable(ctx);
		else
		{
			const char *e = SkipEntry(ctx.s, ctx.e);
			String tmp(ctx.s, e);

			const char *s = NextEntry(ctx.s, ctx.e);
			if	(ctx.s == s)
				++ctx.s;	// skip
			else
			{
				if	((s[0] == '<') && (s[1] == '-'))
					r = ParseGlobalVariable(ctx);
				else	if	((ctx.type == Symbol::TypeClass) && (s[0] == '='))
					r = ParseMemberVariable(ctx);
				else
					ctx.s = s;
			}
		}
	}
}
Source				*ParseSource(const char *s, const char *e)
{
	AutoPtr <Source> source(new Source);

	ParserContext ctx(s, e, s, &source->symbols, Symbol::TypeNone);
	ParseContext(ctx);

	return source.Detach();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Source				*SquirrelAnalyzer::Analyze(const char *nut, Program &program, bool replace)
{
	Source *old_source = ListFindEx(program.sources, SourceFindByNameFunctor, nut);
	if	(old_source && !replace)
		return NULL;

	String source;
	{
		Array <char> buffer;
		if	(!Platform::Get().io->FileLoad(nut, buffer))
			return NULL;
		source.Set(buffer.c_ptr(), buffer.c_ptr() + buffer.GetCount());
	}

	Source *new_source = ParseSource(source.c_str(), source.c_str() + source.Len());
	if	(!new_source)
		return NULL;

	new_source->name = nut;

	program.sources.Remove(old_source);
	program.sources.Add(new_source);
	return new_source;
}
//------------------------------------------------------------------------------
