/*!
	Model Factory.
	create the additional scenenodes for ( bullets, health... )

	Defines the Entities for Quake3
*/

#include "q3factory.h"
#include "sound.h"

using namespace irr;
using namespace scene;
using namespace gui;
using namespace video;
using namespace core;
using namespace quake3;

//! This list is based on the original quake3.
static const SItemElement Quake3ItemElement [] = {
{	"item_health",
	{"models/powerups/health/medium_cross.md3",
	"models/powerups/health/medium_sphere.md3"},
	"sound/items/n_health.wav",
	"icons/iconh_yellow",
	"25 Health",
	25,
	HEALTH,
	SUB_NONE,
	SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1
},
{	"item_health_large",
	"models/powerups/health/large_cross.md3",
	"models/powerups/health/large_sphere.md3",
	"sound/items/l_health.wav",
	"icons/iconh_red",
	"50 Health",
	50,
	HEALTH,
	SUB_NONE,
	SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1
},
{
	"item_health_mega",
	"models/powerups/health/mega_cross.md3",
	"models/powerups/health/mega_sphere.md3",
	"sound/items/m_health.wav",
	"icons/iconh_mega",
	"Mega Health",
	100,
	HEALTH,
	SUB_NONE,
	SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1
},
{
	"item_health_small",
	"models/powerups/health/small_cross.md3",
	"models/powerups/health/small_sphere.md3",
	"sound/items/s_health.wav",
	"icons/iconh_green",
	"5 Health",
	5,
	HEALTH,
	SUB_NONE,
	SPECIAL_SFX_BOUNCE | SPECIAL_SFX_ROTATE_1
},
{	"ammo_bullets",
	"models/powerups/ammo/machinegunam.md3",
	"",
	"sound/misc/am_pkup.wav",
	"icons/icona_machinegun",
	"Bullets",
	50,
	AMMO,
	MACHINEGUN,
	SPECIAL_SFX_BOUNCE,
},
{
	"ammo_cells",
	"models/powerups/ammo/plasmaam.md3",
	"",
	"sound/misc/am_pkup.wav",
	"icons/icona_plasma",
	"Cells",
	30,
	AMMO,
	PLASMAGUN,
	SPECIAL_SFX_BOUNCE
},
{	"ammo_rockets",
	"models/powerups/ammo/rocketam.md3",
	"",
	"",
	"icons/icona_rocket",
	"Rockets",
	5,
	AMMO,
	ROCKET_LAUNCHER,
	SPECIAL_SFX_ROTATE
},
{
	"ammo_shells",
	"models/powerups/ammo/shotgunam.md3",
	"",
	"sound/misc/am_pkup.wav",
	"icons/icona_shotgun",
	"Shells",
	10,
	AMMO,
	SHOTGUN,
	SPECIAL_SFX_ROTATE
},
{
	"ammo_slugs",
	"models/powerups/ammo/railgunam.md3",
	"",
	"sound/misc/am_pkup.wav",
	"icons/icona_railgun",
	"Slugs",
	10,
	AMMO,
	RAILGUN,
	SPECIAL_SFX_ROTATE
},
{
	"item_armor_body",
	"models/powerups/armor/armor_red.md3",
	"",
	"sound/misc/ar2_pkup.wav",
	"icons/iconr_red",
	"Heavy Armor",
	100,
	ARMOR,
	SUB_NONE,
	SPECIAL_SFX_ROTATE
},
{
	"item_armor_combat",
	"models/powerups/armor/armor_yel.md3",
	"",
	"sound/misc/ar2_pkup.wav",
	"icons/iconr_yellow",
	"Armor",
	50,
	ARMOR,
	SUB_NONE,
	SPECIAL_SFX_ROTATE
},
{
	"item_armor_shard",
	"models/powerups/armor/shard.md3",
	"",
	"sound/misc/ar1_pkup.wav",
	"icons/iconr_shard",
	"Armor Shared",
	5,
	ARMOR,
	SUB_NONE,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_gauntlet",
	"models/weapons2/gauntlet/gauntlet.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_gauntlet",
	"Gauntlet",
	0,
	WEAPON,
	GAUNTLET,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_shotgun",
	"models/weapons2/shotgun/shotgun.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_shotgun",
	"Shotgun",
	10,
	WEAPON,
	SHOTGUN,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_machinegun",
	"models/weapons2/machinegun/machinegun.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_machinegun",
	"Machinegun",
	40,
	WEAPON,
	MACHINEGUN,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_grenadelauncher",
	"models/weapons2/grenadel/grenadel.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_grenade",
	"Grenade Launcher",
	10,
	WEAPON,
	GRENADE_LAUNCHER,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_rocketlauncher",
	"models/weapons2/rocketl/rocketl.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_rocket",
	"Rocket Launcher",
	10,
	WEAPON,
	ROCKET_LAUNCHER,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_lightning",
	"models/weapons2/lightning/lightning.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_lightning",
	"Lightning Gun",
	100,
	WEAPON,
	LIGHTNING,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_railgun",
	"models/weapons2/railgun/railgun.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_railgun",
	"Railgun",
	10,
	WEAPON,
	RAILGUN,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_plasmagun",
	"models/weapons2/plasma/plasma.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_plasma",
	"Plasma Gun",
	50,
	WEAPON,
	PLASMAGUN,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_bfg",
	"models/weapons2/bfg/bfg.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_bfg",
	"BFG10K",
	20,
	WEAPON,
	BFG,
	SPECIAL_SFX_ROTATE
},
{
	"weapon_grapplinghook",
	"models/weapons2/grapple/grapple.md3",
	"",
	"sound/misc/w_pkup.wav",
	"icons/iconw_grapple",
	"Grappling Hook",
	0,
	WEAPON,
	GRAPPLING_HOOK,
	SPECIAL_SFX_ROTATE
},
{
	0
}

};


/*!
*/
const SItemElement * getItemElement ( const stringc& key )
{
	const SItemElement *item = Quake3ItemElement;

	while ( item->key )
	{
		if ( 0 == strcmp ( key.c_str(), item->key ) )
			return item;
		item += 1;
	}
	return 0;
}

/*!
	Quake3 Model Factory.
	Takes the mesh buffers and creates scenenodes for their associated shaders
*/
void Q3ShaderFactory (	Q3LevelLoadParameter &loadParam,
						IrrlichtDevice *device,
						IQ3LevelMesh* mesh,
						eQ3MeshIndex meshIndex,
						ISceneNode *parent,
						IMetaTriangleSelector *meta,
						bool showShaderName )
{
	if ( 0 == mesh || 0 == device )
		return;

	IMeshSceneNode* node = 0;
	ISceneManager* smgr = device->getSceneManager();
	ITriangleSelector * selector = 0;

	// the additional mesh can be quite huge and is unoptimized
	// Save to cast to SMesh
	SMesh * additional_mesh = (SMesh*) mesh->getMesh ( meshIndex );
	if ( 0 == additional_mesh || additional_mesh->getMeshBufferCount() == 0)
		return;

	char buf[128];
	if ( loadParam.verbose > 0 )
	{
		loadParam.startTime = device->getTimer()->getRealTime();
		if ( loadParam.verbose > 1 )
		{
			snprintf(buf, 128, "q3shaderfactory start" );
			device->getLogger()->log( buf, ELL_INFORMATION);
		}
	}

	IGUIFont *font = 0;
	if ( showShaderName )
		font = device->getGUIEnvironment()->getFont("fontlucida.png");

	IVideoDriver *driver = device->getVideoDriver();

	// create helper textures
	if ( 1 )
	{
		tTexArray tex;
		u32 pos = 0;
		getTextures ( tex, "$redimage $blueimage $whiteimage $checkerimage", pos,
								device->getFileSystem(), driver );
	}

	s32 sceneNodeID = 0;
	for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount (); ++i )
	{
		IMeshBuffer *meshBuffer = additional_mesh->getMeshBuffer ( i );
		const SMaterial &material = meshBuffer->getMaterial();

		//! The ShaderIndex is stored in the second material parameter
		s32 shaderIndex = (s32) material.MaterialTypeParam2;

		// the meshbuffer can be rendered without additional support, or it has no shader
		IShader *shader = (IShader *) mesh->getShader ( shaderIndex );

		// no shader, or mapped to existing material
		if ( 0 == shader )
		{

#if 1
			// clone mesh
			SMesh * m = new SMesh ();
			m->addMeshBuffer ( meshBuffer );
			SMaterial &mat = m->getMeshBuffer( 0 )->getMaterial();
			if ( mat.getTexture( 0 ) == 0 )
				mat.setTexture ( 0, driver->getTexture ( "$blueimage" ) );
			if ( mat.getTexture( 1 ) == 0 )
				mat.setTexture ( 1, driver->getTexture ( "$redimage" ) );

			IMesh * store = smgr->getMeshManipulator ()->createMeshWith2TCoords ( m );
			m->drop();

			node = smgr->addMeshSceneNode ( store,  parent, sceneNodeID );
			node->setAutomaticCulling ( scene::EAC_OFF );
			store->drop ();
			sceneNodeID += 1;
#endif
		}
		else if ( 1 )
		{
/*
			stringc s;
			dumpShader ( s, shader );
			printf ( s.c_str () );
*/
			// create sceneNode
			node = smgr->addQuake3SceneNode ( meshBuffer, shader, parent, sceneNodeID );
			node->setAutomaticCulling ( scene::EAC_FRUSTUM_BOX );
			sceneNodeID += 1;
		}

		// show Debug Shader Name
		if ( showShaderName && node )
		{
			IBillboardTextSceneNode* node2 = 0;
			swprintf ( (wchar_t*) buf, 64, L"%hs:%d", node->getName(),node->getID() );
			node2 = smgr->addBillboardTextSceneNode(
					font,
					(wchar_t*) buf,
					node,
					dimension2d<f32>(80.0f, 8.0f),
					vector3df(0, 10, 0),
					sceneNodeID
					);
			snprintf ( buf, 64, "%s:%d", node->getName(),node->getID() );
			//node2->setName ( buf );
			sceneNodeID += 1;
		}

		// create Portal Rendertargets
		if ( shader )
		{
			const SVarGroup *group = shader->getGroup(1);
			if ( group->isDefined( "surfaceparm", "portal" ) )
			{
			}

		}


		// add collision
		// find out if shader is marked als nonsolid
		u8 doCreate = meta !=0  ;

		if ( shader )
		{
			const SVarGroup *group = shader->getGroup(1);
			if (	group->isDefined( "surfaceparm", "trans" )
					// || group->isDefined( "surfaceparm", "sky" )
					// || group->isDefined( "surfaceparm", "nonsolid" )
				)
			{
				if ( !group->isDefined( "surfaceparm", "metalsteps" ) )
				{
					doCreate = 0;
				}
			}
		}

		if ( doCreate )
		{
			IMesh *m = 0;

			//! controls if triangles are modified by the scenenode during runtime
			bool takeOriginal = true;

			if ( takeOriginal )
			{
				m = new SMesh ();
				((SMesh*) m )->addMeshBuffer (meshBuffer);
			}
			else
			{
				m = node->getMesh();
			}

			//selector = smgr->createOctreeTriangleSelector ( m, 0, 128 );
			selector = smgr->createTriangleSelector ( m, 0 );
			meta->addTriangleSelector ( selector );
			selector->drop ();

			if ( takeOriginal )
			{
				delete m;
			}
		}

	}

#if 0
	if ( meta )
	{
		selector = smgr->createOctreeTriangleSelector ( additional_mesh, 0 );
		meta->addTriangleSelector ( selector );
		selector->drop ();
	}
#endif

	if ( loadParam.verbose > 0 )
	{
		loadParam.endTime = device->getTimer()->getRealTime ();
		snprintf(buf, 128, "q3shaderfactory needed %04d ms to create %d shader nodes",
			loadParam.endTime - loadParam.startTime,
			sceneNodeID
			);
		device->getLogger()->log(buf, ELL_INFORMATION);
	}

}


/*!
	create Items from Entity
*/
void Q3ModelFactory (	Q3LevelLoadParameter &loadParam,
						IrrlichtDevice *device,
						IQ3LevelMesh* masterMesh,
						ISceneNode *parent,
						bool showShaderName
						)
{
	if ( 0 == masterMesh )
		return;

	tQ3EntityList &entity = masterMesh->getEntityList ();
	ISceneManager* smgr = device->getSceneManager();


	char buf[128];
	const SVarGroup *group;
	IEntity search;
	s32 index;
	s32 lastIndex;

/*
	stringc s;
	FILE *f = 0;
	f = fopen ( "entity.txt", "wb" );
	for ( index = 0; (u32) index < entityList.size (); ++index )
	{
		const IEntity *entity = &entityList[ index ];
		s = entity->name;
		dumpShader ( s, entity );
		fwrite ( s.c_str(), 1, s.size(), f );
	}
	fclose ( f );
*/
	IAnimatedMeshMD3* model;
	SMD3Mesh * mesh;
	const SMD3MeshBuffer *meshBuffer;
	IMeshSceneNode* node;
	ISceneNodeAnimator* anim;
	const IShader *shader;
	u32 pos;
	vector3df p;
	u32 nodeCount = 0;
	tTexArray textureArray;

	IGUIFont *font = 0;
	if ( showShaderName )
		font = device->getGUIEnvironment()->getFont("fontlucida.png");

	const SItemElement *itemElement;

	// walk list
	for ( index = 0; (u32) index < entity.size(); ++index )
	{
		itemElement = getItemElement ( entity[index].name );
		if ( 0 == itemElement )
			continue;

		pos = 0;
		p = getAsVector3df ( entity[index].getGroup(1)->get ( "origin" ), pos );

		nodeCount += 1;
		for ( u32 g = 0; g < 2; ++g )
		{
			if ( 0 == itemElement->model[g] || itemElement->model[g][0] == 0 )
				continue;
			model = (IAnimatedMeshMD3*) smgr->getMesh( itemElement->model[g] );
			if ( 0 == model )
				continue;

			mesh = model->getOriginalMesh();
			for ( u32 j = 0; j != mesh->Buffer.size (); ++j )
			{
				meshBuffer = mesh->Buffer[j];
				if ( 0 == meshBuffer )
					continue;

				shader = masterMesh->getShader ( meshBuffer->Shader.c_str(), false );
				IMeshBuffer *final = model->getMesh(0)->getMeshBuffer(j);
				if ( shader )
				{
					//!TODO: Hack don't modify the vertexbuffer. make it better;-)
					final->getMaterial().ColorMask = 0;
					node = smgr->addQuake3SceneNode ( final, shader, parent );
					final->getMaterial().ColorMask = 15;
				}
				else
				{
					// clone mesh
					SMesh * m = new SMesh ();
					m->addMeshBuffer ( final );
					node = smgr->addMeshSceneNode ( m,  parent );
					m->drop();
				}

				if ( 0 == node )
				{
					snprintf ( buf, 128, "q3ModelFactory shader %s failed", meshBuffer->Shader.c_str() );
					device->getLogger()->log ( buf );
					continue;
				}

				// node was maybe centered by shaderscenenode
				node->setPosition ( p );
				node->setName ( meshBuffer->Shader );
				node->setAutomaticCulling ( scene::EAC_BOX );

				// add special effects to node
				if (	itemElement->special & SPECIAL_SFX_ROTATE ||
						(g == 0 && itemElement->special & SPECIAL_SFX_ROTATE_1)
					)
				{
					anim = smgr->createRotationAnimator ( vector3df ( 0.f,
						2.f, 0.f ) );
					node->addAnimator ( anim );
					anim->drop ();
				}

				if ( itemElement->special & SPECIAL_SFX_BOUNCE )
				{
					//anim = smgr->createFlyStraightAnimator (
					//	p, p + vector3df ( 0.f, 60.f, 0.f ), 1000, true, true );
					anim = smgr->createFlyCircleAnimator (
						p + vector3df( 0.f, 20.f, 0.f ),
						20.f,
						0.005f,
						vector3df ( 1.f, 0.f, 0.f ),
						core::fract ( nodeCount * 0.05f ),
						1.f
						);
					node->addAnimator ( anim );
					anim->drop ();
				}
			}
		}
		// show name
		if ( showShaderName )
		{
			IBillboardTextSceneNode* node2 = 0;
			swprintf ( (wchar_t*) buf, sizeof(buf) / 2, L"%hs", itemElement->key );
			node2 = smgr->addBillboardTextSceneNode(
					font,
					(wchar_t*) buf,
					parent,
					dimension2d<f32>(80.0f, 8.0f),
					p + vector3df(0, 30, 0),
					0
					);
		}

	}

	// music
	search.name = "worldspawn";
	index = entity.binary_search_multi ( search, lastIndex );

	if ( index >= 0 )
	{
		group = entity[ index ].getGroup(1);
		background_music ( group->get ( "music" ).c_str () );
	}

	// music
	search.name = "worldspawn";
	index = entity.binary_search_multi ( search, lastIndex );

	if ( index >= 0 )
	{
		group = entity[ index ].getGroup(1);
		background_music ( group->get ( "music" ).c_str () );
	}

	//IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
	//IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );

}

/*!
	so we need a good starting Position in the level.
	we can ask the Quake3 Loader for all entities with class_name "info_player_deathmatch"
*/
s32 Q3StartPosition (	IQ3LevelMesh* mesh,
						ICameraSceneNode* camera,
						s32 startposIndex,
						const vector3df &translation
					)
{
	if ( 0 == mesh )
		return 0;

	tQ3EntityList &entityList = mesh->getEntityList ();

	IEntity search;
	search.name = "info_player_start";	// "info_player_deathmatch";

	// find all entities in the multi-list
	s32 lastIndex;
	s32 index = entityList.binary_search_multi ( search, lastIndex );

	if ( index < 0 )
	{
		search.name = "info_player_deathmatch";
		index = entityList.binary_search_multi ( search, lastIndex );
	}

	if ( index < 0 )
		return 0;

	index += core::clamp ( startposIndex, 0, lastIndex - index );

	u32 parsepos;

	const SVarGroup *group;
	group = entityList[ index ].getGroup(1);

	parsepos = 0;
	vector3df pos = getAsVector3df ( group->get ( "origin" ), parsepos );
	pos += translation;

	parsepos = 0;
	f32 angle = getAsFloat ( group->get ( "angle"), parsepos );

	vector3df target ( 0.f, 0.f, 1.f );
	target.rotateXZBy ( angle - 90.f, vector3df () );

	if ( camera )
	{
		camera->setPosition ( pos );
		camera->setTarget ( pos + target );
		//! New. FPSCamera and animators catches reset on animate 0
		camera->OnAnimate ( 0 );
	}
	return lastIndex - index + 1;
}


/*!
	gets a accumulated force on a given surface
*/
vector3df getGravity ( const c8 * surface )
{
	if ( 0 == strcmp ( surface, "earth" ) ) return vector3df ( 0.f, -90.f, 0.f );
	if ( 0 == strcmp ( surface, "moon" ) ) return vector3df ( 0.f, -6.f / 100.f, 0.f );
	if ( 0 == strcmp ( surface, "water" ) ) return vector3df ( 0.1f / 100.f, -2.f / 100.f, 0.f );
	if ( 0 == strcmp ( surface, "ice" ) ) return vector3df ( 0.2f / 100.f, -9.f / 100.f, 0.3f / 100.f );

	return vector3df ( 0.f, 0.f, 0.f );
}



/*
	Dynamically load the Irrlicht Library
*/

#if defined(_IRR_WINDOWS_API_)
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

#include <windows.h>

funcptr_createDevice load_createDevice ( const c8 * filename)
{
	return (funcptr_createDevice) GetProcAddress ( LoadLibrary ( filename ), "createDevice" );
}

funcptr_createDeviceEx load_createDeviceEx ( const c8 * filename)
{
	return (funcptr_createDeviceEx) GetProcAddress ( LoadLibrary ( filename ), "createDeviceEx" );
}

#else

// TODO: Dynamic Loading for other os
funcptr_createDevice load_createDevice ( const c8 * filename)
{
	return createDevice;
}

funcptr_createDeviceEx load_createDeviceEx ( const c8 * filename)
{
	return createDeviceEx;
}

#endif

/*
	get the current collision respone camera animator
*/
ISceneNodeAnimatorCollisionResponse* camCollisionResponse( IrrlichtDevice * device )
{
	ICameraSceneNode *camera = device->getSceneManager()->getActiveCamera();
	ISceneNodeAnimatorCollisionResponse *a = 0;

	list<ISceneNodeAnimator*>::ConstIterator it = camera->getAnimators().begin();
	for (; it != camera->getAnimators().end(); ++it)
	{
		a = (ISceneNodeAnimatorCollisionResponse*) (*it);
		if ( a->getType() == ESNAT_COLLISION_RESPONSE )
			return a;
	}

	return 0;
}


//! internal Animation
void setTimeFire ( TimeFire *t, u32 delta, u32 flags )
{
	t->flags = flags;
	t->next = 0;
	t->delta = delta;
}


void checkTimeFire ( TimeFire *t, u32 listSize, u32 now )
{
	u32 i;
	for ( i = 0; i < listSize; ++i )
	{
		if ( now < t[i].next )
			continue;

		t[i].next = core::max_ ( now + t[i].delta, t[i].next + t[i].delta );
		t[i].flags |= FIRED;
	}
}
