/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program 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 3 of the License, or
(at your option) any later version.

This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "hsTypes.h"
#include "hsStlUtils.h"
#include "plSDL.h"
#include "plFile/hsFiles.h"
#include "plFile/plStreamSource.h"
#include "pnNetCommon/pnNetCommon.h"
#include "pnNetCommon/plNetApp.h"

static const int kTokenLen=256;

void plSDLParser::DebugMsg(char* fmt, ...) const
{
	return;
	plNetApp* netApp = plSDLMgr::GetInstance()->GetNetApp();

	va_list args;
	va_start(args, fmt);
	
	if (netApp)
	{
		hsLogEntry(netApp->DebugMsgV(fmt, args));
	}
	else
		DebugMsgV(fmt, args);
	va_end(args);
}

void plSDLParser::DebugMsgV(char* fmt, va_list args) const
{
	if (strlen(fmt)==nil)
		return;
	hsStatusMessage(xtl::formatv(fmt,args).c_str());
}

//
// parsing stateDesc
// read name, version
// return true to skip the next token read
//
bool plSDLParser::IParseStateDesc(const char* fileName, hsStream* stream, char token[], plStateDescriptor*& curDesc) const
{	
	plSDL::DescriptorList* descList = &plSDLMgr::GetInstance()->fDescriptors;

	bool ok = true;

	//
	// NAME
	//
//	curDesc=plSDLMgr::GetInstance()->FindDescriptor(token, plSDL::kLatestVersion);
//	if (!curDesc)
	{
		curDesc = TRACKED_NEW plStateDescriptor;
		curDesc->SetName(token);

		DebugMsg("SDL: DESC name=%s", token);
	}
	
	//
	// {
	//
	stream->GetToken(token, kTokenLen);	// skip '{'
	
	//
	// VERSION
	//
	if (stream->GetToken(token, kTokenLen))	
	{
		if (!strcmp(token, "VERSION"))
		{
			// read desc version
			hsAssert(curDesc, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			if (stream->GetToken(token, kTokenLen))
			{
				int v=atoi(token);
				curDesc->SetVersion(v);
				DebugMsg("\tVersion=%d", v);
			}				
		}
		else
		{
			hsAssert(false, xtl::format("Error parsing state desc, missing VERSION, fileName=%s", 
				fileName).c_str());
			ok = false;
		}
	}
	else
	{
		hsAssert(false, xtl::format("Error parsing state desc, fileName=%s", fileName).c_str());
		ok = false;
	}

	if ( ok )
	{
		ok = ( plSDLMgr::GetInstance()->FindDescriptor(curDesc->GetName(), curDesc->GetVersion())==nil );
		if ( !ok )
		{
			std::string err = xtl::format( "Found duplicate SDL descriptor for %s version %d.\nFailed to parse file: %s", curDesc->GetName(), curDesc->GetVersion(), fileName );
			plNetApp::StaticErrorMsg( err.c_str() );
			hsAssert( false, err.c_str() );
		}
	}

	if ( ok )
	{
		descList->push_back(curDesc);
	}
	else
	{
		delete curDesc;
		curDesc = nil;
	}

	return false;
}

//
// Parse a variable descriptor.
// read type, name, count [default]
// return true to skip the next token read
//
bool plSDLParser::IParseVarDesc(const char* fileName, hsStream* stream, char token[], plStateDescriptor*& curDesc, 
								plVarDescriptor*& curVar) const
{
	hsAssert(curDesc, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
	if ( !curDesc )
		return false;

	bool skipNext=false;
	std::string dbgStr;
	static char	seps[] = "( ,)[]";
	// read type, name, cnt, [default]
	
	//
	// TYPE
	// create new state var, make current
	//
	if (*token == '$')
	{
		// nested sdls
		char* sdlName = token+1;
		plStateDescriptor* stateDesc = plSDLMgr::GetInstance()->FindDescriptor(sdlName, plSDL::kLatestVersion);
		hsAssert(stateDesc, xtl::format("can't find nested state desc reference %s, fileName=%s", 
				sdlName, fileName).c_str());
		curVar = TRACKED_NEW plSDVarDescriptor(stateDesc);
	}
	else
		curVar = TRACKED_NEW plSimpleVarDescriptor;
	
	curDesc->AddVar(curVar);
	bool ok=curVar->SetType(token);
	hsAssert(ok, xtl::format("Variable 'type' syntax problem with .sdl file, type=%s, fileName=%s", token, fileName).c_str());
	dbgStr = xtl::format("\tVAR Type=%s ", token).c_str();
	
	//
	// NAME (foo[1])
	//			
	if (stream->GetToken(token, kTokenLen))
	{
		hsAssert(strstr(token, "[") && strstr(token, "]"), xtl::format("invalid var syntax, missing [x], fileName=%s", 
				fileName).c_str());
		char* ptr = strtok( token, seps );	// skip [
		
		hsAssert(curVar, xtl::format("Missing current var.  Syntax problem with .sdl file, fileName=%s", fileName).c_str());
		curVar->SetName(token);
		//
		// COUNT
		//
		char* cntTok=strtok(nil, seps);		// kill ]
		int cnt = cntTok ? atoi(cntTok) : 0;
		curVar->SetCount(cnt);
		if (cnt==0)
			curVar->SetVariableLength(true);
		dbgStr += xtl::format("Name=%s[%d]", curVar->GetName(), cnt).c_str();
	}
	
	//
	// optional tokens: DEFAULT, INTERNAL
	//
	while (stream->GetToken(token, kTokenLen))
	{
		if (!strcmp(token, "DEFAULT"))
		{
			hsAssert(curVar, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			// read state var type
			
			std::string defaultStr;
			plSimpleVarDescriptor* sVar=(plSimpleVarDescriptor*)curVar;
			if (sVar)
			{
				int i;
				for(i=0;i<sVar->GetAtomicCount();i++)
				{
					if (stream->GetToken(token, kTokenLen))
					{
						defaultStr += token;
						if (i!=sVar->GetAtomicCount()-1)
							defaultStr += ",";
					}
				}
			}
			if (defaultStr.size())
			{
				curVar->SetDefault(defaultStr.c_str());				
				dbgStr += std::string(" DEFAULT=") + defaultStr;
			}
		}
		else
		if (!strcmp(token, "DISPLAYOPTION"))
		{
			hsAssert(curVar, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			dbgStr += std::string(" ") + token;

			hsBool read=stream->GetToken(token, kTokenLen);
			if (read)
			{
				std::string oldOptions=curVar->GetDisplayOptions();
				if (oldOptions.size())
					oldOptions += std::string(",");
				oldOptions += token;
				curVar->SetDisplayOptions(oldOptions.c_str());
				dbgStr += std::string("=") + token;
				if (!stricmp(token, "hidden"))
					curVar->SetInternal(true);
			}
			else
			{
				hsAssert(false, xtl::format("missing displayOption string, fileName=%s", fileName).c_str());
			}
		}
		else
		if (!strcmp(token, "DEFAULTOPTION"))
		{
			hsAssert(curVar, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			dbgStr += std::string(" ") + token;

			hsBool read=stream->GetToken(token, kTokenLen);
			if (read)
			{
				dbgStr += std::string("=") + token;
				if (!stricmp(token, "vault"))
					curVar->SetAlwaysNew(true);
			}
			else
			{
				hsAssert(false, xtl::format("missing defaultOption string, fileName=%s", fileName).c_str());
			}
		}

#if 1	// delete me in May 2003
		else
		if (!strcmp(token, "INTERNAL"))
		{
			hsAssert(curVar, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			curVar->SetInternal(true);
			dbgStr += std::string(" ") + token;
		}
		else
		if (!strcmp(token, "PHASED"))
		{
			hsAssert(curVar, xtl::format("Syntax problem with .sdl file, fileName=%s", fileName).c_str());
			curVar->SetAlwaysNew(true);
			dbgStr += std::string(" ") + token;
		}
#endif
		else
		{
			skipNext=true;
			break;
		}
	}

	DebugMsg((char*)dbgStr.c_str());

	return skipNext;
}

//
// create state descriptor from sdl file.
// return false on err.
//
bool plSDLParser::ILoadSDLFile(const char* fileName) const
{
	DebugMsg("Parsing SDL file %s", fileName);
	
	wchar_t* temp = hsStringToWString(fileName);
	hsStream* stream = plStreamSource::GetInstance()->GetFile(temp);
	delete [] temp;
	if (!stream)
		return false;

	stream->Rewind();

	plVarDescriptor* curVar=nil;
	plStateDescriptor* curDesc=nil;
	char token[kTokenLen];
	bool parsingStateDesc=false;
	bool skip=false;
	while (1)
	{		
		if (!skip)
		{
			if (!stream->GetToken(token, kTokenLen))
				break;
		}
		skip=false;

		if (!strcmp(token, "VAR"))
		{
			parsingStateDesc=false;
			curVar=nil;		// start fresh
			continue;
		}
		
		if (!strcmp(token, "STATEDESC"))
		{
			parsingStateDesc=true;
			curDesc=nil;	// start fresh
			continue;
		}

		if (!strcmp(token, "}"))
		{
			if ( curDesc )
				curDesc->SetFilename( fileName );
			parsingStateDesc=false;
			continue;
		}

		if (parsingStateDesc)
		{
			skip=IParseStateDesc(fileName, stream, token, curDesc);
			if ( !curDesc )
				break;	// failed to parse state desc
		}
		else
		{
			skip=IParseVarDesc(fileName, stream, token, curDesc, curVar);
		}
	}

	// If the very last char is a } without a \n, then it won't be handled above for some reason, so we have to catch it here.
	if ( curDesc )
		curDesc->SetFilename( fileName );

	// do not close or delete the stream, we do not own it
	return true;
}

//
// load all .sdl files in sdl directory, and create descriptors for each.
// return false on error
//
bool plSDLParser::IReadDescriptors() const
{
	std::string sdlDir = plSDLMgr::GetInstance()->GetSDLDir();
	DebugMsg("SDL: Reading latest descriptors from directory %s", sdlDir.c_str());

	wchar_t* temp = hsStringToWString(sdlDir.c_str());
	std::wstring wSDLDir = temp;
	delete [] temp;

	// Get the names of all the sdl files
	std::vector<std::wstring> files = plStreamSource::GetInstance()->GetListOfNames(wSDLDir, L".sdl");

	bool ret=true;
	int cnt=0;
	for (int i = 0; i < files.size(); i++)
	{
		char* str = hsWStringToString(files[i].c_str());
		if (!ILoadSDLFile(str))
		{
			plNetApp* netApp = plSDLMgr::GetInstance()->GetNetApp();
			if (netApp)
				netApp->ErrorMsg("Error loading SDL file %s", str);
			else
				hsStatusMessageF("Error loading SDL file %s", str);
			ret=false;
		}
		else
			cnt++;
		delete [] str;
	}
	DebugMsg("Done reading SDL files");	

	if (!cnt)
		ret=false;

	return ret;
}


//
// reads sdl folder, creates descriptor list
//
bool plSDLParser::Parse() const
{
	return IReadDescriptors();
}