//========= Copyright © 1996-2008, Valve LLC, All rights reserved. ============
//
// Purpose: Class for tracking inventory
//
// $NoKeywords: $
//=============================================================================

#include "stdafx.h"
#include "Inventory.h"
#include "SpaceWarClient.h"


//-----------------------------------------------------------------------------
// Purpose: singleton instance of CSpaceWarLocalInventory
//-----------------------------------------------------------------------------
CSpaceWarLocalInventory *SpaceWarLocalInventory()
{
	static CSpaceWarLocalInventory inv;
	return &inv;
}

CSpaceWarLocalInventory::CSpaceWarLocalInventory()
:	m_SteamInventoryResult( this, &CSpaceWarLocalInventory::OnSteamInventoryResult ),
	m_SteamInventoryFullUpdate( this, &CSpaceWarLocalInventory::OnSteamInventoryFullUpdate )
{
	m_hPlaytimeRequestResult = k_SteamInventoryResultInvalid;
	m_hPromoRequestResult = k_SteamInventoryResultInvalid;
	m_hLastFullUpdate = k_SteamInventoryResultInvalid;
	m_hExchangeRequestResult = k_SteamInventoryResultInvalid;

	m_LastDropInstanceID = k_SteamItemInstanceIDInvalid;

	// Indicate that this game has a use for item definition properties (we look up "name").
	// If your game hardcodes the complete set of items, then you can probably skip this call.
	SteamInventory()->LoadItemDefinitions();

	// If there are any promotional items which your game offers (or may offer in the future)
	// then this is the call that will grant them. Promotional items are a result of meeting
	// some external criteria like owning another specific game. These criteria are specified
	// in your Steamworks item definitions.
	SteamInventory()->GrantPromoItems( &m_hPromoRequestResult );

#ifdef _DEBUG
	GrantTestItems();
#endif

	// We could pass a variable to receive the result handle, for
	// comparison to the handle in SteamInventoryResultReady_t,
	// but this simple example does not bother to keep track of
	// multiple in-flight API calls.
	SteamInventory()->GetAllItems( NULL ); // this will fire off FullUpdate and then ResultReady
}

//-----------------------------------------------------------------------------
// Purpose: Handles notification that GetAllItems has refreshed the local inventory
//-----------------------------------------------------------------------------
void CSpaceWarLocalInventory::OnSteamInventoryFullUpdate( SteamInventoryFullUpdate_t *callback )
{
	// This callback triggers immediately before the ResultReady callback. We shouldn't
	// free the result handle here, as we wil always free it at the end of ResultReady.

	bool bGotResult = false;
	std::vector<SteamItemDetails_t> vecDetails;
	uint32 count = 0;
	if ( SteamInventory()->GetResultItems( callback->m_handle, NULL, &count ) )
	{
		vecDetails.resize( count );
		bGotResult = SteamInventory()->GetResultItems( callback->m_handle, vecDetails.data(), &count );
	}

	if ( bGotResult )
	{
		// For everything already in the inventory, check for update (exists in result) or removal (does not exist)
		std::list<CSpaceWarItem *>::iterator iter;
		for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); /*incr at end of loop*/ )
		{
			bool bFound = false;
			for ( size_t i = 0; i < vecDetails.size(); i++ )
			{
				if ( (*iter)->GetItemId() == vecDetails[i].m_itemId )
				{
					// Update item with matching item id
					(*iter)->m_Details = vecDetails[i];

					// Remove elements from the result vector as we process updates (fast swap-and-pop removal)
					if ( i < vecDetails.size() - 1 )
						vecDetails[i] = vecDetails.back();
					vecDetails.pop_back();
					
					bFound = true;
					break;
				}
			}

			if ( !bFound )
			{
				// No items in the full update match the existing item. Delete current iterator and advance.
				delete *iter;
				iter = m_listPlayerItems.erase( iter );
			}
			else
			{
				// Increment iterator without deleting.
				++iter;
			}
		}

		// Anything remaining in the result vector is a new item, since we removed all the updates.
		for ( size_t i = 0; i < vecDetails.size(); ++i )
		{
			CSpaceWarItem *item = new CSpaceWarItem();
			item->m_Details = vecDetails[i];
			m_listPlayerItems.push_back( item );
		}
	}

	// Remember that we just processed this full update to avoid doing work in ResultReady
	m_hLastFullUpdate = callback->m_handle;
}

//-----------------------------------------------------------------------------
// Purpose: Handles notification that the inventory is updated
//-----------------------------------------------------------------------------
void CSpaceWarLocalInventory::OnSteamInventoryResult( SteamInventoryResultReady_t *callback )
{
	// Ignore results that belong to some other SteamID - this normally won't happen, unless you start
	// calling SerializeResult/DeserializeResult, but it is better to be safe. Also ignore anything that
	// we just processed in OnSteamInventoryFullUpdate to avoid duplicate work.
	if ( callback->m_result == k_EResultOK && m_hLastFullUpdate != callback->m_handle &&
		 SteamInventory()->CheckResultSteamID( callback->m_handle, SpaceWarClient()->GetLocalSteamID() ) )
	{
		bool bGotResult = false;
		std::vector<SteamItemDetails_t> vecDetails;
		uint32 count = 0;
		if ( SteamInventory()->GetResultItems( callback->m_handle, NULL, &count ) )
		{
			vecDetails.resize( count );
			bGotResult = SteamInventory()->GetResultItems( callback->m_handle, vecDetails.data(), &count );
		}

		if ( bGotResult )
		{
			// For everything already in the inventory, check for update or removal
			std::list<CSpaceWarItem *>::iterator iter;
			for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); /*incr at end of loop*/ )
			{
				bool bDestroy = false;
				for ( size_t i = 0; i < vecDetails.size(); i++ )
				{
					if ( (*iter)->GetItemId() == vecDetails[i].m_itemId )
					{
						// If flagged for removal by a partial update, remove it
						if ( vecDetails[i].m_unFlags & k_ESteamItemRemoved )
						{
							bDestroy = true;
						}
						else
						{
							(*iter)->m_Details = vecDetails[i];
						}

						// Remove elements from the result vector as we process updates (fast swap-and-pop removal)
						if ( i < vecDetails.size() - 1 )
							vecDetails[i] = vecDetails.back();
						vecDetails.pop_back();

						break;
					}
				}

				if ( bDestroy )
				{
					// Delete list element at current iterator and advance.
					delete *iter;
					iter = m_listPlayerItems.erase( iter );
				}
				else
				{
					// Increment iterator without deleting.
					++iter;
				}
			}

			// Anything remaining in the result vector is a new item, unless flagged for removal by an operation result.
			for ( size_t i = 0; i < vecDetails.size(); ++i )
			{
				if ( !( vecDetails[i].m_unFlags & k_ESteamItemRemoved ) )
				{
					CSpaceWarItem *item = new CSpaceWarItem();
					item->m_Details = vecDetails[i];
					m_listPlayerItems.push_back( item );
				}
			}
		}
	}

	// Clear out any pending handles.
	if ( callback->m_handle == m_hPlaytimeRequestResult )
		m_hPlaytimeRequestResult = -1;
	if ( callback->m_handle == m_hExchangeRequestResult )
		m_hExchangeRequestResult = -1;
	if ( callback->m_handle == m_hPromoRequestResult )
		m_hPromoRequestResult = -1;
	if ( callback->m_handle == m_hLastFullUpdate )
		m_hLastFullUpdate = -1;

	// We're not hanging on the the result after processing it.
	SteamInventory()->DestroyResult( callback->m_handle );
}

void CSpaceWarLocalInventory::CheckForItemDrops()
{
	SteamInventory()->TriggerItemDrop( &m_hPlaytimeRequestResult, k_SpaceWarItem_TimedDropList );
}

void CSpaceWarLocalInventory::DoExchange()
{
	const CSpaceWarItem *item100 = GetInstanceOf( k_SpaceWarItem_ShipDecoration1 );
	const CSpaceWarItem *item101 = GetInstanceOf( k_SpaceWarItem_ShipDecoration2 );
	const CSpaceWarItem *item102 = GetInstanceOf( k_SpaceWarItem_ShipDecoration3 );
	const CSpaceWarItem *item103 = GetInstanceOf( k_SpaceWarItem_ShipDecoration4 );
	if ( item100 && item101 && item102 && item103 )
	{
		SteamItemInstanceID_t inputItems[4];
		uint32 inputQuantities[4];
		inputItems[0] = item100->GetItemId();
		inputQuantities[0] = 1;
		inputItems[1] = item101->GetItemId();
		inputQuantities[1] = 1;
		inputItems[2] = item102->GetItemId();
		inputQuantities[2] = 1;
		inputItems[3] = item103->GetItemId();
		inputQuantities[3] = 1;
		SteamItemDef_t outputItems[1];
		outputItems[0] = 110;
		uint32 outputQuantity[1];
		outputQuantity[0] = 1;
		SteamInventory()->ExchangeItems( &m_hExchangeRequestResult, outputItems, outputQuantity, 1, inputItems, inputQuantities, 4 );
	}
}

void CSpaceWarLocalInventory::GrantTestItems()
{
	std::vector<SteamItemDef_t> newItems;
	newItems.push_back( k_SpaceWarItem_ShipDecoration1 );
	newItems.push_back( k_SpaceWarItem_ShipDecoration2 );
	SteamInventory()->GenerateItems( NULL, newItems.data(), NULL, (uint32) newItems.size() );
}

const CSpaceWarItem * CSpaceWarLocalInventory::GetItem( SteamItemInstanceID_t nItemId ) const
{
	std::list<CSpaceWarItem *>::const_iterator iter;
	for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); ++iter )
	{
		if ( (*iter)->GetItemId() == nItemId )
			return (*iter);
	}
	return NULL;
}

bool CSpaceWarLocalInventory::HasInstanceOf( SteamItemDef_t nDefinition ) const
{
	std::list<CSpaceWarItem *>::const_iterator iter;
	for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); ++iter )
	{
		if ( ( *iter )->GetDefinition() == nDefinition )
			return true;
	}
	return false;
}

uint32 CSpaceWarLocalInventory::GetNumOf( SteamItemDef_t nDefinition ) const
{
	uint32 unQuantity = 0;
	std::list<CSpaceWarItem *>::const_iterator iter;
	for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); ++iter )
	{
		if ( ( *iter )->GetDefinition() == nDefinition )
		{
			unQuantity += (*iter)->GetQuantity();
		}
	}
	return unQuantity;
}

const CSpaceWarItem *  CSpaceWarLocalInventory::GetInstanceOf( SteamItemDef_t nDefinition ) const
{
	std::list<CSpaceWarItem *>::const_iterator iter;
	for ( iter = m_listPlayerItems.begin(); iter != m_listPlayerItems.end(); ++iter )
	{
		if ( ( *iter )->GetDefinition() == nDefinition )
			return (*iter);
	}
	return NULL;
}

void CSpaceWarLocalInventory::RefreshFromServer()
{
	// This will trigger the SteamInventoryResultReady_t callback,
	// and possibly the SteamInventoryFullUpdate_t callback first.
	
	// We could pass a variable to receive the result handle, for
	// comparison to the handle in SteamInventoryResultReady_t,
	// but this simple example does not bother to keep track of
	// multiple in-flight API calls.
	SteamInventory()->GetAllItems( NULL );
}


std::string CSpaceWarItem::GetLocalizedName() const
{
	std::string ret;
	char buf[512];
	uint32 bufSize = sizeof(buf);
	if ( SteamInventory()->GetItemDefinitionProperty( GetDefinition(), "name", buf, &bufSize ) && bufSize <= sizeof(buf) )
	{
		ret = buf;
	}
	else
	{
		ret = "(unknown)";
	}
	return ret;
}

std::string CSpaceWarItem::GetLocalizedDescription() const
{
	std::string ret;
	char buf[2048];
	uint32 bufSize = sizeof(buf);
	if ( SteamInventory()->GetItemDefinitionProperty( GetDefinition(), "description", buf, &bufSize ) && bufSize <= sizeof(buf) )
	{
		ret = buf;
	}
	return ret;
}

std::string CSpaceWarItem::GetIconURL() const
{
	std::string ret;
	char buf[512];
	uint32 bufSize = sizeof(buf);
	if ( SteamInventory()->GetItemDefinitionProperty( GetDefinition(), "icon_url", buf, &bufSize ) && bufSize <= sizeof(buf) )
	{
		ret = buf;
	}
	return ret;
}
