/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
$Id$
$DateTime$

-------------------------------------------------------------------------
History:
- 18:10:2005   18:00 : Created by Márcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "Game.h"
#include <IEntitySystem.h>
#include <ICryPak.h>
#include <IScriptSystem.h>
#include "IGameObject.h"
#include "Actor.h"
#include "ItemParams.h"
#include "WeaponSystem.h"

#include "Projectile.h"
#include "Bullet.h"
#include "Rocket.h"
#include "HomingMissile.h"
#include "TagBullet.h"

#include "Single.h"
#include "Automatic.h"
#include "Burst.h"
#include "Rapid.h"
#include "Throw.h"
#include "Plant.h"
#include "Detonate.h"
#include "FreezingBeam.h"
#include "Charge.h"
#include "Shotgun.h"
#include "Melee.h"


#include "IronSight.h"
#include "Scope.h"


template <typename T, typename R> R *CreateIt() { return new T(); };



#define REGISTER_PROJECTILE(name, T)	\
struct C##name##Creator : public IGameObjectExtensionCreatorBase	\
{ \
	T *Create() \
	{ \
		return new T(); \
	} \
	void GetGameObjectExtensionRMIData( void ** ppRMI, size_t * nCount ) \
	{ \
		T::GetGameObjectExtensionRMIData( ppRMI, nCount ); \
	} \
}; \
static C##name##Creator _##name##Creator; \
RegisterProjectile(#name, &_##name##Creator); \


//------------------------------------------------------------------------
CWeaponSystem::CWeaponSystem(CGame *pGame, ISystem *pSystem)
: m_pGame(pGame),
	m_pSystem(pSystem),
	m_pItemSystem(pGame->GetIGameFramework()->GetIItemSystem())
{
	// register fire modes here
	RegisterFireMode("Single", &CreateIt<CSingle, IFireMode>);
	RegisterFireMode("Automatic", &CreateIt<CAutomatic, IFireMode>);
	RegisterFireMode("Burst", &CreateIt<CBurst, IFireMode>);
	RegisterFireMode("Rapid", &CreateIt<CRapid, IFireMode>);
	RegisterFireMode("Throw", &CreateIt<CThrow, IFireMode>);
	RegisterFireMode("Plant", &CreateIt<CPlant, IFireMode>);
	RegisterFireMode("Detonate", &CreateIt<CDetonate, IFireMode>);
	RegisterFireMode("FreezingBeam", &CreateIt<CFreezingBeam, IFireMode>);
	RegisterFireMode("Charge", &CreateIt<CCharge, IFireMode>);
	RegisterFireMode("Shotgun", &CreateIt<CShotgun, IFireMode>);
	RegisterFireMode("Melee", &CreateIt<CMelee, IFireMode>);
  
	// register zoom modes here
	RegisterZoomMode("IronSight", &CreateIt<CIronSight, IZoomMode>);
	RegisterZoomMode("Scope", &CreateIt<CScope, IZoomMode>);

	// register projectile classes here
	REGISTER_PROJECTILE(Projectile, CProjectile);
	REGISTER_PROJECTILE(Bullet, CBullet);
	REGISTER_PROJECTILE(Rocket, CRocket);
  REGISTER_PROJECTILE(HomingMissile, CHomingMissile);
	REGISTER_PROJECTILE(TagBullet, CTagBullet);

  RegisterCVars();
}

//------------------------------------------------------------------------
CWeaponSystem::~CWeaponSystem()
{
	// cleanup current projectiles
	for (TProjectileMap::iterator pit = m_projectiles.begin(); pit != m_projectiles.end(); pit++)
		GetISystem()->GetIEntitySystem()->RemoveEntity(pit->first, true);

	for (TAmmoTypeParams::iterator it = m_ammoparams.begin(); it != m_ammoparams.end(); it++)
		it->second->Release();
}

//------------------------------------------------------------------------
void CWeaponSystem::RegisterCVars()
{
  IConsole* pConsole = GetISystem()->GetIConsole();
  
  pConsole->RegisterString("i_debuggun_1", "ai_stats_target", VF_DUMPTODISK, "Command to execute on primary DebugGun fire");
  pConsole->RegisterString("i_debuggun_2", "ag_debug", VF_DUMPTODISK, "Command to execute on secondary DebugGun fire");
}


//------------------------------------------------------------------------
bool CWeaponSystem::Init()
{
	return false;
}

//------------------------------------------------------------------------
void CWeaponSystem::Update(float frameTime)
{
	m_tracerManager.Update(frameTime);
}

//------------------------------------------------------------------------
void CWeaponSystem::Release()
{
	delete this;
}

//------------------------------------------------------------------------
IFireMode *CWeaponSystem::CreateFireMode(const char *name)
{
	TFireModeRegistry::iterator it = m_fmregistry.find(name);
	if (it != m_fmregistry.end())
		return it->second();
	return 0;
}

//------------------------------------------------------------------------
void CWeaponSystem::RegisterFireMode(const char *name, IFireMode *(*CreateProc)())
{
	m_fmregistry.insert(TFireModeRegistry::value_type(name, CreateProc));
}

//------------------------------------------------------------------------
IZoomMode *CWeaponSystem::CreateZoomMode(const char *name)
{
	TZoomModeRegistry::iterator it = m_zmregistry.find(name);
	if (it != m_zmregistry.end())
		return it->second();
	return 0;
}

//------------------------------------------------------------------------
void CWeaponSystem::RegisterZoomMode(const char *name, IZoomMode *(*CreateProc)())
{
	m_zmregistry.insert(TZoomModeRegistry::value_type(name, CreateProc));
}

//------------------------------------------------------------------------
CProjectile *CWeaponSystem::SpawnAmmo(const char *ammoName)
{
	TAmmoTypeParams::iterator it = m_ammoparams.find(ammoName);
	if (it == m_ammoparams.end())
	{
		GameWarning("Failed to spawn ammo '%s'! Unknown class...", ammoName);
		return 0;
	}

	IEntityClass *pClass = GetISystem()->GetIEntitySystem()->GetClassRegistry()->FindClass(ammoName);
	if (!pClass)
	{
		GameWarning("Failed to spawn ammo '%s'! Entity class not registered...", ammoName);
		return 0;
	}

	// verify spawn flags
	uint flags=0;
	int serverSpawn=0;
	const IItemParamsNode *params = it->second;
	const IItemParamsNode *flagsNode = params->GetChild("flags");
	if (flagsNode)
	{
		bool param;
		CItemParamReader reader(flagsNode);
		reader.Read("ClientOnly", param); flags |= param?ENTITY_FLAG_CLIENT_ONLY:0; param=0;
		reader.Read("ServerOnly", param); flags |= param?ENTITY_FLAG_SERVER_ONLY:0; param=0;
		reader.Read("ServerSpawn", serverSpawn);
	}

	if (serverSpawn && !m_pGame->GetIGameFramework()->IsServer())
		return 0;

	SEntitySpawnParams spawnParams;
	spawnParams.pClass = pClass;
	spawnParams.sName = "";
	spawnParams.nFlags = flags | ENTITY_FLAG_NO_PROXIMITY; // No proximity for this entity.

	IEntity *pEntity = GetISystem()->GetIEntitySystem()->SpawnEntity(spawnParams);
	if (!pEntity)
	{
		GameWarning("Failed to spawn ammo '%s'! Entity creation failed...", ammoName);
		return 0;
	}

	return GetProjectile(pEntity->GetId());
}

//-------------------------------------------	-----------------------------
void CWeaponSystem::RegisterProjectile(const char *name, IGameObjectExtensionCreatorBase *pCreator)
{
	m_projectileregistry.insert(TProjectileRegistry::value_type(name, pCreator));
}

//------------------------------------------------------------------------
const IItemParamsNode *CWeaponSystem::GetAmmoParams(const char *ammoName) const
{
	TAmmoTypeParams::const_iterator it = m_ammoparams.find(ammoName);
	if (it != m_ammoparams.end())
		return it->second;
	return 0;
}

//------------------------------------------------------------------------
void CWeaponSystem::AddProjectile(IEntity *pEntity, CProjectile *pProjectile)
{
	m_projectiles.insert(TProjectileMap::value_type(pEntity->GetId(), pProjectile));
}

//------------------------------------------------------------------------
void CWeaponSystem::RemoveProjectile(CProjectile *pProjectile)
{
	m_projectiles.erase(pProjectile->GetEntity()->GetId());
}

//------------------------------------------------------------------------
CProjectile *CWeaponSystem::GetProjectile(EntityId entityId)
{
	TProjectileMap::iterator it = m_projectiles.find(entityId);
	if (it != m_projectiles.end())
		return it->second;
	return 0;
}

//------------------------------------------------------------------------
void CWeaponSystem::Scan(const char *folderName)
{
	string folder = folderName;
	string search = folder;
	search += "/*.*";

	ICryPak *pPak = m_pSystem->GetIPak();

	_finddata_t fd;
	intptr_t handle = pPak->FindFirst(search.c_str(), &fd);

	CryLog("Loading item XML definitions from '%s'!", folderName);

	if (handle > -1)
	{
		do
		{
			if (!strcmp(fd.name, ".") || !strcmp(fd.name, ".."))
				continue;

			if (fd.attrib & _A_SUBDIR)
			{
				string subName = folder+"/"+fd.name;
				Scan(subName.c_str());
				continue;
			}

			if (stricmp(PathUtil::GetExt(fd.name).c_str(), "xml"))
				continue;

			string xmlFile = folder + string("/") + string(fd.name);
			XmlNodeRef rootNode = m_pSystem->LoadXmlFile(xmlFile.c_str());

			if (!rootNode)
			{
				GameWarning("Invalid XML file '%s'! Skipping...", xmlFile.c_str());
				continue;
			}

			if (!ScanXML(rootNode, xmlFile.c_str()))
				continue;

		} while (pPak->FindNext(handle, &fd) >= 0);
	}

	CryLog("Finished loading item XML definitions from '%s'!", folderName);
}

//------------------------------------------------------------------------
bool CWeaponSystem::ScanXML(XmlNodeRef &root, const char *xmlFile)
{
	if (strcmpi(root->getTag(), "ammo"))
		return false;

	const char *name = root->getAttr("name");
	if (!name)
	{
		GameWarning("Missing ammo name in XML '%'! Skipping...", xmlFile);
		return false;
	}

	const char *className = root->getAttr("class");

	if (!className)
	{
		GameWarning("Missing ammo class in XML '%s'! Skipping...", xmlFile);
		return false;
	}

	TProjectileRegistry::iterator it = m_projectileregistry.find(className);
	if (it == m_projectileregistry.end())
	{
		GameWarning("Unknown ammo class '%s' specified in XML '%s'! Skipping...", className, xmlFile);
		return false;
	}

	const char *scriptName = root->getAttr("script");
	IEntityClassRegistry::SEntityClassDesc classDesc;
	classDesc.sName = name;
	classDesc.sScriptFile = scriptName?scriptName:"";
	//classDesc.pUserProxyData = (void *)it->second;
	//classDesc.pUserProxyCreateFunc = &CreateProxy<CProjectile>;
	classDesc.flags |= ECLF_INVISIBLE;

	m_pGame->GetIGameFramework()->GetIGameObjectSystem()->RegisterExtension(name, it->second, &classDesc);

	IItemParamsNode *params = m_pItemSystem->CreateParams();
	params->ConvertFromXML(root);

	m_ammoparams.insert(TAmmoTypeParams::value_type(name, params));

	return true;
}

//------------------------------------------------------------------------
void CWeaponSystem::DebugGun(IConsoleCmdArgs *args)
{
  IGameFramework* pGF = GetISystem()->GetIGame()->GetIGameFramework();  
  IItemSystem* pItemSystem = pGF->GetIItemSystem();

  const char* debugGunClass = "DebugGun";     
  
  IActor* pActor = pGF->GetClientActor();
  if (!pActor || !pActor->IsPlayer())
    return;

  IInventory *pInventory = static_cast<IInventory *>(pActor->GetGameObject()->QueryExtension("Inventory"));
  if (!pInventory)
    return;  
  
  // give & select the debuggun 
  EntityId itemId = pInventory->GetItemByClass(debugGunClass);
  if (0 == itemId)        
  {
    // if actor doesn't have it, only give it in editor
    if (!GetISystem()->IsEditor())
      return;

    itemId = pItemSystem->GiveItem(pActor, debugGunClass, false, true, true);
  }
  pItemSystem->SetActorItem(pActor, itemId, true);   
     
}

//------------------------------------------------------------------------
void CWeaponSystem::RefGun(IConsoleCmdArgs *args)
{
	IGameFramework* pGF = GetISystem()->GetIGame()->GetIGameFramework();  
	IItemSystem* pItemSystem = pGF->GetIItemSystem();

	const char* refGunClass = "ReferenceWeapon";

	IActor* pActor = pGF->GetClientActor();
	if (!pActor || !pActor->IsPlayer())
		return;

	IInventory *pInventory = static_cast<IInventory *>(pActor->GetGameObject()->QueryExtension("Inventory"));
	if (!pInventory)
		return;  

	// give & select the refgun 
	EntityId itemId = pInventory->GetItemByClass(refGunClass);
	if (0 == itemId)        
	{
		// if actor doesn't have it, only give it in editor
		if (!GetISystem()->IsEditor())
			return;

		itemId = pItemSystem->GiveItem(pActor, refGunClass, false, true, true);
	}
	pItemSystem->SetActorItem(pActor, itemId, true);   

}

