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


		#include <cstring>
		#include "metafile/nml.h"
		#include "ascii/parser.h"
		#include "ascii/ascii_encoder.h"
		#include "filesystem/io_handle.h"
		#include "filesystem/filesystem.h"
		#include "platform.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::NML;
		using namespace	GS::AsciiParser;


//------------------------------------------------------------------------------
bool				Parser::IsMetafile(const char *name)
{
	AutoPtr <IO::Handle> h(Platform::Get().io->Open(name));
	if	(h.IsNull())
		return false;

	char header[9];
	h->Read(header, 9);
	if	(Memory::Compare(header, "<Version=", 9) && Memory::Compare(header, "<NML=", 5))
		return false;

	return true;
}
const char			*Parser::ParseTagPreprocessorDirective(Tag &tag, const char *s, const char *e)
{
	s++;

	// Catch preprocessor directive.
	if	(!strncmp(s, "include", 7))
	{
		// Ensure base coherency.
		if	(tag.GetValue().GetType() != Variant::VariantNone)
			__ERR__(__LOG_E__ << "Incoherent type in tag '" << tag.name << "' declaration.\n", NULL)

			// Parse directive.
			s += SkipSpace(s + 7, e) - s;
		if	(s[0] != '(')
			__ERR__(__LOG_E__ << "Mangled #include directive.\n", NULL);

		// Fetch arguments.
		String include_path;

		forever
		{
			if	(s[0] == ')')
				break;
			s += SkipSpace(s + 1, e) - s;

			// Path.
			if	(s[0] == '"')
			{
				s++;
				uint len = (uint)(RunToEOS(s, e) - s);
				include_path.Set(s, s + len);

				s += SkipSpace(s + len + 1, e) - s;
			}
			else
				__ERR__(__LOG_E__ << "Unexpected character in #include directive.\n", NULL);
		}
		s++;

		// Load external metafile.
		File tmp_file;

		if	(Parser::Load(include_path, tmp_file, true))
		{
			// Then transfer all tags to our current file.
			NMLFileForeach(t, tmp_file)
			{
				if	(tmp_file.UnlinkRoot(t))
					tag.AddChild(t);
				else
					__LOG_E__ << "Could not relink root tag from included metafile.\n";
			}
		}
		else
			__LOG_E__ << "Failed to include external metafile '" << include_path << "'.\n";
	}
	else	if	(!strncmp(s, "append", 6))
		__LOG_E__ << "Preprocessor directive #append deprecated.\n";
	else
		__ERR__(__LOG_E__ << "Unknown preprocessor directive, metatag '" << tag.name << "'.\n", NULL)

	return s;
}
bool				Parser::ParseTag(Tag &tag, const char *s, const char *e, const char **es)
{
	// Safety net.
	s += SkipSpace(s, e) - s;
	if	(s[0] != '<')
		return false;

	// Get the tag id.
	#define	MLTAG_ERROR(c)	{ __LOG_E__ << c; return false; }

	const char *etn = s + 1;
	etn += SkipEntry(etn, e) - etn;
	if	(String::strfindchar(s + 1, ':', etn) != etn)
		MLTAG_ERROR("':' is a path character and cannot be used in tag name.\n")
	if	(String::strfindchar(s + 1, ';', etn) != etn)
		MLTAG_ERROR("';' is a path character and cannot be used in tag name.\n")
	
	tag.name.Set(s + 1, etn);
	s = etn;

	// Get tag type.
	s += SkipSpace(s, e) - s;
	if	(s == e)
		MLTAG_ERROR("Mangled definition, metatag '" << tag.name << "'.\n")

	switch (s[0])
	{
		case '>':
			s++;
			break;

		// Node/real/integer/string.
		case '=':
		{
			s++;
			forever
			{
				s += SkipSpace(s, e) - s;
				if	(s == e)
					MLTAG_ERROR("Mangled definition, metatag '" << tag.name << "'.\n")

				// End of tag.
				if	(s[0] == '>')
				{
					s++;
					break;
				}

				// Preprocessor directive.
				if	(s[0] == '#')
					s = ParseTagPreprocessorDirective(tag, s + 1, e);

				// Node.
				else	if	(s[0] == '<')
				{
					if	(tag.GetValue().GetType() != Variant::VariantNone)
						MLTAG_ERROR("Incoherent type in tag '" << tag.name << "' declaration.\n")

					Tag *stag = tag.tags.Add(new Tag)->Object();
					if	(!ParseTag(*stag, s, e, &s))
						MLTAG_ERROR("")
					s += SkipSpace(s, e) - s;
				}

				// Constant.
				else
				{
					if	(tag.GetValue().GetType() != Variant::VariantNone)
						MLTAG_ERROR("Incoherent type in tag '" << tag.name << "' declaration.\n")

					// Binary.
					if	(s[0] == '=')
					{
						s++;
						if	(!(s[0] >= '0' && s[0] <= '9'))
							MLTAG_ERROR("Expected encoded size in binary tag '" << tag.name << "' declaration.\n")

						const char *ye = s;
						while (ye[0] >= '0' && ye[0] <= '9')
							ye++;
						if	(ye[0] != ':')
							MLTAG_ERROR("Expected size delimiter in binary tag '" << tag.name << "' declaration.\n")
						uint asize = String(s, ye).Integer();

						s = ye + 1;
						if	(!(s[0] >= '0' && s[0] <= '9'))
							MLTAG_ERROR("Expected binary size in binary tag '" << tag.name << "' declaration.\n")
						ye = s;
						while (ye[0] >= '0' && ye[0] <= '9')
							ye++;

						// Trailing @ means yEnc binary.
						File::Binary encoding = File::Binary_UU;
						if	(ye[0] == '@')
						{
							encoding = File::Binary_yEnc;
							ye++;
						}

						// Detect EOL
						if	((ye[0] != 0x0a) && ((ye[0] != 0x0d) && (ye[1] != 0x0a)))
							MLTAG_ERROR("Expected EOL following binary size in binary tag '" << tag.name << "' declaration.\n")

						size_t eol_size = (ye[0] == 0x0a) ? 1 : 2;
 						uint bsize = String(s, ye).Integer();
 
						uchar *astart = (uchar *)(ye + eol_size);

						// [EJ] Adjust asize to account for Windows EOL (historically NML only specifies Unix ascii size).
						if	(eol_size > 1)
						{
							__LOG_V__ << "CRLF reduces NML binary load performance.\n";

							size_t a_size_in = asize;
							asize = 0;

							for (; a_size_in > 0; --a_size_in)
								if	((astart[asize] == 0x0d) && (astart[asize + 1] == 0x0a))
									asize += 2;
								else
									++asize;
						}
						// Load ASCII encoded data.
 						Array <uchar> aenc(asize, Alloc::Metatag);
 						if	(!aenc)
 							MLTAG_ERROR("Failed to allocate binary buffer in binary tag '" << tag.name << "'.\n")
						
						memcpy(&aenc[0], astart, asize);
 
						s = ye + eol_size + asize;
 						if	(s[0] != '>')
 							MLTAG_ERROR("Expected closing tag in tag '" << tag.name << "'.\n")

						Array <uchar> data(bsize, Alloc::Metatag);
						if	(data)
						{
							switch (encoding)
							{
								case File::Binary_UU:	nAsciiEncoder::UUDecode(&aenc[0], asize, &data[0], bsize);	break;
								case File::Binary_yEnc:	nAsciiEncoder::yDecode(&aenc[0], asize, &data[0], bsize);	break;
							}
							tag.GetValue().SetBinary(&data[0], bsize);
						}
					}
					// Real/Integer.
					else	if	((s[0] >= '0' && s[0] <= '9') || (s[0] == '.') || (s[0] == '-'))
					{
						if	(IsConstantFloat(s, e))
								tag.GetValue() = String::atof(s, e, true);
						else	tag.GetValue() = String::atoi(s);

						if	(s[0] == '-')
							s++;
						s += SkipEntry(s, e) - s;

						if	(s[0] != '>')
							MLTAG_ERROR("Unexpected trailing expression following value, metatag '" << tag.name << "'.\n")
					}
					// String.
					else	if	(s[0] == '\"')
					{
						s++;
						ptrdiff_t len = RunToEOS(s, e) - s;
						if	((s + len) == e)
							MLTAG_ERROR("Mangled string declaration, metatag '" << tag.name << "'.\n")

						// Copy string.
						tag.GetValue() = String(s, s + len);

						s += SkipSpace(s + len + 1, e) - s;						// Jump over string.
						if	(s == e)
							MLTAG_ERROR("Unexpected EOF after string declaration, metatag '" << tag.name << "'.\n")
						if	(s[0] != '>')
							MLTAG_ERROR("Unexpected trailing expression following string object, metatag '" << tag.name << "'.\n")

						tag.GetValue().s_value.ReplaceAll("\\n", "\n");		// convert CF
					}
					// Boolean.
					else	if	(!strncmp(s, "True", 4))
					{
						tag.GetValue() = true;
						s += SkipSpace(s + 4, e) - s;
						if	(s[0] != '>')
							MLTAG_ERROR("Unexpected trailing expression following value, metatag '" << tag.name << "'.\n")
					}
					else	if	(!strncmp(s, "False", 5))
					{
						tag.GetValue() = false;
						s += SkipSpace(s + 5, e) - s;
						if	(s[0] != '>')
							MLTAG_ERROR("Unexpected trailing expression following value, metatag '" << tag.name << "'.\n")
					}
					else
						MLTAG_ERROR("Unexpected '" << s[0] << "' in assignation, metatag '" << tag.name << "'.\n")
				}
			}
		}
		break;

		default:
			MLTAG_ERROR("Unexpected trailing expression after metatag '" << tag.name << "' name declaration.\n")
	}
	if	(es)
		es[0] = s;
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Parser::LoadFromMemory(const char *data, size_t size, File &mfl)
{
	mfl.Free();
	if	(!data)
		return false;

	// Read header tag, expected to be NML version.
	const char *pof = data, *eof = data + size;

	#define	MEM_MLP_ERROR(c)	{	(c); return false;	}

	Tag header_tag;
	if	(!ParseTag(header_tag, pof, eof, &pof))
		MEM_MLP_ERROR(__LOG_E__ << "Invalid metafile.\n")
	if	((header_tag.name != "Version") && (header_tag.name != "NML"))
		MEM_MLP_ERROR(__LOG_E__ << "Unknown metafile variant.\n")

	switch (header_tag.GetValue().GetType())
	{
		case Variant::VariantInteger:
			if	(header_tag.GetInteger() > version)
				__LOG_W__ << "Newer version NML header found (" << header_tag.GetInteger() << ">" << version << ").\n";
			break;

		case Variant::VariantFloat:
			if	(header_tag.GetReal() > version)
				__LOG_W__ << "Newer version NML header found (" << header_tag.GetReal() << ">" << version << ").\n";
			break;

		default:
			__LOG_W__ << "Unknown NML header version identification method.\n";
			break;
	}

	// Read all root tags.
	while (pof < eof)
	{
		Tag *tag = mfl.tags.Add(new Tag)->Object();
		if	(!ParseTag(*tag, pof, eof, &pof))
			return false;
		pof += SkipSpace(pof, eof) - pof;
	}
	return true;
}
bool				Parser::Load(const char *path, File &file, bool verbose)
{
	if	(!path)
		return false;

	Array <char> data;
	if	(!Platform::Get().io->FileLoad(path, data, verbose))
		return false;

	try
	{
		if	(!LoadFromMemory(data.c_ptr(), data.GetSize(), file))
			return false;
	}
	catch (char *e)
	{
		__LOG_E__ << "Failed to load file LoadFromMemory. " << path << "\n";
		return false;
	}
	file.name = path;
	return true;
}
File			*Parser::Load(const char *metafile, bool verbose)
{
	AutoPtr <File> mfl(new File);
	if	(mfl.IsNull())
		__ERR__(__LOG_E__ << "Failed to allocate file.\n", NULL)
	return Load(metafile, *mfl, verbose) ? mfl.Detach() : NULL;
}
//------------------------------------------------------------------------------
