// auto-generates spawner entities based on AI entities

#include "StdAfx.h"
#include "GenerateSpawners.h"
#include "IEntitySystem.h"
#include "IScriptSystem.h"

namespace
{
	CString Tabs( int n )
	{
		CString tabs;
		for (int i=0; i<n; i++)
			tabs += '\t';
		return tabs;
	}
	inline const char* to_str( float val )
	{
		static char temp[32];
		sprintf( temp,"%g",val );
		return temp;
	}
}

static bool ShouldGenerateSpawner( IEntityClass * pClass )
{
	const CString startPath = "Scripts/Entities/AI/";
	// things that we're not smart enough to skip
	const CString exceptionClasses[] = {
		"AIAlertness",
		"AIAnchor",
		"SmartObjectCondition"
	};

	CString filename = pClass->GetScriptFile();
	CString classname = pClass->GetName();

	// check the path name is right
	if (filename.IsEmpty())
		return false;
	if (filename.GetLength() < startPath.GetLength())
		return false;
	if (filename.Left(startPath.GetLength()) != startPath)
		return false;

	for (int i=0; i<sizeof(exceptionClasses)/sizeof(*exceptionClasses); i++)
		if (classname == exceptionClasses[i])
			return false;

	// check the table has properties (a good filter)
	SmartScriptTable pTable;
	gEnv->pScriptSystem->GetGlobalValue( pClass->GetName(), pTable );
	if (!pTable)
		return false;
	if (!pTable->HaveValue("Properties"))
		return false;

	return true;
}

static bool OutputFile( const CString& name, const CString& data )
{
	FILE * f = fopen( (const char *)name, "wt" );
	if (!f)
	{
		CryLogAlways("Unable to open file %s", (const char*) name);
		return false;
	}
	fwrite( (const char *)data, data.GetLength(), 1, f );
	fclose(f);
	return true;
}

static bool GenerateEntityForSpawner( IEntityClass * pClass )
{
	CString os;
	os += CString("<Entity Name=\"Spawn") + pClass->GetName() + "\" Script=\"Scripts/Entities/AISpawners/Spawn" + pClass->GetName() + ".lua\"/>\n";
	return OutputFile( Path::GetGameFolder()+CString("\\Entities\\Spawn") + pClass->GetName() + ".ent", os );
}

static void CloneTable( CString& os, SmartScriptTable from, const char * table, int tabs )
{
	SmartScriptTable tbl;
	from->GetValue( table, tbl );
	if (!tbl)
		return;

	os += Tabs(tabs) + table + " =\n"
		+ Tabs(tabs) + "{\n";
	IScriptTable::Iterator iter = tbl->BeginIteration();
	while (tbl->MoveNext(iter))
	{
		if (!iter.sKey)
			continue;
		switch (iter.value.type)
		{
		case ANY_TSTRING:
			os += Tabs(tabs+1) + iter.sKey + " = \"" + iter.value.str + "\",\n";
			break;
		case ANY_TNUMBER:
			os += Tabs(tabs+1) + iter.sKey + " = " + to_str(iter.value.number) + ",\n";
			break;
		case ANY_TTABLE:
			CloneTable( os, tbl, iter.sKey, tabs+1 );
			break;
		}
	}
	tbl->EndIteration(iter);
	os += Tabs(tabs) + "},\n";
}

static bool GenerateLuaForSpawner( IEntityClass * pClass )
{
	CString os;

	SmartScriptTable entityTable;
	gEnv->pScriptSystem->GetGlobalValue( pClass->GetName(), entityTable );

	os += "-- AUTOMATICALLY GENERATED CODE\n";
	os += "-- use sandbox (AI/Generate Spawner Scripts) to regenerate this file\n";
	os += CString("Script.ReloadScript(\"") + pClass->GetScriptFile() + "\")\n";

	os += CString("Spawn") + pClass->GetName() + " =\n";
	os += "{\n";

	os += "\tspawnedEntity = nil,\n";
	CloneTable( os, entityTable, "Properties", 1 );
	CloneTable( os, entityTable, "PropertiesInstance", 1 );

	os += "}\n";

	os += CString("Spawn") + pClass->GetName() + ".Properties.SpawnedEntityName = \"\"\n";

	// get event information
	std::set<CString> inputEvents, outputEvents;
	std::map<CString, CString> eventTypes;
	SmartScriptTable eventsTable;
	entityTable->GetValue( "FlowEvents", eventsTable );
	if (!!eventsTable)
	{
		SmartScriptTable inputs, outputs;
		eventsTable->GetValue("Inputs", inputs);
		eventsTable->GetValue("Outputs", outputs);

		if (!!inputs)
		{
			IScriptTable::Iterator iter = inputs->BeginIteration();
			while (inputs->MoveNext(iter))
			{
				if (!iter.sKey || iter.value.type != ANY_TTABLE)
					continue;
				const char * type;
				if (iter.value.table->GetAt( 2, type ))
				{
					eventTypes[iter.sKey] = type;
					inputEvents.insert(iter.sKey);
				}
			}
			inputs->EndIteration(iter);
		}
		if (!!outputs)
		{
			IScriptTable::Iterator iter = outputs->BeginIteration();
			while (outputs->MoveNext(iter))
			{
				if (!iter.sKey || iter.value.type != ANY_TSTRING)
					continue;
				eventTypes[iter.sKey] = iter.value.str;
				outputEvents.insert(iter.sKey);
			}
			outputs->EndIteration(iter);
		}
	}

	// boiler plate (almost) code
	os += CString("function Spawn") + pClass->GetName() + ":Event_Spawn(sender,params)\n"
		+ "\tlocal params = {\n"
		+ "\t\tclass = \"" + pClass->GetName() + "\",\n"
		+ "\t\tposition = self:GetPos(),\n"
		+ "\t\torientation = self:GetDirectionVector(1),\n"
		+ "\t\tscale = self:GetScale(),\n"
		+ "\t\tproperties = self.Properties,\n"
		+ "\t\tpropertiesInstance = self.PropertiesInstance,\n"
		+ "\t}\n"
		+ "\tif self.Properties.SpawnedEntityName ~= \"\" then\n"
		+ "\t\tparams.name = self.Properties.SpawnedEntityName\n"
		+ "\telse\n"
		+ "\t\tparams.name = self:GetName()\n"
		+ "\tend\n"
		+ "\tlocal ent = System.SpawnEntity(params)\n"
		+ "\tif ent then\n"
		+ "\t\tself.spawnedEntity = ent.id\n"
		+ "\t\tif not ent.Events then ent.Events = {} end\n"
		+ "\t\tlocal evts = ent.Events\n";
	for (std::set<CString>::const_iterator iter = outputEvents.begin(); iter != outputEvents.end(); ++iter)
	{
		// setup event munging...
		os += CString("\t\tif not evts.") + *iter + " then evts." + *iter + " = {} end\n"
			+ "\t\ttable.insert(evts." + *iter + ", {self.id, \"" + *iter + "\"})\n";
	}
	os += CString("\tend\n")
		+ "\tBroadcastEvent(self, \"Spawned\")\n"
		+ "end\n"
		+ "function Spawn" + pClass->GetName() + ":OnReset()\n"
		+ "\tif self.spawnedEntity then\n"
		+ "\t\tSystem.RemoveEntity(self.spawnedEntity)\n"
		+ "\t\tself.spawnedEntity = nil\n"
		+ "\tend\n"
		+ "end\n"
		+ "function Spawn" + pClass->GetName() + ":GetFlowgraphForwardingEntity()\n"
		+ "\treturn self.spawnedEntity\n"
		+ "end\n"
		+ "function Spawn" + pClass->GetName() + ":Event_Spawned()\n"
		+ "\tBroadcastEvent(self, \"Spawned\")\n"
		+ "end\n";

	// output the event information
	for (std::map<CString, CString>::const_iterator iter = eventTypes.begin(); iter != eventTypes.end(); ++iter)
	{
		os += CString("function Spawn") + pClass->GetName() + ":Event_" + iter->first + "(sender, param)\n";
		if (outputEvents.find(iter->first) != outputEvents.end())
			os += CString("\tif sender and sender.id == self.spawnedEntity then BroadcastEvent(self, \"") + iter->first + "\") end\n";
		if (inputEvents.find(iter->first) != inputEvents.end())
			os += CString("\tif self.spawnedEntity and ((not sender) or (self.spawnedEntity ~= sender.id)) then\n")
				+ "\t\tlocal ent = System.GetEntity(self.spawnedEntity)\n"
				+ "\t\tif ent and ent ~= sender then\n"
				+ "\t\t\tself.Handle_" + iter->first + "(ent, sender, param)\n"
				+ "\t\tend\n"
				+ "\tend\n";
		if (iter->first == "OnDeath" || iter->first == "Die")
			os += "\tif sender and sender.id == self.spawnedEntity then self.spawnedEntity = nil end\n";
		os += "end\n";
	}
	os += CString("Spawn") + pClass->GetName() + ".FlowEvents =\n"
		+ "{\n"
		+ "\tInputs = \n"
		+ "\t{\n"
		+ "\t\tSpawn = { Spawn" + pClass->GetName() + ".Event_Spawn, \"bool\" },\n";
	for (std::set<CString>::const_iterator iter = inputEvents.begin(); iter != inputEvents.end(); ++iter)
		os += CString("\t\t") + *iter + " = { Spawn" + pClass->GetName() + ".Event_" + *iter + ", \"" + eventTypes[*iter] + "\" },\n";
	os += CString("\t},\n")
		+ "\tOutputs = \n"
		+ "\t{\n"
		+ "\t\tSpawned = \"bool\",\n";
	for (std::set<CString>::const_iterator iter = outputEvents.begin(); iter != outputEvents.end(); ++iter)
		os += CString("\t\t") + *iter + " = \"" + eventTypes[*iter] + "\",\n";
	os += CString("\t}\n")
		+ "}\n";

	for (std::set<CString>::const_iterator iter = inputEvents.begin(); iter != inputEvents.end(); ++iter)
	{
		os += CString("Spawn") + pClass->GetName() + ".Handle_" + *iter + " = " + pClass->GetName() + ".FlowEvents.Inputs." + *iter + "[1]\n";
	}

	return OutputFile( Path::GetGameFolder()+CString("\\Scripts\\Entities\\AISpawners\\Spawn") + pClass->GetName() + ".lua", os );
}

static bool GenerateSpawner( IEntityClass * pClass )
{
	GenerateEntityForSpawner( pClass );
	return GenerateLuaForSpawner( pClass );
}

void GenerateSpawners()
{
	// iterate over all entity classes, and try to find those that are spawning AI's
	IEntityClass * pClass;
	IEntityClassRegistry * pReg = gEnv->pEntitySystem->GetClassRegistry();
	for (pReg->IteratorMoveFirst(); pClass = pReg->IteratorNext();)
	{
		if (ShouldGenerateSpawner(pClass))
			if (!GenerateSpawner( pClass ))
				CryLogAlways( "Couldn't generate spawner for %s", pClass->GetName() );
	}
}
