//////////////////////////////////////////////////////////////////////////////
//
// File: AVP_Actor_PickupHandler.cpp
//
// (AJK - 02.09.2008) Created
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Actor/AVP_Actor_PickupHandler.h"

// ASURA
#include "Core/Asura_HashedList.h"
#include "Network/Asura_Message.h"

// AVP
#include "Actor/AVP_Actor_InputHandler.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ClientEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_ClientActor_Vertebrate.h"
#include "Entity/PhysicalObject/Pickup/AVP_ClientEntity_Pickup.h"
#include "Entity/PhysicalObject/Pickup/AVP_Pickup.h"
#include "Entity/PhysicalObject/Pickup/AVP_Pickup_Ammo.h"
#include "Entity/PhysicalObject/Pickup/AVP_Pickup_Messages.h"
#include "Entity/PhysicalObject/Pickup/AVP_Pickup_Weapon.h"
#include "Entity/PhysicalObject/Pickup/AVP_ServerEntity_Pickup.h"
#include "Entity/PlayerInfo/AVP_ServerEntity_PlayerInfo.h"
#include "Inventory/AVP_AmmoStore.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/AVP_Inventory_Group.h"
#include "Inventory/AVP_Inventory_Item.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_InventoryItem_SubType.h"

// Debug vars
DEBUGVAR float  dbg_fLagTolerance = 1.02f;

AVP_Actor_PickupHandler::AVP_Actor_PickupHandler(Asura_Guid uActorGuid)
: m_uActorGuid(uActorGuid)
, m_uNearestPickupGuid(ASURA_GUID_UNREGISTERED)
, m_bHasRequestedPickup( false )
, m_bOverrideAimDirectionUsage( false )
{
}

void AVP_Actor_PickupHandler::UpdateServer()
{
	Asura_Hashed_List_It<AVP_ServerEntity_Pickup> xPickupIter(AVP_ServerEntity_Pickup::GetList());
	AVP_ServerEntity_Actor* const pxOwner = AVP_ServerEntity_Actor::FindEntity(m_uActorGuid);
	if(pxOwner)
	{
		AVP_ServerEntity_Pickup* pxNearestPickup       = NULL;
		AVP_ServerEntity_Pickup* pxCurrentPickup       = NULL;
		const AVP_Pickup*              pxPickupObject  = NULL;
		const AVP_Inventory_System*    pxInventory     = pxOwner->GetInventorySystem();
		if(pxInventory)
		{
			float fBestColDistSqrd = Asura_Maths::LargeFloat;
			float fThisColDistSqrd;

			const AVP_Settings::Species eOwnerSpecies = pxOwner->GetSpeciesByClassification();

			// Loop through all pickups
			for ( ; !xPickupIter.Done(); xPickupIter.Next() )
			{
				pxCurrentPickup = xPickupIter.GetCurrent();
				if(pxCurrentPickup)
				{
					pxPickupObject  = pxCurrentPickup->GetPickupObject();
					if(pxPickupObject)
					{
						if(pxPickupObject->GetPickupType() == AVP_Pickup::PICKUP_TYPE_TOOL) continue; // Tools MUST be prompted

						// fThisColDistSqrd get the current collection distance from CanCollect, and if CanCollect returns true, it is compared with the current nearest pickup distance
						if (   ( !pxCurrentPickup->IsHidden() )
							&& ( pxCurrentPickup->IsEnabled() )
							&& ( pxPickupObject->GetAllegiance() == eOwnerSpecies ) 
							&& ( AVP_Actor_PickupHandler::CanCollect( *pxCurrentPickup, *pxOwner, fThisColDistSqrd ) ) 
							&& ( fThisColDistSqrd < fBestColDistSqrd )   
							&& ( !IsThisPickupStoreFull(*pxPickupObject, *pxInventory) )	 )
						{
							fBestColDistSqrd = fThisColDistSqrd;
							pxNearestPickup  = pxCurrentPickup;
						}
					}
					else
					{
						Asura_Assert(pxOwner, "In Pickup handler, UpdateServer, where is pxPickupObject?");
					}
				}
				else
				{
					Asura_Assert(pxOwner, "In Pickup handler, UpdateServer, where is pxCurrentPickup?");
				}
			}

			// Send message if there a pickup
			if(pxNearestPickup)
			{
				OnPickup(pxOwner, pxNearestPickup);
			}
		}
		else
		{
			Asura_Assert(pxOwner, "In Pickup handler, UpdateServer, where is pxInventory?");
		}
	}
	else
	{
		Asura_Assert(pxOwner, "In Pickup handler, UpdateServer, where is pxOwner?");
	}
}

void AVP_Actor_PickupHandler::OnPickup(AVP_ServerEntity_Actor* pxOwner, AVP_ServerEntity_Pickup* pxPickup, bool bSetAsCurrent /* defaults to true */) 
{
	if(pxOwner && pxPickup)
	{
		float fQuantity = 1.0f;
		float fSecondaryQuantity = 1.0f;

		if( pxPickup->GetPickupObject()->GetGroupHash() == uAVP_INVENTORY_GROUPHASH_AMMO)
		{
			// Get or calculate the quantity required
			if (pxPickup->IsFixedQuantity())
			{
				fQuantity = static_cast<float>(pxPickup->GetQuantity());
			}
			else
			{
				// Get the player info server entity
				const AVP_ServerEntity_PlayerInfo* const pxPlayerInfo = AVP_ServerEntity_PlayerInfo::FindEntity(pxOwner->GetPlayerInfoGuid());

				if(pxPlayerInfo == NULL)
				{
					Asura_Assert(false, "Pickup Handler, player info");
				}
				else
				{
					// We are sure here to have a AVP_Pickup_Ammo type; so down cast is OK.
					fQuantity = AVP_AmmoStore::GetIntelligentAmmoQuantity(pxPlayerInfo->GetIntelligentAmount(), *(static_cast<const AVP_Pickup_Ammo*> (pxPickup->GetPickupObject())));  
				}
			}
		}
		else if( pxPickup->GetPickupObject()->GetGroupHash() == uAVP_INVENTORY_GROUPHASH_WEAPON)
		{
			if(pxPickup->IsDroppedByAI())
			{
				const AVP_ServerEntity_PlayerInfo* const pxPlayerInfo = AVP_ServerEntity_PlayerInfo::FindEntity(pxOwner->GetPlayerInfoGuid());

				fQuantity = AVP_AmmoStore::GetIntelligentAmmoQuantity(pxPlayerInfo->GetIntelligentAmount(), *pxPickup->GetPickupObject());  
				fSecondaryQuantity = 0.0f; // Cannot pickup secondary ammo from random drops.
			}
			else
			{
				fQuantity = static_cast<float>(pxPickup->GetQuantity());
				fSecondaryQuantity = static_cast<float>(pxPickup->GetSecondaryQuantity());
			}
		}

		SendCollectMessage(pxOwner, pxPickup, fQuantity, fSecondaryQuantity, bSetAsCurrent);

		// Hide or destroy this pickup
		pxOwner->OnPickup(pxPickup, fQuantity, fSecondaryQuantity, bSetAsCurrent);
		pxPickup->OnPickup();
	}
	else
	{
		Asura_Assert(pxPickup, "In Pickup handler, OnPickup, where is pxOwner or pxPickup?");
	}
}

bool AVP_Actor_PickupHandler::CanCurrentWeaponBeDropped(void) const
{
	const AVP_ClientEntity_Actor*  const pxOwner = AVP_ClientEntity_Actor::FindEntity(m_uActorGuid);
	if(pxOwner)
	{
		const AVP_Inventory_System* pxSystem = pxOwner->GetInventorySystem();
		const AVP_Inventory_Group* pxGroup = (pxSystem)?pxSystem->GetGroup(uAVP_INVENTORY_GROUPHASH_WEAPON):NULL;
		const AVP_Inventory_Item* pxItem = (pxGroup)?pxGroup->GetCurrentInventoryItem():NULL;
		if(pxItem)
		{
			if(pxItem->GetFixedSlot() != -1)
			{
				return false;
			}
		}
	}
	return true;
}

bool AVP_Actor_PickupHandler::IsSpaceForNewWeapon(void) const
{
	const AVP_ClientActor_Vertebrate*  const pxOwner = AVP_ClientActor_Vertebrate::FindEntity(m_uActorGuid);
	if(pxOwner)
	{
		const AVP_Inventory_System* pxSystem = pxOwner->GetInventorySystem();
		const AVP_Inventory_Group* pxGroup = (pxSystem)?pxSystem->GetGroup(uAVP_INVENTORY_GROUPHASH_WEAPON):NULL;
		if(pxGroup && pxGroup->GetNumItems() < pxOwner->GetMaxWeapons() && !pxGroup->HasDualSlotWeapon())
		{
			return true;
		}
	}
	return false;
}

void AVP_Actor_PickupHandler::SendCollectMessage(const AVP_ServerEntity_Actor* pxOwner, AVP_ServerEntity_Pickup* pxPickup, float fQuantity, float fSecondaryQuantity, bool bSetAsCurrent /* defaults to true */)
{
	// Server checked for auto picks and send collect message to client
	Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_COLLECT_PICKUP, sizeof(AVP_Message_Pickup_Collect));
	if( pxMessage )
	{
		AVP_Message_Pickup_Collect*   pxData = static_cast<AVP_Message_Pickup_Collect*>(pxMessage->GetData());

		pxData->FillInfo(pxPickup->GetGuid(), pxPickup->GetPickupBlueprintHash(), Asura_Maths::FloatToInt(fQuantity), bSetAsCurrent, Asura_Maths::FloatToInt(fSecondaryQuantity));
		pxMessage->SetFromGuid( pxOwner->GetGuid() );
		pxMessage->SetToGuid( ASURA_GUID_MY_CLIENT_ENTITIES );
		Asura_Network::Send(pxMessage);
	}
}

void AVP_Actor_PickupHandler::SendRequestMessage()
{
	const AVP_ClientEntity_Actor*  const pxOwner         = AVP_ClientEntity_Actor::FindEntity(m_uActorGuid);
	const AVP_ClientEntity_Pickup* const pxNearestPickup = AVP_ClientEntity_Pickup::FindEntity(m_uNearestPickupGuid);

	if( pxOwner && pxNearestPickup)
	{
		// Send the request to collect to the server
		Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_REQUEST_PICKUP, sizeof(AVP_Message_Pickup_RequestCollection));
		if( pxMessage )
		{
			AVP_Message_Pickup_RequestCollection* pxData  = static_cast<AVP_Message_Pickup_RequestCollection*>(pxMessage->GetData());

			pxData->FillInfo(pxNearestPickup->GetGuid());
			pxMessage->SetFromGuid( pxOwner->GetGuid() );
			pxMessage->SetToGuid  ( pxOwner->GetGuid() );
			Asura_Network::Send(pxMessage);
		}
	}
	else
	{
		Asura_Assert(false, "Cannot find pickup or actor!");
	}

	m_bHasRequestedPickup = true; // Set to true
}

void AVP_Actor_PickupHandler::UpdateClient()
{
	Asura_Hashed_List_It<AVP_ClientEntity_Pickup> xPickupIter(AVP_ClientEntity_Pickup::GetPromptedPickList());
	const AVP_ClientEntity_Actor* const pxOwner    = AVP_ClientEntity_Actor::FindEntity(m_uActorGuid);
	if(pxOwner)
	{
		const AVP_ClientEntity_Pickup* pxNearestPickup = NULL;
		const AVP_ClientEntity_Pickup* pxCurrentPickup = NULL;
		const AVP_Pickup*              pxPickupObject  = NULL;
		const AVP_Inventory_System*    pxInventory     = pxOwner->GetInventorySystem();
		if(pxInventory)
		{
			float fBestColDistSqrd = Asura_Maths::LargeFloat;
			float fThisColDistSqrd;

			const AVP_Settings::Species eOwnerSpecies = pxOwner->GetSpeciesByClassification();

			// Loop through all pickups
			for ( ; !xPickupIter.Done(); xPickupIter.Next() )
			{
				pxCurrentPickup = xPickupIter.GetCurrent();
				if(pxCurrentPickup)
				{
					pxPickupObject  = pxCurrentPickup->GetPickupObject();
					if(pxPickupObject)
					{
						// fThisColDistSqrd get the current collection distance from CanCollect, and if CanCollect returns true, it is compared with the current nearest pickup distance
						if (    ( !pxCurrentPickup->IsInvisible() )
							 && ( pxCurrentPickup->IsEnabled() )
							 && ( pxPickupObject->GetAllegiance() == eOwnerSpecies ) 
							 && ( AVP_Actor_PickupHandler::CanCollect( *pxCurrentPickup, *pxOwner, fThisColDistSqrd ) )
							 && ( fThisColDistSqrd < fBestColDistSqrd )   
							 && ( !IsThisPickupStoreFull(*pxPickupObject, *pxInventory) )	   )
						{
							fBestColDistSqrd = fThisColDistSqrd;
							pxNearestPickup  = pxCurrentPickup;
						}
					}
					else
					{
						Asura_Assert(pxOwner, "In Pickup handler, UpdateClient, where is pxPickupObject?");
					}
				}
				else
				{
					Asura_Assert(pxOwner, "In Pickup handler, UpdateClient, where is pxCurrentPickup?");
				}
			}

			// Do we have the nearest pickup?
			// If we have a nearest pickup; the input hanlder check for it and for the interaction button press and then call the SendRequestMessage
			m_uNearestPickupGuid = (pxNearestPickup)?pxNearestPickup->GetGuid():ASURA_GUID_UNREGISTERED;

			m_bHasRequestedPickup = false; // reset now melee system has updated
		}
		else
		{
			Asura_Assert(pxOwner, "In Pickup handler, UpdateClient, where is pxInventory?");
		}
	}
	else
	{
		Asura_Assert(pxOwner, "In Pickup handler, UpdateClient, where is pxOwner?");
	}
}

bool AVP_Actor_PickupHandler::CanCollect(const AVP_ServerEntity_Pickup& xPickup, const AVP_ServerEntity_Actor& xOwner, float& fThisColDistSqrd, bool bClientRequest)
{
	float fToleratedCollectDistance = xPickup.GetPickupObject()->GetCollectDistanceSqrd();
	const Asura_Vector_3 xDistance  = xOwner.GetPosition() - xPickup.GetPosition();

	if(bClientRequest)
	{
		fToleratedCollectDistance *= dbg_fLagTolerance;
	}

	fThisColDistSqrd = xDistance.MagnitudeSquared();

	if( fThisColDistSqrd < fToleratedCollectDistance )
	{
		if ( xPickup.GetPickupBlueprintHash() == 0xec41ae0d )	// Battery
		{
			if ( xOwner.GetInventorySystem() && xOwner.GetInventorySystem()->GetCurrentItem( uAVP_INVENTORY_GROUPHASH_WEAPON ) && xOwner.GetInventorySystem()->GetCurrentItem( uAVP_INVENTORY_GROUPHASH_WEAPON )->IsEncumberment() )
			{
				return false;
			}
		}
		return true;
	}
	return false;
}

bool AVP_Actor_PickupHandler::CanCollect(const AVP_ClientEntity_Pickup& xPickup, const AVP_ClientEntity_Actor& xOwner, float& fThisColDistSqrd)
{
	const Asura_Vector_3 xDistance = xOwner.GetCurrentPosition() - xPickup.GetCurrentPosition();

	fThisColDistSqrd = xDistance.MagnitudeSquared();

	if( fThisColDistSqrd < xPickup.GetPickupObject()->GetCollectDistanceSqrd() )
	{
		if ( xPickup.GetPickupBlueprintHash() == 0xec41ae0d )	// Battery
		{
			if ( xOwner.GetInventorySystem() && xOwner.GetInventorySystem()->GetCurrentItem( uAVP_INVENTORY_GROUPHASH_WEAPON ) && xOwner.GetInventorySystem()->GetCurrentItem( uAVP_INVENTORY_GROUPHASH_WEAPON )->IsEncumberment() )
			{
				return false;
			}
		}
		return true;
	}
	return false;
}

bool AVP_Actor_PickupHandler::IsThisPickupStoreFull(const AVP_Pickup& xPickupObject, const AVP_Inventory_System& xInventory)
{
	switch(xPickupObject.GetGroupHash())
	{
		case uAVP_INVENTORY_GROUPHASH_AMMO:
		{
			const AVP_Pickup_Ammo& xAmmoPickup = static_cast<const AVP_Pickup_Ammo&>(xPickupObject);
			const AVP_AmmoStore*   pxAmmoStore  = xInventory.GetAmmoStore();
			if(pxAmmoStore)
			{
				return pxAmmoStore->IsFull(xAmmoPickup.GetAmmoType());
			}
			else
			{
				Asura_Assert(false, "No Ammo Store!");
			}
			return false;
		}
		case uAVP_INVENTORY_GROUPHASH_HEALTH:
		{
			const AVP_Inventory_Group* pxGroup = xInventory.GetPermanentlyActiveItemGroup();
			const AVP_Inventory_Item* pxItem = (pxGroup)?pxGroup->GetItem(uAVP_INVENTORY_ITEMHASH_STIM):NULL;
			if(pxItem && pxItem->GetNumSubTypes() > 0)
			{
				if(pxItem->GetSubType(0)->GetCurrentQuantity() < pxItem->GetSubType(0)->GetMaxQuantity())
				{
					return false;
				}
			}
			return true;
		}
		case uAVP_INVENTORY_GROUPHASH_WEAPON:
		{	
			const AVP_Pickup_Weapon& xWeaponPickup = static_cast<const AVP_Pickup_Weapon&>(xPickupObject);
			if(xInventory.IsClientSide())
			{
				// Client should only be treating it as a weapon...
				const AVP_Inventory_Group* pxGroup = xInventory.GetGroup(uAVP_INVENTORY_GROUPHASH_WEAPON);
				return pxGroup ? ( pxGroup->FindItemByBlueprintHash(xWeaponPickup.GetWeaponType()) != NULL ) : ( false ) ;
			}
			else
			{	
				// The server sees it as an ammo pickup if we have the weapon
				const AVP_Inventory_Group* pxGroup = xInventory.GetGroup(uAVP_INVENTORY_GROUPHASH_WEAPON);
				if(pxGroup)
				{
					const AVP_Inventory_Item* pxItem = pxGroup->FindItemByBlueprintHash(xWeaponPickup.GetWeaponType());
					if(pxItem)
					{	
						// If we have this item... Check if ammo is full
						const AVP_AmmoStore*   pxAmmoStore  = xInventory.GetAmmoStore();
						if(pxAmmoStore)
						{
							bool bAmmoFull1 = false;
							//bool bAmmoFull2 = false;
							if(pxItem->GetNumSubTypes()>0)
							{
								bAmmoFull1 = pxAmmoStore->IsFull(pxItem->GetSubType(0)->GetAmmoType());

								/*if(pxItem->GetNumSubTypes()>1)
								{
									bAmmoFull2 = pxAmmoStore->IsFull(pxItem->GetSubType(1)->GetAmmoType());
								}
								else
								{
									bAmmoFull2 = true;
								}*/
							}
							return(bAmmoFull1/* && bAmmoFull2*/);
						}

						return true; // No ammo store?!
					}
				}
				
				return true;// The server is only allowed to use these as ammo, so return true
			}
		}
		case uAVP_INVENTORY_GROUPHASH_TOOL:
		{
			return false;
		}
		case uAVP_INVENTORY_GROUPHASH_AUDIODIARY:
		{
			return false;
		}
		default: 
		{
			Asura_Assert(false, "undefined pickup inventory type!");
			return false;
		}
	}
}