// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "mission_compiler.h"
#include "step.h"
#include "nel/misc/i18n.h"
#include "nel/misc/common.h"
#include "nel/ligo/primitive_utils.h"

using namespace std;
using namespace NLMISC;
using namespace NLLIGO;


// hack to get access to string manager item enumeration to string without including
// almost all of the Ryzom server side project
namespace STRING_MANAGER
{
	NL_BEGIN_STRING_CONVERSION_TABLE (TParamType)	
		NL_STRING_CONVERSION_TABLE_ENTRY( item )
		NL_STRING_CONVERSION_TABLE_ENTRY( place )
		NL_STRING_CONVERSION_TABLE_ENTRY( creature )
		NL_STRING_CONVERSION_TABLE_ENTRY( skill )
		NL_STRING_CONVERSION_TABLE_ENTRY( role )
		NL_STRING_CONVERSION_TABLE_ENTRY( ecosystem )
		NL_STRING_CONVERSION_TABLE_ENTRY( race )
		NL_STRING_CONVERSION_TABLE_ENTRY( sbrick )
		NL_STRING_CONVERSION_TABLE_ENTRY( faction )
		NL_STRING_CONVERSION_TABLE_ENTRY( guild )
		NL_STRING_CONVERSION_TABLE_ENTRY( player )
		NL_STRING_CONVERSION_TABLE_ENTRY( bot )
		{ "int", integer},
//		NL_STRING_CONVERSION_TABLE_ENTRY( integer )
		NL_STRING_CONVERSION_TABLE_ENTRY( time )
		NL_STRING_CONVERSION_TABLE_ENTRY( money )
		NL_STRING_CONVERSION_TABLE_ENTRY( compass )
		NL_STRING_CONVERSION_TABLE_ENTRY( string_id )
		NL_STRING_CONVERSION_TABLE_ENTRY( dyn_string_id )
		NL_STRING_CONVERSION_TABLE_ENTRY( self )
		NL_STRING_CONVERSION_TABLE_ENTRY( creature_model )
		NL_STRING_CONVERSION_TABLE_ENTRY( entity )
		NL_STRING_CONVERSION_TABLE_ENTRY( body_part )
		NL_STRING_CONVERSION_TABLE_ENTRY( score )
		NL_STRING_CONVERSION_TABLE_ENTRY( sphrase )
		NL_STRING_CONVERSION_TABLE_ENTRY( characteristic )
		NL_STRING_CONVERSION_TABLE_ENTRY( damage_type )
		NL_STRING_CONVERSION_TABLE_ENTRY( bot_name)
		NL_STRING_CONVERSION_TABLE_ENTRY( power_type )		
		NL_STRING_CONVERSION_TABLE_ENTRY( literal )		
	NL_END_STRING_CONVERSION_TABLE(TParamType, ParamTypeConversion, NB_PARAM_TYPES)

	//-----------------------------------------------
	// stringToParamType
	//-----------------------------------------------
	TParamType stringToParamType( const std::string & str )
	{
		return ParamTypeConversion.fromString( str );
	}

	//-----------------------------------------------
	// stringToParamType
	//-----------------------------------------------
	const std::string & paramTypeToString( TParamType type )
	{
		return ParamTypeConversion.toString( type );
	}
}

// utility to 'tabulate' the lines in a string
void tabulateLine(std::string &text, uint nbTabs)
{
	if (text.empty())
		return;
	string::size_type pos = 0;
	string	tabs;
	
	for (uint i=0; i<nbTabs; ++i)
		tabs += "\t";

	// add a tab at start
	text = tabs + text;

	// add a tab at each new line
	while ((pos = text.find('\n', pos)) != string::npos)
	{
		if (pos < text.size()-1 && text[pos+1] == '\r')
		{
			// add after the '\r' char
			++pos;
		}
		if (pos < text.size()-1)
			text = text.substr(0, pos+1) + tabs + text.substr(pos+1);
	}
}



class GenderExtractor
{
public:
	

	GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level=0);

	std::string operator()(unsigned int i) const;

	unsigned int size() const;

	
	~GenderExtractor();
private:	
	bool extractMarkup(const std::string& literal, const std::string & markup,  std::string &before,  std::string &inside,  std::string & after);

	bool parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include = false );

	std::string getPhrase(unsigned int i) const;

	std::string getExtension(unsigned int i) const;
	
	std::string getCondition(unsigned int i) const;

	std::string getEntity(unsigned int i) const;

	std::string getIdentifier(unsigned int i) const;


private:
	
	
	bool _Entity;
	std::string _EntityName;

	std::string _Text;
	std::string _Identifier;
	
	GenderExtractor* _Female;
	GenderExtractor* _Male;



};
GenderExtractor::~GenderExtractor()
{
	delete _Female;
	delete _Male;
}
std::string GenderExtractor::getIdentifier(unsigned int i) const
{
	return _Identifier + getExtension(i);
}

std::string GenderExtractor::operator()(unsigned int i) const
{
	std::string ret("\t");
	std::string condition = getCondition(i);

	ret += condition.empty() ? "":std::string("( ") + condition + " )" + NL + "\t\t";
	ret +=  getIdentifier(i) + "\t[" + getPhrase(i) + "]" + NL;
	return ret;
}


GenderExtractor::GenderExtractor(const std::string & literal, const std::string& identifier, unsigned int level)
{

	
	static const char * es[] ={"e", "e1", "e2", "e3"};
	static const char * fs[] ={"f", "f1", "f2", "f3"};
	static const char * hs[] ={"h", "h1", "h2", "h3"};

	const char * e = es[level];
	const char * f = fs[level];
	const char * h = hs[level];

	_Identifier = toLower(identifier);

	std::string newPhrase;

	std::string before;
	std::string after;
	std::string femaleText;
	std::string maleText;

	_Entity =  extractMarkup(literal, e, before, _EntityName, after);
	if (_EntityName.size() > 2)
	{
		if (_EntityName[0] == '$'  && _EntityName[_EntityName.size() - 1] == '$')
		{
			_EntityName = _EntityName.substr(1, _EntityName.size() - 2);
		}
	}

	std::string newLiteral = before + after;

	bool isFemale = parseMarkup(newLiteral,f,newPhrase, true);		
	if ( isFemale) 
	{
		parseMarkup(newPhrase,h,newPhrase, false);
		femaleText = newPhrase;
	}

	bool isMale = parseMarkup(newLiteral, h, newPhrase, true);
	if (isMale)
	{

		parseMarkup(newPhrase, f, newPhrase, false);
		maleText = newPhrase;
	}

	if (isMale != isFemale)
	{
		std::string goodMarkup = isMale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">";
		std::string badMarkup  = isFemale ? std::string("") +"<" + h + "></" + h + ">" : std::string("")+"<"+f+"></"+f+">";
		std::string exceptionText = std::string("Expression ") + identifier + " that contains a tag " + goodMarkup + " needs also tags " + badMarkup + " even empty.";		
		throw EParseException(0, exceptionText.c_str());
	}

	if (!isMale && !isFemale)
	{
		_Text = literal;
		_Female = 0;
		_Male = 0;

	}
	else
	{ 
		if (!_Entity) {	_EntityName = "self"; } 
		_Female = new GenderExtractor(femaleText, identifier, level+1);
		_Male =  new GenderExtractor(maleText, identifier, level+1);
	}



}

bool GenderExtractor::extractMarkup(const std::string& literal, const std::string & markup,  std::string &before,  std::string &inside,  std::string & after)
{
	std::string::size_type posBegin;
	std::string::size_type posEnd;
	std::string::size_type posInside;
	
	std::string beginMarkup = std::string("<") + markup + std::string(">");
	std::string endMarkup = std::string("</") + markup + std::string(">");
	posBegin = literal.find(beginMarkup);
	if ( posBegin != std::string::npos )
	{
		posEnd = literal.find(endMarkup, posBegin + beginMarkup.size());
		if (posEnd != std::string::npos)
		{			
			before = literal.substr(0, posBegin);
			posInside = posBegin + beginMarkup.size();
			inside = literal.substr(posInside, posEnd - posInside);
			after = literal.substr(posEnd+endMarkup.size());
			return true;
		}	
	}
	after = literal;
	return false;
}

bool GenderExtractor::parseMarkup(const std::string& literal, const std::string & markup, std::string& newPhrase, bool include )
{
	
	bool markupExist;
	bool changed = false;
	std::string oldPhrase = literal;
	
	newPhrase.clear();
	do 
	{
		std::string before;
		std::string inside;	
		std::string after;
		markupExist = extractMarkup(oldPhrase, markup, before, inside, after);
		newPhrase += before;
		if (include){ newPhrase += inside; }
		oldPhrase = after;
		if (markupExist){ changed = true; }

	} while(markupExist);
	
	newPhrase += oldPhrase;
	return changed;	
}

std::string GenderExtractor::getPhrase(unsigned int i) const
{ 
	if ( i%2 == 0) { return _Male ?  _Male->getPhrase(i/2) :  _Text; } 
	if ( i%2 == 1) { nlassert(_Female); return  _Female->getPhrase(i/2);}
	nlassert(0);
	return "";
}

std::string GenderExtractor::getExtension(unsigned int i) const
{ 
	if ( i%2 == 0) { return _Male ?  std::string("_m") + _Male->getExtension(i/2) :  ""; } 
	if ( i%2 == 1) { nlassert(_Female); return  std::string("_f") + _Female->getExtension(i/2);}
	nlassert(0);
	return "";
}

std::string GenderExtractor::getCondition(unsigned int i) const
{ 

	//if ( i%2 == 0) { return _Male ?  std::string("\t(") + _Male->getExtension(i/2) :  "\t"; } 
	//if ( i%2 == 1) { nlassert(_Female); return  std::string("_f") + _Female->getExtension(i/2);}

	if ( i%2 == 0) 
	{ 
		if (_Male)
		{
			std::string next = _Male->getCondition(i/2);
			std::string current = _EntityName + ".gender = male";
			return next.size() ? current + " & " + next : current;
			
		}
		else
		{
			return "";
		}		
	}
	
	if ( i%2 == 1) 
	{	
		std::string next = _Female->getCondition(i/2);
		std::string current = _EntityName + ".gender = female";
		return next.size() ? current + " & " + next : current;		
	} 

	nlassert(0);

	return "";
}

unsigned int GenderExtractor::size() const
{
	return _Male ? _Male->size() +  _Female->size(): 1;
}


string CPhrase::genPhrase()
{
	string ret;
	if (!_PhraseLiterals.empty())
	{
		for (uint p=0; p<_PhraseLiterals.size(); ++p)
		{
			string identifier = _PhraseId;
			if (_NumEntry != 0)
				identifier += toString("_%u", p+1);

			GenderExtractor gender(_PhraseLiterals[p], identifier, 0);

			ret += identifier + " (";
			// generate default param list
			if (_DefaultParams.size() > p)
			{
				for (uint i=0; i<_DefaultParams[p].size(); ++i)
				{
					ret += STRING_MANAGER::paramTypeToString(_DefaultParams[p][i].ParamType) + " "+_DefaultParams[p][i].ParamName;
					if (i != _DefaultParams[p].size()-1 || !_AdditionalParams.empty())
						ret += ", ";
				}
			}
			// generate additional param list
			for (uint i=0; i<_AdditionalParams.size(); ++i)
			{
				ret += STRING_MANAGER::paramTypeToString(_AdditionalParams[i].ParamType) + " "+_AdditionalParams[i].ParamName;
				if (i != _AdditionalParams.size()-1)
					ret += ", ";
			}
			ret += ")" + NL;
			ret += "{" + NL;

			for (unsigned int i = 0; i < gender.size(); ++i)							
			{				
				ret += gender(i);
			}

			ret += "}" + NL + NL;
		}
	}
nlinfo("genphrase: %s", ret.c_str());
	return ret;
}




bool CMissionCompiler::generateDotScript(NLLIGO::IPrimitive *missionPrim, std::string &dotScript, std::string &log)
{
	//assume that the mission is compiled in the last compiled mission slot
	try
	{
		if (compileMission(missionPrim, string()))
		{
			dotScript = _CompiledMission.back()->generateDotScript();
			return true;
		}
		else
		{
			return false;
		}
	}
	catch(const EParseException & e)
	{
		log = e.Why;
		return false;
	}
}

/*
bool CMissionCompiler::parseGlobalMissionData(IPrimitive *mission, CMissionData &md)
{
	// Mission name
	string *s;
	if (!mission->getPropertyByName("name", s) || s->empty())
		throw EParseException(mission, "missing mission name !");
	md.setMissionName(*s);

	// giver primitive file
	if (!mission->getPropertyByName("giver_primitive", s) || s->empty())
		throw EParseException(mission, "missing giver primitive !");
	md.setGiverPrimitive(*s);

	// giver name
	if (!mission->getPropertyByName("giver_primitive", s) || s->empty())
		throw EParseException(mission, "missing giver primitive !");
	md.setGiverName(*s);
	// If the mission is under a npc_bot node, then the giver is directly taken
	// from the npc name
	if (mission->getParent())
	{
		if (mission->getParent()->getPropertyByName("class", s) && *s == "npc_bot")
		{
			if (mission->getParent()->getPropertyByName("name", s))
				md.setGiverName(*s);
		}
	}

	// TODO : read all other params... 

	return true;
}
*/

void CMissionData::initHeaderPhrase(IPrimitive *prim)
{
	CPhrase::TPredefParams params;
	params.resize(1);
	params[0].push_back(CPhrase::TParamInfo("giver", STRING_MANAGER::bot));
	_MissionTitle.initPhrase(*this, prim, _MissionTitleRaw, 0, params); 
	_MissionDescription.initPhrase(*this, prim, _MissionDescriptionRaw, 0, params);
	_MissionAutoMenu.initPhrase(*this, prim, _MissionAutoMenuRaw);
}

bool CMissionCompiler::compileMission(NLLIGO::IPrimitive *rootPrim, const std::string &primFileName)
{
	TPrimitiveClassPredicate pred("mission_tree");
	if (!pred(rootPrim))
		return false;

	IPrimitive	*mission = rootPrim;
	CMissionData	*pmd = new CMissionData;
	CMissionData	&md = *pmd;

	// Read the mission name
	string missionName = md.getProperty(mission, "name", false, false);
	if( missionName.find(' ') != string::npos)
	{
		throw EParseException(mission, toString("Mission name '%s' must not contains space", missionName.c_str()).c_str());
	}
	md.setMissionName(missionName);
	// Create a temporary primitive node to create default variable
	{	
		// giver default var
		IPrimitive *temp = new CPrimNode();
		temp->addPropertyByName("class", new CPropertyString("var_npc"));
		temp->addPropertyByName("name", new CPropertyString("giver = giver"));
		temp->addPropertyByName("npc_name", new CPropertyString("giver"));
		temp->addPropertyByName("var_name", new CPropertyString("giver"));

		IVar *var = IVar::createVar(md, temp);
		md.addVariable(NULL, var);

		delete temp;
	}

	{
		// player default var
		IPrimitive *temp = new CPrimNode();
		temp->addPropertyByName("class", new CPropertyString("var_npc"));
		temp->addPropertyByName("name", new CPropertyString("player = player"));
		temp->addPropertyByName("npc_name", new CPropertyString("player"));
		temp->addPropertyByName("var_name", new CPropertyString("player"));

		IVar *var = IVar::createVar(md, temp);
		md.addVariable(NULL, var);

		delete temp;
	}

	{	
		// guild_name default var
		IPrimitive *temp = new CPrimNode();
		temp->addPropertyByName("class", new CPropertyString("var_text"));
		temp->addPropertyByName("name", new CPropertyString("guild_name = guild_name"));
		temp->addPropertyByName("npc_name", new CPropertyString("guild_name"));
		temp->addPropertyByName("var_name", new CPropertyString("guild_name"));

		IVar *var = IVar::createVar(md, temp);
		md.addVariable(NULL, var);

		delete temp;
	}

	// first, start by reading mission variables
	IPrimitive	*variables;
	{
		TPrimitiveClassPredicate predTmp("variables");
		variables= NLLIGO::getPrimitiveChild(mission, predTmp);
	}

	if (!variables)
	{
		nlwarning("Can't find variables !");
		return false;
	}
	parseVariables(md, variables);

	// read global mission data
	md.parseMissionHeader(rootPrim);

	// now, we can init the mission header phrase (they need variable knwoled)
	md.initHeaderPhrase(rootPrim);
	
	IPrimitive	*preReq;
	{
		TPrimitiveClassPredicate predTmp("pre_requisite");
		preReq = getPrimitiveChild(mission, predTmp);
	}

	if (!preReq)
	{
		nlwarning("Can't find pre requisite !");
		return false;
	}
	parsePreRequisite(md, preReq);
	
/*	IPrimitive	*steps = getPrimitiveChild(mission, TPrimitivePropertyPredicate("step_tag", "true"));
	if (!steps)
	{
		nlwarning("Can't find steps !");
		return false;
	}
*/	parseSteps(md, mission);

	// Store the compiled mission
	_CompiledMission.push_back(pmd);
	
	string script = md.generateMissionScript(primFileName);

	nlinfo("The script :");
	nlinfo("%s", script.c_str());

	string phrases = md.generatePhraseFile();
	nlinfo("The phrase file is :");
	{
		vector<string> lines;
		explode(phrases, string("\n"), lines, false);
		for (uint i=0; i<lines.size(); ++i)
		{
			if(lines[i][0] == '\r') lines[i] = lines[i].substr(1);
			nlinfo("%s", lines[i].c_str());
		}
	}

	string dot = md.generateDotScript();
	nlinfo("The dot script is :");
	{
		vector<string> lines;
		explode(dot, string("\n"), lines, false);
		for (uint i=0; i<lines.size(); ++i)
		{
			if(lines[i][0] == '\r') lines[i] = lines[i].substr(1);
			nlinfo("%s", lines[i].c_str());
		}
	}
	return true;
}

bool	CMissionCompiler::compileMissions(IPrimitive *rootPrim, const std::string &primFileName)
{
	bool ret = true;
	// 1st, build a set of mission_scrip nodes
	NLLIGO::TPrimitiveSet	missionTrees;
	
	CPrimitiveSet<TPrimitiveClassPredicate>	scriptsSet;
	
	TPrimitiveClassPredicate pred("mission_tree");
	scriptsSet.buildSet(rootPrim, pred, missionTrees);
	
	nlinfo("Found %u mission tree in the primitive file", missionTrees.size());
	
	for (uint i=0; i<missionTrees.size(); ++i)
	{
//		try
//		{
			compileMission(missionTrees[i], primFileName);
//		}
//		catch (const EParseException &e)
//		{
//			nlwarning("Error while parsing a mission: '%s'", e.Why.c_str());
//			ret = false;
//		}

	}
	
	return ret;
	
}

bool CMissionCompiler::installCompiledMission(NLLIGO::CLigoConfig &ligoConfig, const std::string &primFileName)
{
	// generate the mission script into the npcs...
	{
		map<string, TLoadedPrimitive >	loadedPrimitives;

		// store the previous alias value
		map<string, uint32>			missionAlias;

		// First loop to remove any mission that belong to the compiled primitive file
		for (uint i=0; i<_CompiledMission.size(); ++i)
		{
			CMissionData &mission = *(_CompiledMission[i]);
			// first, look for the primitive file to load
			string fileName = mission.getGiverPrimitive();
			if (fileName.empty())
			{
				// use mission primitive instead
				fileName = primFileName;
			}
			if (loadedPrimitives.find(toLower(fileName)) == loadedPrimitives.end())
			{
				string fullFileName = CPath::lookup(fileName, false);
				if (fullFileName.empty())
				{
					throw EParseException(NULL, toString("Can't find primitive file '%s' in path", fileName.c_str()).c_str());
				}
				// we need to load this primitive file.
				CPrimitives *primDoc = new CPrimitives;
				CPrimitiveContext::instance().CurrentPrimitive = primDoc;
				if (loadXmlPrimitiveFile(*primDoc, fullFileName, ligoConfig))
				{
					// the primitive file is loaded correctly
					loadedPrimitives.insert(make_pair(toLower(fileName), TLoadedPrimitive(primDoc, fullFileName)));
					CPrimitiveContext::instance().CurrentPrimitive = NULL;
				}
				else
				{
					CPrimitiveContext::instance().CurrentPrimitive = NULL;
					throw EParseException(NULL, toString("Can't read primitive file '%s'", fullFileName.c_str()).c_str());
				}
			}
			TLoadedPrimitive &loadedPrim = loadedPrimitives[toLower(fileName)];
			CPrimitives *primDoc = loadedPrim.PrimDoc;

			TPrimitiveSet scripts;
			CPrimitiveSet<TPrimitiveClassPredicate> filter;
			TPrimitiveClassPredicate pred("mission");
			filter.buildSet(primDoc->RootNode, pred, scripts);
			
			// for each script, check if it was generated, and if so, check the name
			// of the source primitive file.
			for (uint i=0; i<scripts.size(); ++i)
			{
				vector<string> *script;
				if (scripts[i]->getPropertyByName("script", script) && !script->empty())
				{						
					string missionName;

					scripts[i]->getPropertyByName("name", missionName);

					// Format should be : #compiled from <source_primitive_name>
					if (script->front().find("generated from") != string::npos)
					{
						// we have a compiled mission
						if (script->front().find(CFile::getFilename(primFileName)) != string::npos)
						{
							// ok, this mission is compiled from the same primitive
							
							// store it's alias
							TPrimitiveClassPredicate pred("alias");

							IPrimitive *p = getPrimitiveChild(scripts[i], pred);

							if (p)
							{
								CPrimAlias *pa = dynamic_cast<CPrimAlias*>(p);
								if (pa)
								{
									uint32 alias = pa->getAlias();
									missionAlias.insert(make_pair(missionName, alias));
								}
							}
							else
							{
								nlwarning("Can't find alias prim in primitive '%s'", buildPrimPath(scripts[i]).c_str());
							}
						
							// and remove it
							scripts[i]->getParent()->removeChild(scripts[i]);
						}
					}
				}
			}
		}

		// second loop to assign compiled mission to giver npc
		for (uint i=0; i<_CompiledMission.size(); ++i)
		{
			CMissionData &mission = *(_CompiledMission[i]);
			string fileName = mission.getGiverPrimitive();
			if (fileName.empty())
			{
				// no giver primitive file specified in the mission, use the mission primitive instead
				fileName = primFileName;
			}

			TLoadedPrimitive &loadedPrim = loadedPrimitives[toLower(fileName)];
			CPrimitives *primDoc = loadedPrim.PrimDoc;
			CPrimitiveContext::instance().CurrentPrimitive = primDoc;

			TPrimitiveSet bots;
			CPrimitiveSet<TPrimitiveClassAndNamePredicate> filter;
			TPrimitiveClassAndNamePredicate pred("npc_bot", mission.getGiverName());
			filter.buildSet(primDoc->RootNode, pred, bots);

			if (bots.empty())
			{
				string err = toString("Can't find bot '%s' in primitive '%s' !", 
					mission.getGiverName().c_str(),
					fileName.c_str());
				throw EParseException(NULL, err.c_str());
			}
			else if (bots.size() > 1)
			{
				string err = toString("Found more than one bot named '%s' in primitive '%s' !", 
					mission.getGiverName().c_str(),
					fileName.c_str());
				throw EParseException(NULL, err.c_str());
			}

			// ok, all is good, we can add the mission node to the giver
			IPrimitive *giver = bots.front();
			// create a new node for the mission
			IPrimitive *script = new CPrimNode;
			// set the class
			script->addPropertyByName("class", new CPropertyString("mission"));
			// set the name
			script->addPropertyByName("name", new CPropertyString(mission.getMissionName()));
//				string alias(toString("%u", makeHash32(mission.getMissionName())));
//			script->addPropertyByName("alias", new CPropertyString(mission.getAlias()));
			string scriptLines = mission.generateMissionScript(primFileName);
			vector<string> lines;
			explode(scriptLines, NL, lines, false);

			script->addPropertyByName("script", new CPropertyStringArray(lines));

			// insert the script into the giver
			giver->insertChild(script);

			// add the alias
			{
				CPrimAlias *pa = new CPrimAlias;
				pa->addPropertyByName("class", new CPropertyString ("alias"));
				pa->addPropertyByName("name", new CPropertyString ("alias"));

				if (missionAlias.find(mission.getMissionName()) != missionAlias.end())
				{
					// restore the previous alias
					primDoc->forceAlias(pa, missionAlias.find(mission.getMissionName())->second);
				}

				// insert in first place
				script->insertChild(pa, 0);
			}

			CPrimitiveContext::instance().CurrentPrimitive = NULL;
		}

		// Save the modified primitive files
		while (!loadedPrimitives.empty())
		{
			TLoadedPrimitive &loadedPrim = loadedPrimitives.begin()->second;
			if (!saveXmlPrimitiveFile(*(loadedPrim.PrimDoc), loadedPrim.FullFileName))
				return false;

			_FilesToPublish.push_back(loadedPrim.FullFileName);

			// Free the memory
			delete loadedPrim.PrimDoc;

			loadedPrimitives.erase(loadedPrimitives.begin());
		}
	}

	// generate the phrase file (if any)
	{
		string phraseFileName = CFile::getFilenameWithoutExtension(primFileName) + "_wk.txt";

		CSString content;

		for (uint i=0; i<_CompiledMission.size(); ++i)
		{
			content += _CompiledMission[i]->generatePhraseFile();
		}
		// transform NL (\n\r) into single \n
		content = content.replace(NL.c_str(), "\n");
		ucstring ucs;
		ucs.fromUtf8(content);

		CI18N::writeTextFile(phraseFileName, ucs, true);

		_FilesToPublish.push_back(phraseFileName);
	}

	return true;
}


bool CMissionCompiler::publishFiles(const std::string &serverPathPrim, const std::string &serverPathText, const std::string &localPathText)
{
	for (uint i=0 ; i<_FilesToPublish.size() ; i++)
	{
		string dst, src = _FilesToPublish[i];

		string::size_type n = src.find("primitives");
		if (n == string::npos)
		{
			// text files : copy it and check include in phrase_rites_wk.txt

			// server
			string textFile = CPath::standardizePath(serverPathText) + "phrase_rites_wk.txt";
			includeText(textFile, string("#include \"") + src + string("\"\n"));
			dst = CPath::standardizePath(serverPathText) + src;
			NLMISC::CFile::copyFile(dst, src);

			// local
			textFile = CPath::standardizePath(localPathText) + "phrase_rites_wk.txt";
			includeText(textFile, string("#include \"") + src + string("\"\n"));
			dst = CPath::standardizePath(localPathText) + src;
			NLMISC::CFile::copyFile(dst, src);
		}
		else
		{
			// primitive file : copy to server
			dst = CPath::standardizePath(serverPathPrim) + string(src, n, src.size());
			NLMISC::CFile::copyFile(dst, src);
		}
	}
	return true;
}

bool CMissionCompiler::includeText(const std::string filename, const std::string text)
{
	FILE *f = nlfopen(filename, "r+");
	if (f == NULL)
		return false;

	bool isIn = false;
	char buffer[1024];

	// Check for UTF8 format
	fread(buffer, 1, 3, f);
	if (buffer[0] != -17 || buffer[1] != -69 || buffer[2] != -65)
		fseek(f, 0, SEEK_SET);

	// Compare each line
	while(fgets(buffer, 1024, f))
	{
		if (!strcmp(text.c_str(), buffer))
		{
			isIn = true;
			break;
		}
	}

	if (!isIn)
		fputs(text.c_str(), f);

	fclose(f);
	return true;
}

bool CMissionCompiler::parsePreRequisite(CMissionData &md, IPrimitive *preReq)
{
	md.parsePrerequisites(preReq);
	return true;
}

bool CMissionCompiler::parseOneStep(CMissionData &md, IPrimitive *stepToParse, IStep *parent, bool bEndOfBranch)
{
	IStep *step = IStep::createStep(md, stepToParse);
	if (step != NULL)
	{

		if (!step->isAJump() && !step->getStepName().empty())
		{
			if (md.getStepByName(step->getStepName()) != NULL)
			{
				string err = toString("Step '%s' already defined !", step->getStepName().c_str());
				throw EParseException(step->getPrimitive(), err.c_str());
			}

			if (step->getStepName().find(' ') != string::npos)
			{
				throw EParseException(step->getPrimitive(), toString("Step name '%s' must not contains space", step->getStepName().c_str()).c_str());
			}
			md.addStepName(step->getStepName(), step);
		}

		TPrimitiveSet subBranchs = step->getSubBranchs();
		
		// Add the step (if no parent add to the mission data)
		if (parent == NULL)
		{
			if (!md.addStep(step))
			{
				throw EParseException(stepToParse, "Error parsing mission step");
			}
		}
		else
		{
			parent->addSubStep(step);
		}

		CStepIf *pSI = dynamic_cast<CStepIf *>(step);
		// If this is a IF step : parse with 'step' as a parent 

		IStep *pParentStep = NULL;
		
		if ((dynamic_cast<CStepIf*>(step) != NULL) ||
			(dynamic_cast<CStepPlayerReconnect*>(step) != NULL))
			pParentStep = step;

		if (!subBranchs.empty())
		{
			// need to parse subbranch before continuing
			for (uint i=0; i<subBranchs.size(); ++i)
			{					
				if (!parseOneStep(md, subBranchs[i], pParentStep, i==(subBranchs.size()-1)))
					return false;
			}
		}

		// if this is the last step, flag it as such
		step->EndOfBranch = bEndOfBranch;
	}
	return true;
}

bool CMissionCompiler::parseSteps(CMissionData &md, IPrimitive *steps, IStep *parent)
{
	TPrimitiveSet childs;
	TPrimitivePropertyPredicate pred("step_tag", "true");
	filterPrimitiveChilds(steps, pred, childs);

	if (childs.empty())
	{
		CPrimNode node;
		node.addPropertyByName("class", new CPropertyString("end"));
		node.addPropertyByName("name", new CPropertyString(""));
		IStep *step = IStep::createStep(md, &node);
		delete step;
//		md.addStep(step);
	}
	if (!childs.empty())
	{
		for (uint i=0; i<childs.size(); ++i)
		{
			IPrimitive *child = childs[i];

			parseOneStep(md, childs[i], NULL, i == (childs.size()-1));

		}
	}
	return true;
}

string CMissionCompiler::getProp(IPrimitive *prim, const string &propName)
{
	string s;
	bool ret = prim->getPropertyByName(propName.c_str(), s);
	if (!ret)
		throw EParseException(prim, toString("Property %s does't exist", propName.c_str()).c_str());
	
	return s;
}

string CMissionCompiler::getClass(IPrimitive *prim)
{
	string className;
	bool ret = prim->getPropertyByName("class", className);
	nlassert(ret);
	return className;
}

bool CMissionCompiler::parseVariables(CMissionData &md, IPrimitive *variables)
{
	for (uint i=0; i<variables->getNumChildren(); ++i)
	{
		IPrimitive *child;
		if (variables->getChild(child, i))
		{
			IVar *var = IVar::createVar(md, child);
			if (var)
			{
				nldebug("Adding variable '%s' as type %u", var->getVarName().c_str(), var->getVarType());
				md.addVariable(child, var);
			}
		}
	}
	return true;
}

template <class VectorType>
bool strtokquote(const string &src, VectorType &tokens)
{
	enum TMode
	{
		read_blank,
		read_token,
		read_quoted
	};

	string	temp;
	TMode	mode = read_blank;
	
	for (uint i=0; i<src.size(); ++i)
	{
		switch (mode)
		{
		case read_blank:
			if (src[i] != ' ' && src[i] != '\t' && src[i] != '\n' && src[i] != '\r')
			{
				// end of blank !
				if (src[i] == '\"')
				{
					// begin of a quoted string
					temp = "\"";
					mode = read_quoted;
				}
				else
				{
					// begin of a token
					temp.clear();
					temp += src[i];
					mode = read_token;
				}
			}
			break;
		case read_token:
			if (src[i] == ' ' || src[i] == '\t' || src[i] == '\n' || src[i] == '\r' || src[i] == '\"')
			{
				// end of token
				tokens.push_back(temp);
				temp.clear();
				--i;
				mode = read_blank;
			}
			else
			{
				temp += src[i];
			}
			break;
		case read_quoted:
			if (src[i] == '\\')
			{
				// special treatment for escape command
				if (i < src.size()-1)
				{
					temp += src[i];
					temp += src[i+1];
					// skip escaped char
					i++;
				}
				else
				{
					nlwarning("Error parsing escape char in quoted string");
					return false;
				}
			}
			else if (src[i] != '\"')
			{
				// just add this char
				temp += src[i];
			}
			else
			{
				// end of quoted string
				temp += src[i];
				tokens.push_back(temp);
				temp.clear();
				mode = read_blank;
			}
			break;
		}
	}
	if (!temp.empty())
	{
		if (mode == read_quoted)
		{
			nlwarning("Missing closing quote at end of string while reading text in '%s'", src.c_str());
			return false;
		}
		tokens.push_back(temp);
	}
	return true;
}

template <class VectorType>
bool strtokquote(const vector<string> &src, VectorType &tokens)
{
	for (uint i=0; i<src.size(); ++i)
	{
		if (!strtokquote(src[i], tokens))
			return false;
	}
	return true;
}

struct TFindParamPred : std::unary_function<CPhrase::TParamInfo, bool>
{
	string	Name;
	TFindParamPred(const std::string &name)
		: Name (name)
	{}

	bool operator() (const CPhrase::TParamInfo &paramInfo) const
	{
		return paramInfo.ParamName == Name;
	}
};


bool CPhrase::isEmpty()
{
	return _PhraseId.empty();
}

bool CPhrase::asAdditionnalParams()
{
	return !_AdditionalParams.empty();
}


void CPhrase::initPhrase (CMissionData &md, 
					IPrimitive *prim, 
					const vector<string> &texts, 
					uint32 numEntry, 
					const TPredefParams &predefParams )
{
//	nlassert(numEntry == predefParams.size());

	// store the predefined/default parameters
	_DefaultParams = predefParams;
	// store the number of entry to generate (for literal with variant)
	_NumEntry = numEntry;

	numEntry = max(uint32(1), numEntry);

	_PhraseLiterals.clear();
//	_PhraseLiterals.resize(numEntry);

	// first, concatenate the text vector
	string text;
	for (uint i=0; i<texts.size(); ++i)
	{
		text = text + texts[i];
		if (i != texts.size() -1)
			text += "\n";
	}

	nldebug("phrase text: %s", text.c_str());

	CVectorSString	tokens;

	if (!strtokquote(text, tokens))
		throw EParseException(prim, toString("failed to tokenize the string '%s'", text.c_str()).c_str());

	if (tokens.empty())
		// nothing to parse
		return;

	// storage for additional parameters
	vector<string>	params;

retry:
	// ok, the string is parsed, now we can analyze it
	// look at the first letter of the first token to determine the type of data we have
	if (tokens[0][0] == '\"')
	{
		// we have a literal, so we must found numEntry literal, then a suffix tag for the phrase name
		if (tokens.size() != numEntry +1)
			throw EParseException(prim, toString("bad number of tokens in phrase : need %u (%u entries + 1 suffix), found %u\n(in : '%s')", 
									numEntry+1, 
									numEntry, 
									tokens.size(),
									text.c_str()
									).c_str());

		_PhraseLiterals.resize(numEntry);
		for (uint i=0; i<numEntry; ++i)
		{
			CSString text = tokens[i];
			// remove quotation marks
			text = text.leftCrop(1);
			text = text.rightCrop(1);

			// store the literal phrase value
			_PhraseLiterals[i] = text;
			// escape any ']' in the string
			_PhraseLiterals[i] = CSString(_PhraseLiterals[i]).replace("]", "\\]");

			// now, we can analyse the string content, looking for parameters replacement
			while (text.contains('$'))
			{
				// 'advance' to replacement point
				text = text.splitFrom('$');
				if (!text.empty())
				{
					if (text[0] != '$')
					{
						if (!text.contains('$'))
							throw EParseException(prim, "missing parameter closing tag '$'");

						string::size_type paramStart = _PhraseLiterals[i].size() - text.size();
						// ok, we found a parameter
						CSString p = text.splitTo('$', true);
						// remove any subpart access
						p = p.splitTo('.');
						if (i >= predefParams.size() || find_if(predefParams[i].begin(), predefParams[i].end(), TFindParamPred(static_cast<string&>(p))) == predefParams[i].end())
						{
							// this param is not in the predefined params list, add it to the optional params
							params.push_back(p);
						}	

						// remove any compiler param from the phrase literal
						if (p.find("@") != string::npos)
						{
							string::size_type pos = _PhraseLiterals[i].find(p, paramStart);
							if (pos != string::npos)
							{
								string::size_type pos2 = _PhraseLiterals[i].find("@", pos);
								if (pos2 != string::npos)
								{
									while (pos2 < _PhraseLiterals[i].size() 
										&& _PhraseLiterals[i][pos2] != '.' 
										&& _PhraseLiterals[i][pos2] != '$')
									{
										_PhraseLiterals[i].erase(pos2, 1);
									}
								}

							}
						}
					}
					else
					{
						// this is an escaped $, skip it
						text.leftCrop(1);
					}
				}
			}
		}
		// last, read the suffix
		_Suffixe = tokens.back();

		// generate identifier
		_PhraseId = toUpper(md.getMissionName()+"_"+_Suffixe);

		set<string>	ps;
		// select only unique params
		ps.insert(params.begin(), params.end());

		vector<string> temp(ps.begin(), ps.end());
		params.swap(temp);

	}
	else if (tokens[0][0] == '$')
	{
		// we have a variable substitution. Retrieve the var and recall init

		// do the var replacement
		CVectorSString	tokens2;

		tokens[0] = md.replaceVar(prim, tokens[0]);

		if (!strtokquote(tokens[0], tokens2))
			throw EParseException(prim, toString("failed to tokenize the string ('%s')", tokens[0].c_str()).c_str());

		tokens2.insert(tokens2.end(), tokens.begin()+1, tokens.end());
		tokens.swap(tokens2);
		
		// and retry the decoding
		goto retry;
	}
	else
	{
		// this should be a simple identifier, followed by any number of additional parameters

		// do the var replacement
//		tokens = md.replaceVar(prim, tokens);
//		untagVar(tokens[0]);
	
		// ok, now extract the phrase label and the additional parameters
		_PhraseId = tokens[0];
		for (uint i=1; i<tokens.size(); ++i)
		{
			untagVar(tokens[i]);
			if (predefParams.empty() || find_if(predefParams[0].begin(), predefParams[0].end(), TFindParamPred(static_cast<string&>(tokens[i]))) == predefParams[0].end())
			{
				// this param is not in the predefined params list, add it to the optional params
				params.push_back(tokens[i]);
			}
		}
	}

	// now, build the parameter list

	vector<string>::iterator first(params.begin()), last(params.end());
	for (; first != last; ++first)
	{
		string name, param;
		vector<string> parts;
		NLMISC::explode(*first, string("@"), parts, false);
		
		if (parts.size() > 0)
			name = parts[0];
		if (parts.size() > 1)
			param = parts[1];

		const string &varName = name;

		if (varName != "self")
		{
			IVar *var = md.getVariable(varName);
			if (var == NULL)
			{
				string err = toString("Can't find variable '%s' referenced from a phrase", 
					name.c_str());
				throw EParseException(prim, err.c_str());
			}

			TParamInfo pi;
			pi.ParamName = name;
			pi.CompilerParam = param;
			pi.ParamType = var->getStringManagerType();
			_AdditionalParams.push_back(pi);
		}
	}
}

std::string CPhrase::genScript(CMissionData &md)
{
	std::string ret;

	ret = _PhraseId;
	for (uint i=0; i<_AdditionalParams.size(); ++i)
	{
		IVar *var = md.getVariable(_AdditionalParams[i].ParamName);
		if (var == NULL)
		{
			string err = toString("Can't find variable named '%s' to generate phrase param", _AdditionalParams[i].ParamName.c_str());
			throw EParseException(NULL, err.c_str());
		}
		ret += "; " + var->evalVar(_AdditionalParams[i].CompilerParam);
	}

	return ret;
}

CMissionData::CMissionData()
{
	// init all datas
	_MonoInstance = false;
	_MissionAuto = false;
	_RunOnce = false;
	_Replayable = false;
	_Solo = false;
	_Guild = false;
	_NotInJournal = false;
	_AutoRemoveFromJournal = false;
	_PlayerReplayTimer = 0;
	_GlobalReplayTimer = 0;
	_NotProposed = false;
	_NonAbandonnable = false;
	_NeedValidation = false;
	_FailIfInventoryIsFull = false;
}

CMissionData::~CMissionData()
{
	while (!_Variables.empty())
	{
		delete _Variables.begin()->second;
		_Variables.erase(_Variables.begin());
	}

	while (!_Steps.empty())
	{
		delete _Steps.back();
		_Steps.pop_back();
	}
}

void CMissionData::setMissionName(const string &missionName)
{
	_MissionName = missionName;
}

const string &CMissionData::getMissionName()	{ return _MissionName;}

bool CMissionData::addVariable(NLLIGO::IPrimitive *prim, IVar *var)
{
	if (_Variables.find(var->getVarName()) != _Variables.end())
		throw EParseException(prim, toString("Variable '%s' already defined !", var->getVarName().c_str()).c_str());
	
	_Variables.insert(make_pair(var->getVarName(), var));
	_VariablesOrder.push_back(var);
	return true;
}

IVar *CMissionData::getVariable(const string &varName)
{
	map<string, IVar*>::iterator it(_Variables.find(varName));
	if (it != _Variables.end())
		return it->second;
	return NULL;
}

IStep *CMissionData::getNextStep(IStep *current)
{
	for (uint i=0; i<_Steps.size(); ++i)
	{
		if (_Steps[i] == current && i < _Steps.size()-1)
			return _Steps[i+1];
	}
	return NULL;
}

IStep *CMissionData::getStepByName(const std::string &stepName)
{
	if (_StepsByNames.find(stepName) != _StepsByNames.end())
	{
		return _StepsByNames[stepName];
	}

	return NULL;
}


bool CMissionData::addStep(IStep *step)
{
	_Steps.push_back(step);
	return true;
}

string CMissionData::genPreRequisites()
{
	string ret;
	if (!_ReqSkills.empty())
	{
		ret += "req_skill : ";
		for (uint i=0; i<_ReqSkills.size(); ++i)
		{
			ret += _ReqSkills[i].Skill+" "+_ReqSkills[i].MinLevel+" "+_ReqSkills[i].MaxLevel;
			if (i < _ReqSkills.size()-1)
				ret +="; ";
			else
				ret += NL;
		}
	}
	if (!_ReqMissionDone.empty())
	{
		for (uint i=0; i<_ReqMissionDone.size(); ++i)
		{
			ret += "req_mission : "+ _ReqMissionDone[i]+NL;
		}
	}
	if (!_ReqMissionNotDone.empty())
	{
		for (uint i=0; i<_ReqMissionNotDone.size(); ++i)
		{
			ret += "req_mission_neg : "+_ReqMissionNotDone[i]+NL;
		}
	}
	if (!_ReqMissionRunning.empty())
	{
		for (uint i=0; i<_ReqMissionRunning.size(); ++i)
		{
			ret += "req_mission_running : "+_ReqMissionRunning[i]+NL;
		}
	}
	if (!_ReqMissionNotRunning.empty())
	{
		
		for (uint i=0; i<_ReqMissionNotRunning.size(); ++i)
		{
			ret += "req_mission_running_neg : "+_ReqMissionNotRunning[i]+NL;
		}
	}
	if (!_ReqWearItem.empty())
	{
		ret += "req_wear : ";
		for (uint i=0; i<_ReqWearItem.size(); ++i)
		{
			ret += _ReqWearItem[i];
			if(i < _ReqWearItem.size()-1)
				ret +="; ";
			ret += NL;
		}
	}
	if (!_ReqOwnItem.empty())
	{
		ret += "req_item : ";
		for (uint i=0; i<_ReqOwnItem.size(); ++i)
		{
			ret += _ReqOwnItem[i];
			if(i < _ReqOwnItem.size()-1)
				ret +="; ";
			ret += NL;
		}
	}
	if (!_ReqTitle.empty())
	{
		ret += "req_title : "+_ReqTitle+NL;
	}
	if (!_ReqFames.empty())
	{
		for (uint i=0; i<_ReqFames.size(); ++i)
		{
			ret += "req_fame : "+_ReqFames[i].Faction+" "+_ReqFames[i].Fame;
			ret += NL;
		}
	}
	if(_ReqGuild)
	{
		ret += "req_guild"+NL;
	}
	if (!_ReqGrade.empty())
	{
		ret += "req_grade : "+_ReqGrade+NL;
	}
	if (!_ReqTeamSize.empty())
	{
		ret += "req_team_size : "+_ReqTeamSize+NL;
	}
	if (!_ReqBrick.empty())
	{
		ret += "req_brick : ";
		for (uint i=0; i<_ReqBrick.size(); ++i)
		{
			ret += _ReqBrick[i];
			if(i < _ReqBrick.size()-1)
				ret +="; ";
			ret += NL;
		}
	}
	if (!_ReqCharacterAge.empty())
	{
		ret += "req_character_age : "+_ReqCharacterAge+NL;
	}
	if (!_ReqMaxPlayerID.empty())
	{
		ret += "req_max_player_id : "+_ReqMaxPlayerID+NL;
	}
	if (!_ReqSeason.empty())
	{
		ret += "req_season : "+_ReqSeason+NL;
	}
	if (!_ReqEncyclo.empty())
	{
		ret += "req_encyclo_thema : " + _ReqEncyclo + NL;
	}
	if (!_ReqEncycloNeg.empty())
	{
		ret += "req_encyclo_thema_neg : " + _ReqEncycloNeg + NL;
	}
	if (!_ReqEventFaction.empty())
	{
		ret += "req_event_faction : " + _ReqEventFaction + NL;
	}
	
	return ret;
}


string CMissionData::generateMissionScript(const std::string &primFileName)
{
	_JumpPoints.clear();
	// first, gather jump point list
	for (uint i=0; i<_Steps.size(); ++i)
	{
		set<TJumpInfo>	temp;
		_Steps[i]->fillStepJump(*this, temp);

		// remove any jump to the next step (normal flow)
		if (i < _Steps.size()-1)
		{
			set<TJumpInfo>::iterator first(temp.begin()), last(temp.end());
			for (; first != last; )
			{
				const TJumpInfo &ji = *first;

				if (ji.StepName == _Steps[i+1]->getStepName() && ji.Discardable)
				{
					temp.erase(first);
					first = temp.begin();
				}
				else
					++first;
			}
		}

		_JumpPoints.insert(temp.begin(), temp.end());
	}
	// generate the script
	string script;
	// generate mission header
	script += "# script generated from '"+CFile::getFilename(primFileName)+"'"+NL+NL;
	script += "#mission tags and pre-requisites"+NL;
	if (_MonoInstance)
		script += "mono"+NL;
	if (_RunOnce)
		script += "once"+NL;
	if (_Replayable)
		script += "replayable"+NL;
	if (_Solo)
		script += "solo"+NL;
	if (_Guild)
		script += "guild"+NL;
	if (_NotInJournal)
		script += "no_list"+NL;
	if (_AutoRemoveFromJournal)
		script += "auto_remove"+NL;
	if (!_MissionCategory.empty())
		script += "mission_category : "+_MissionCategory+NL;
	if (_PlayerReplayTimer != 0)
		script += "player_replay_timer : "+toString("%u", _PlayerReplayTimer)+NL;
	if (_GlobalReplayTimer != 0)
		script += "global_replay_timer : "+toString("%u", _GlobalReplayTimer)+NL;
	if (_NotProposed)
		script += "not_proposed"+NL;
	if (_MissionAuto)
		script += string("auto : ")+_MissionAutoMenu.genScript(*this)+NL;
	if (_NonAbandonnable)
		script += "non_abandonnable"+NL;
	if (!_MissionIcon.empty())
		script += "mission_icon : "+_MissionIcon+NL;
	if (_NeedValidation)
		script += "need_validation"+NL;
	if (_FailIfInventoryIsFull)
		script += "fail_if_inventory_is_full"+NL;
	
	if (!_ParentMissions.empty())
	{
		set<string>::iterator first(_ParentMissions.begin()), last(_ParentMissions.end());
		for (; first != last; ++first)
		{
			script += "parent : "+ *first+NL;
		}
	}

	script += NL+"#Variables declaration"+NL;

	// declare all the variables
	{
		std::vector<IVar*>::iterator first(_VariablesOrder.begin()), last(_VariablesOrder.end());
		for (; first != last; ++first)
		{
			script += (*first)->genDecl(*this);
		}
	}

	script += NL+"#pre-requisites"+NL;
	script += genPreRequisites();

	script += NL+"#script"+NL;
	// generate mission title and desc
	script += "mission_title : "+_MissionTitle.genScript(*this)+NL;
	script += "mission_desc : "+_MissionDescription.genScript(*this)+NL;

	// generate steps scripts
	for (uint i=0; i<_Steps.size(); ++i)
	{
		script += "# "+_Steps[i]->getStepName()+NL;
		if (_JumpPoints.find(_Steps[i]->getStepName()) != _JumpPoints.end()
			&& !_Steps[i]->isAJump())
		{
			// insert a jump point
			script += "jump_point : " + _Steps[i]->getStepName() + NL;
		}

		script += _Steps[i]->genCode(*this);
		//if (_Steps[i]->EndOfBranch && !_Steps[i]->isAJump())
		//	script += "end"+NL;
	}

	return script;
}

string CMissionData::generatePhraseFile()
{
	string ret;
	// generate header phrase
	ret = _MissionTitle.genPhrase();
	ret += _MissionDescription.genPhrase();
	ret += _MissionAutoMenu.genPhrase();

	// generate var phrase
	for (uint i=0; i<_VariablesOrder.size(); ++i)
	{
		ret += _VariablesOrder[i]->genPhrase();
	}

	// generate step phrase
	for (uint i=0; i<_Steps.size(); ++i)
	{
		ret += _Steps[i]->genPhrase();
	}
	return ret;
}

string CMissionData::generateDotScript()
{
	string ret = "digraph " + _MissionName + NL;
	ret += "{" + NL;

	// set default shape to 'record'
	ret += "node [shape=record]"+NL;

	ret += "\t__start__ [shape=\"point\", peripheries=2, label=\"\"]"+NL;

	// 1st pass, generate node for each step
	for (uint i=0; i<_Steps.size(); ++i)
	{
		if (!_Steps[i]->isEnd() && !_Steps[i]->isAJump())
		{
			ret += "\t"+_Steps[i]->getStepName();
			ret += " [URL=\""+buildPrimPath(_Steps[i]->getPrimitive())+"\"]"+NL;
		}
	}

	ret += "\t__end__ [shape=\"point\"]"+NL;

	// activate red color for shapes that are created after this points
	ret += "node [color=red]"+NL;

	// 2nd pass, generate link between steps
	for (uint i=0; i<_Steps.size(); ++i)
	{
		if (_Steps[i]->isAJump())
			continue;

		if (i == 0)
		{
			ret += "\t__start__ -> " + _Steps[i]->getStepName() + NL;
		}
		set<TJumpInfo> jumps;
		_Steps[i]->fillStepJump(*this, jumps);
		// there is a link there
		while (!jumps.empty())
		{
			const TJumpInfo &ji = *(jumps.begin());
			if (_StepsByNames.find(ji.StepName) != _StepsByNames.end() 
				&& _StepsByNames[ji.StepName]->isAJump())
			{
				// this step is a jump, skip to link to the jump destination
				IStep *jumpStep = _StepsByNames[ji.StepName];
				set<TJumpInfo> jumpJump;
				jumpStep->fillStepJump(*this, jumpJump);
				if (jumpJump.size() != 1)
				{
					string str = toString("Step jump contains %u jumps destination instead of 1", jumpJump.size());
					throw EParseException(jumpStep->getPrimitive(), str.c_str());
				}

				ret += "\t"+_Steps[i]->getStepName() + " -> " + jumpJump.begin()->StepName+" [label=\""+ji.JumpName+"\"]" + NL;
			}
			else
			{
				ret += "\t"+_Steps[i]->getStepName() + " -> " + ji.StepName+" [label=\""+jumps.begin()->JumpName+"\"]" + NL;
			}
			jumps.erase(jumps.begin());
		}

	}

	ret += "}" + NL;

	return ret;
}


void CMissionData::parseMissionHeader(NLLIGO::IPrimitive *prim)
{
//	_MissionName = getProperty(prim, "name", false, false);
//	if( _MissionName.find(' ') != string::npos)
//	{
//		throw EParseException(prim, toString("Mission name '%s' must not contains space", _MissionName.c_str()).c_str());
//	}
	_GiverPrimitive = getProperty(prim,"giver_primitive", true, false);
	_MissionGiver = getProperty(prim, "mission_giver", true, false);

//	_Alias = getProperty(prim, "alias", false, false);

	// If the mission is under a npc_bot node, then the giver is directly taken
	// from the npc name
	if (prim->getParent())
	{
		if (getProperty(prim->getParent(), "class", false, false) == "npc_bot")
		{
			_MissionGiver = getProperty(prim->getParent(), "name", false, false);
		}
	}

	vector<string> vs;
	_MissionTitleRaw = getPropertyArray(prim, "mission_title", false, false);
//	_MissionTitle.init(*this, prim, vs); 
	_MissionDescriptionRaw = getPropertyArray(prim, "mission_description", false, false);
//	_MissionDescription.init(*this, prim, vs);
	_MonoInstance = toLower(getProperty(prim, "mono_instance", true, false)) == "true";
	_RunOnce = toLower(getProperty(prim, "run_only_once", true, false)) == "true";
	_Replayable = toLower(getProperty(prim, "replayable", true, false)) == "true";
	
	_NeedValidation = toLower(getProperty(prim, "need_validation", true, false)) == "true";
	
	_MissionAutoMenuRaw = getPropertyArray(prim, "phrase_auto_menu", false, false);

	// audience setting 
	string s = getProperty(prim, "audience", false, false);
	if (s == "solo")
		_Solo = true;
	else if (s == "guild")
		_Guild = true;
	
	_NotInJournal = NLMISC::toLower(getProperty(prim, "not_in_journal", false, false)) == "true";
	_AutoRemoveFromJournal = NLMISC::toLower(getProperty(prim, "auto_remove_from_journal", false, false)) == "true";
	_MissionCategory = getProperty(prim, "mission_category", false, false);
	NLMISC::fromString(getProperty(prim, "player_replay_timer", true, false), _PlayerReplayTimer);
	NLMISC::fromString(getProperty(prim, "global_replay_timer", true, false), _GlobalReplayTimer);
	_NotProposed = NLMISC::toLower(getProperty(prim, "not_proposed", false, false)) == "true";
	_MissionAuto = NLMISC::toLower(getProperty(prim, "automatic", false, false)) == "true";
	_NonAbandonnable = NLMISC::toLower(getProperty(prim, "non_abandonnable", false, false)) == "true";
	_FailIfInventoryIsFull = NLMISC::toLower(getProperty(prim, "fail_if_inventory_is_full", false, false)) == "true";
	_MissionIcon = getProperty(prim, "mission_icon", false, false);

	if (_MissionAuto)
	{
		if (_MissionAutoMenuRaw.empty())
		{
			string error = toString("Mission is flagged automatic, but no phrase_auto_menu defined !");
			throw EParseException(prim, error.c_str());
		}
	}

	vs = getPropertyArray(prim, "parent_missions", true, false);
	_ParentMissions.insert(vs.begin(), vs.end());

}

void CMissionData::parsePrerequisites(NLLIGO::IPrimitive *prim)
{
	// skills
	vector<string>	vs;
	vs = getPropertyArray(prim, "require_skill/min_level/max_level", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 3)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_skill array. Need 3, found %u", i, parts.size()).c_str());
			}
			TReqSkill rs;
			rs.Skill = parts[0];
			rs.MinLevel = parts[1];
			rs.MaxLevel = parts[2];

			_ReqSkills.push_back(rs);

		}
	}
	// Mission done
	vs = getPropertyArray(prim, "require_mission_done", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_done array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqMissionDone.push_back(parts[0]);
		}
	}
	// Mission not done
	vs = getPropertyArray(prim, "require_mission_not_done", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_not_done array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqMissionNotDone.push_back(parts[0]);
		}
	}
	// Mission running
	vs = getPropertyArray(prim, "require_mission_running", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_running array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqMissionRunning.push_back(parts[0]);
		}
	}
	// Mission not running
	vs = getPropertyArray(prim, "require_mission_not_running", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_mission_not_running array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqMissionNotRunning.push_back(parts[0]);
		}
	}
	// wearing item
	vs = getPropertyArray(prim, "require_wearing_item", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_wearing_item array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqWearItem.push_back(parts[0]);
		}
	}
	// own item
	vs = getPropertyArray(prim, "require_own_item", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_own_item array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqOwnItem.push_back(parts[0]);
		}
	}
	// title
	_ReqTitle = getProperty(prim, "require_title", true, false);
	// fame
	vs = getPropertyArray(prim, "require_faction/fame", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 2)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_faction/fame array. Need 2, found %u", i, parts.size()).c_str());
			}
			TReqFame rf;
			rf.Faction = parts[0];
			rf.Fame = parts[1];

			_ReqFames.push_back(rf);
		}
	}
	// guild 
	if (getProperty(prim, "require_guild_membership", true, false) == "true")
		_ReqGuild = true;
	else
		_ReqGuild = false;
	// grade
	_ReqGrade = getProperty(prim, "require_guild_grade", true, false);
	// team size
	_ReqTeamSize = getProperty(prim, "require_team_size", true, false);
	// character minimum age
	_ReqCharacterAge = getProperty(prim, "require_character_age", true, false);
	// maximum player ID
	_ReqMaxPlayerID = getProperty(prim, "require_max_player_id", true, false);
	// brick
	vs = getPropertyArray(prim, "require_brick_knowledge", true, false);
	for (uint i=0; i<vs.size(); ++i)
	{
		if (!vs[i].empty())
		{
			vector<string> parts;
			strtokquote(vs[i], parts);
			if (parts.size() != 1)
			{
				throw EParseException(prim, toString("Invalide argument count in line %u of require_brick_knowledge array. Need 1, found %u", i, parts.size()).c_str());
			}
			_ReqBrick.push_back(parts[0]);
		}
	}
	// season
	_ReqSeason = getProperty(prim, "require_season", true, false);
	// encyclopedia
	_ReqEncyclo = getProperty(prim, "require_encyclo_thema", true, false);
	_ReqEncycloNeg = getProperty(prim, "require_encyclo_thema_neg", true, false);

	if ((!_ReqEncyclo.empty() && !_ReqEncycloNeg.empty())
		|| (!_ReqEncycloNeg.empty() && !_ReqEncyclo.empty()))
	{
		string err = toString("You can't mix positive and negative encyclopedy requirement");
		throw EParseException(prim, err.c_str());
	}
	// event faction
	_ReqEventFaction = getProperty(prim, "require_event_faction", true, false);
}

std::string CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::string &str)
{
	string::size_type pos = 0;
	string::size_type pos2 = 0;
	string ret;

	while (pos < str.size())
	{
		if (str[pos] != '$')
		{
			ret += str[pos++];
		}
		else if (pos+1 < str.size() && str[pos+1] == '$')
		{
			// check that this $ is not escaped
			ret += '$';
			pos+=2;
		}
		else
		{
			// ok, this is not an escaped $
			CSString varName;
			// skip the initial '$'
			pos++;
//			while (str[pos] != ' ' && str[pos] != '\t' && str[pos] != '\n' && str[pos] != '\r')
			while (pos < str.size() && str[pos] != '$')
				varName += str[pos++];

			if (pos >= str.size())
			{
				string err = toString("Error while parsing variable in '%s', missing closing '$'", str.c_str());
				throw EParseException (NULL, err.c_str());
			}

			// skip the final '$'
			pos++;

			// split the var name and subpart
			vector<string> varParts;
			explode(string(varName), string("@"), varParts, true);

			if (varParts.empty() || varParts.size() > 2)
			{
				throw EParseException(prim, toString("Error parsing varName '%s' in string '%s'", varName.c_str(), str.c_str()).c_str());
			}

			if (_Variables.find(varParts.front()) == _Variables.end())
			{
				string err = toString("Unknown variable '%s' in string '%s'", varParts.front().c_str(), str.c_str());
				throw EParseException (prim, err.c_str());
			}

			IVar *var = _Variables[varParts[0]];

			if (varParts.size() == 1)
				ret += var->evalVar("");
			else
				ret += var->evalVar(varParts[1]);

		}
	}

	return ret;
}

std::vector<std::string> CMissionData::replaceVar(NLLIGO::IPrimitive *prim, const std::vector<std::string> &strs)
{
	vector<string> ret;

	for (uint i=0; i<strs.size(); ++i)
	{
		ret.push_back(replaceVar(prim, strs[i]));
	}

	return ret;
}

std::string CMissionData::getProperty(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail)
{
	string ret;
	string *s;
	if (!prim->getPropertyByName(propertyName.c_str(), s))
	{
		if (!canFail)
		{
			string err = toString("Can't find property '%s'", propertyName.c_str());
			throw EParseException (prim, err.c_str());
		}
	}
	else
	{
		ret = *s;
	}

	if (replaceVar)
	{
		ret = this->replaceVar(prim, ret);
	}

	return ret;
}

std::vector<std::string> CMissionData::getPropertyArray(NLLIGO::IPrimitive *prim, const std::string &propertyName, bool replaceVar, bool canFail)
{
	vector<string> ret;
	vector<string> *vs;
	if (!prim->getPropertyByName(propertyName.c_str(), vs))
	{
		if (!canFail)
		{
			string err = toString("Can't find property '%s'", propertyName.c_str());
			throw EParseException (prim, err.c_str());
		}
	}
	else
	{
		ret = *vs;
	}

	if (replaceVar)
	{
		ret = this->replaceVar(prim, ret);
	}
	return ret;
}

bool CMissionData::isThereAJumpTo(const std::string &stepName)
{
	if (_JumpPoints.find(stepName) != _JumpPoints.end())
		return true;
	else
		return false;
}

void TCompilerVarName::init(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string propName)
{
	_DefaultName = defaultName;
	_ParamType = type;

	_VarName = md.getProperty(prim, propName, false, false);
	// remove the variable tag if any
	untagVar(_VarName);

	_VarValue = md.getProperty(prim, propName, true, false);
}

void TCompilerVarName::initWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string &text)
{
	_DefaultName = defaultName;
	_ParamType = type;

	_VarName = text;
	// remove the variable tag if any
	untagVar(_VarName);

	_VarValue = md.replaceVar(prim, text);
}


CPhrase::TParamInfo TCompilerVarName::getParamInfo() const
{
	if (_VarName.empty())
		return CPhrase::TParamInfo(_DefaultName, _ParamType);
	else
		return CPhrase::TParamInfo(_VarName, _ParamType);
}


bool TCompilerVarName::empty() const
{
	return _VarValue.empty();		
}

TCompilerVarName::operator const std::string  () const
{
	return _VarValue;
}

TCompilerVarName::operator CPhrase::TParamInfo() const
{
	return getParamInfo();
}


std::string operator+(const TCompilerVarName& left, const std::string & right) { return left._VarValue + right;}

std::string operator+(const std::string & left, const TCompilerVarName& right) { return left + right._VarValue;}

std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithText(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName)
{
	std::vector<TCompilerVarName> compilerParams;

	std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false);
	uint first = 0;
	uint last = (uint)values.size();
	compilerParams.resize(last);
	for ( ; first != last; ++first) 
	{		
		compilerParams[first].initWithText( toString("%s%d", defaultName.c_str(), first+1) , type, md, prim,  values[first]);			
	}

	return compilerParams;
}

std::vector<TCompilerVarName> TCompilerVarName::getPropertyArrayWithTextStaticDefaultName(const std::string &defaultName, STRING_MANAGER::TParamType type, CMissionData &md, NLLIGO::IPrimitive *prim, const std::string & arrayProperyName)
{
	std::vector<TCompilerVarName> compilerParams;
	std::vector<std::string> values = md.getPropertyArray(prim, arrayProperyName,false, false);
	uint first = 0;
	uint last = (uint)values.size();
	compilerParams.resize(last);
	for ( ; first != last; ++first) 
	{		
		compilerParams[first].initWithText( defaultName, type, md, prim,  values[first]);			
	}
	return compilerParams;
}