#include "../../Direct3D/Engine/RuInstanceManagerD3D.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

CRuInstancedMeshD3D::CRuInstancedMeshD3D()
{
}

CRuInstancedMeshD3D::~CRuInstancedMeshD3D()
{
}

void CRuInstancedMeshD3D::SetBuffers(IRuVertexBuffer **vbArray, IRuIndexBuffer *ib, INT32 numVBs, UINT32 stride, UINT32 numVertices, UINT32 numPrimitives, RuPrimitiveType primitiveType)
{
	m_vbArray = vbArray;
	m_ib = ib;

	m_numVBs = numVBs;
	m_stride = stride;
	m_numVertices = numVertices;
	m_numPrimitives = numPrimitives;
	m_primitiveType = primitiveType;
}

UINT32 CRuInstancedMeshD3D::Render()
{
	return Render(0, m_numPrimitives);
}

UINT32 CRuInstancedMeshD3D::Render(UINT32 primitiveOffset, UINT32 numPrimitives, INT32 vb0Index, INT32 vb1Index)
{
	// Select vertex buffer for stream 0
	if(vb0Index == -1 || vb0Index >= m_numVBs)
	{
		m_vbArray[0]->Select(0, m_stride);
	}
	else
	{
		m_vbArray[vb0Index]->Select(0, m_stride);
	}

	// Select vertex buffer for stream 1
	if(vb1Index == -1 || vb1Index >= m_numVBs)
	{
		// Should disable other vertex streams here?
	}
	else
	{
		m_vbArray[vb1Index]->Select(1, m_stride);
	}

	// Select index buffer (How i wish i can set the index buffer offset right here....instead of specifying in the draw call)
	m_ib->Select();

	// TODO: Move the following code into the engine's render code

	// Flush engine state changes
	m_engine->FlushStateChanges();

	switch(m_primitiveType)
	{
		case ruPRIMTYPE_LINELIST:
			m_engine->GetD3DDevice()->DrawIndexedPrimitive(D3DPT_LINELIST, 0, 0, m_numVertices, m_ib->GetIndexOffset() + primitiveOffset * 2, numPrimitives);
			return numPrimitives;

		case ruPRIMTYPE_TRIANGLELIST:
			m_engine->GetD3DDevice()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_numVertices, m_ib->GetIndexOffset() + primitiveOffset * 3, numPrimitives);
			return numPrimitives;

		case ruPRIMTYPE_POINTLIST:
			m_engine->GetD3DDevice()->DrawPrimitive(D3DPT_POINTLIST, 0, m_numVertices);
			return numPrimitives;
	}

	return 0;
}

// ************************************************************************************************************************************************************

void CRuInstanceManagerD3D::VBInfo::DiscardResources()
{
	// Release reference to master VB
	ruSAFE_RELEASE(m_masterVB);

	// Release references to child VBs
	for(INT32 i = 0; i < m_numVBs; ++i)
	{
		ruSAFE_RELEASE(m_vbArray[i]);
	}

	// Delete the VB array
	delete[] m_vbArray;
}

const UINT32 CRuInstanceManagerD3D::s_dynamicVBSizesCount = 6;
const UINT32 CRuInstanceManagerD3D::s_dynamicVBSizes[] = {  16384,  32768,  65536, 262144, 524288, 1048576 };
const UINT32 CRuInstanceManagerD3D::s_dynamicIBSizesCount = 6;
const UINT32 CRuInstanceManagerD3D::s_dynamicIBSizes[] = {   8192,  16384,  32768, 131072, 262144, 524288 };

CRuInstanceManagerD3D::CRuInstanceManagerD3D()
:	m_metric_NumIBAllocations(0),
	m_metric_NumVBAllocations(0),
	m_metric_NumIBInstanceOps(0),
	m_metric_NumVBInstanceOps(0)
{
	m_ibQueue = NULL;
	m_vbQueue = NULL;
	m_engine = NULL;

	m_dynamicVertexBuffers = NULL;
	m_dynamicIndexBuffers = NULL;

	m_lastDynamicVBArraySize = 0;
	m_lastDynamicVBArray = NULL;
}

CRuInstanceManagerD3D::~CRuInstanceManagerD3D()
{
	ClearCache();

	delete m_vbQueue;
	delete m_ibQueue;

	if(m_lastDynamicVBArray)
	{
		for(INT32 i = 0; i < m_lastDynamicVBArraySize; ++i)
		{
			ruSAFE_RELEASE(m_lastDynamicVBArray[i]);
		}

		delete[] m_lastDynamicVBArray;
	}

	if(m_dynamicVertexBuffers)
	{
		for(INT32 i = 0; i < s_dynamicVBSizesCount; ++i)
		{
			if(m_dynamicVertexBuffers[i])
			{
				m_dynamicVertexBuffers[i]->Release();
				m_dynamicVertexBuffers[i] = NULL;
			}
		}
	}

	if(m_dynamicIndexBuffers)
	{
		for(INT32 i = 0; i < s_dynamicIBSizesCount; ++i)
		{
			if(m_dynamicIndexBuffers[i])
			{
				m_dynamicIndexBuffers[i]->Release();
				m_dynamicIndexBuffers[i] = NULL;
			}
		}
	}
}

UINT32 CRuInstanceManagerD3D::GetCacheSize()
{
//	return m_vbQueue->GetDataSize() + m_ibQueue->GetDataSize();
	return 0;
}

BOOL CRuInstanceManagerD3D::SetEngine(CRuEngineD3D *engine)
{
	UINT32 vbArenaSize;
	UINT32 ibArenaSize;
	UINT32 vbArenaPages;
	UINT32 ibArenaPages;

	// Store pointer to engine
	m_engine = engine;

	// Invalidate and release current cache
	ClearCache();

	// Get resource arena parameters from the engine
	m_engine->GetResourceArenaSize(vbArenaSize, ibArenaSize, vbArenaPages, ibArenaPages);

	// Setup queues according to engine parameters
	delete m_vbQueue;
	delete m_ibQueue;
	m_vbQueue = ruNEW CRuLRUQueueEx<DWORD, CRuGUID, VBInfo>(vbArenaSize, 128, 4096);
	m_ibQueue = ruNEW CRuLRUQueue<CRuGUID, IBInfo>(ibArenaSize, ibArenaPages * 4);

	// Setup dynamic buffers
	m_dynamicVertexBuffers = new IRuVertexBuffer * [s_dynamicVBSizesCount];
	for(INT32 i = 0; i < s_dynamicVBSizesCount; ++i)
	{
		m_dynamicVertexBuffers[i] = m_engine->CreateDynamicVertexBuffer(s_dynamicVBSizes[i]);

		if(m_dynamicVertexBuffers[i] == NULL)
			return FALSE;
	}

	m_dynamicIndexBuffers = new IRuIndexBuffer * [s_dynamicIBSizesCount];
	for(INT32 i = 0; i < s_dynamicIBSizesCount; ++i)
	{
		m_dynamicIndexBuffers[i] = m_engine->CreateDynamicIndexBuffer(s_dynamicIBSizes[i]);

		if(m_dynamicIndexBuffers[i] == NULL)
			return FALSE;
	}

	// Setup the instanced mesh object
	m_lastInstancedMesh.SetEngine(engine);

	return TRUE;
}

IRuEngine *CRuInstanceManagerD3D::GetEngine()
{
	return m_engine;
}

IRuInstancedMesh *CRuInstanceManagerD3D::GetInstance(IRuMesh *mesh, IRuShader *shader)
{
	// for now, assume we have one pass and one stream only
	VBInfo vbInfo = { 0, NULL };
	IBInfo ibInfo = { 0, NULL };

	vbInfo.m_numVBs = mesh->GetNumMorphTargets();

	// Calculate size needed
	UINT32 childVBSize = shader->GetVertexDeclaration()->GetStreamStride(0) * mesh->GetNumVertices();
	UINT32 masterVBSize = childVBSize * vbInfo.m_numVBs;

	UINT32 ibSize = sizeof(UINT16) * mesh->GetNumIndices();
	UINT32 size = ibSize + masterVBSize;

	BOOL vbIsDynamic = mesh->IsMeshDynamic();
	BOOL ibIsDynamic = mesh->IsTopologyDynamic();
	BOOL reinstanceVB = FALSE;
	BOOL reinstanceIB = FALSE;

	// Dynamic meshes go a different route
	if(vbIsDynamic)
	{
		for(INT32 i = 0; i < s_dynamicVBSizesCount; ++i)
		{
			if(s_dynamicVBSizes[i] >= masterVBSize)
			{
				vbInfo.m_masterVB = m_dynamicVertexBuffers[i];
				break;
			}
		}

		if(vbInfo.m_masterVB == NULL)
		{
			// Severe error
			assert(FALSE);
		}

		// Make sure the dynamic buffer is not lost
		if(vbInfo.m_masterVB->IsInvalidated())
			vbInfo.m_masterVB->Restore();

		if(m_lastDynamicVBArray)
		{
			for(INT32 i = 0; i < m_lastDynamicVBArraySize; ++i)
			{
				ruSAFE_RELEASE(m_lastDynamicVBArray[i]);
			}

			delete[] m_lastDynamicVBArray;
		}

		m_lastDynamicVBArraySize = vbInfo.m_numVBs;
		m_lastDynamicVBArray = ruNEW IRuVertexBuffer * [vbInfo.m_numVBs];

		// Subdivide master vertex buffer into child vertex buffers
		vbInfo.m_vbArray = m_lastDynamicVBArray;
		vbInfo.m_masterVB->Subdivide(vbInfo.m_numVBs, vbInfo.m_vbArray);

		reinstanceVB = TRUE;
	}
	else
	{
		DWORD vertDecl = (DWORD) shader->GetVertexDeclaration();
		DWORD vertDeclHash = RuGlobalHashGen().GetHashCode(vertDecl);

		const CRuGUID &vbKey = mesh->GetGUID();
		DWORD vbKeyHash = vbKey.HashCode();

		// Try to find an existing vertex buffer
		if(m_vbQueue->Get(vertDecl, vertDeclHash, vbKey, vbKeyHash, vbInfo))
		{
			// If the existing VB is too small or has been invalidated, destroy it
			if(masterVBSize > vbInfo.m_masterVB->GetSize() || vbInfo.m_masterVB->IsInvalidated())
			{
				m_vbQueue->Remove(vertDecl, vertDeclHash, vbKey, vbKeyHash);
				vbInfo.DiscardResources();
			}
			// Otherwise, the size is fine, so check to make sure the iteration numbers are the same
			else if(vbInfo.m_iterationNumber != mesh->GetMeshIteration())
			{
				// Update iteration number back into the hash
				vbInfo.m_iterationNumber = mesh->GetMeshIteration();
				m_vbQueue->Set(vertDecl, vertDeclHash, vbKey, vbKeyHash, vbInfo);

				// Iteration numbers are different, so re-instance the vertex buffer
				reinstanceVB = TRUE;
			}
		}

		if(vbInfo.m_masterVB == NULL)
		{
			++m_metric_NumVBAllocations;

			// Allocate vertex buffer
			VBInfo reclaimedVBInfo;
			while((vbInfo.m_masterVB = m_engine->CreateVertexBuffer(masterVBSize)) == NULL)
			{
				if(m_vbQueue->ReclaimUnconditional(reclaimedVBInfo))
				{
					reclaimedVBInfo.DiscardResources();
				}
				else
				{
					// MAJOR ERROR.... cannot reclaim but there is no space!
					assert(FALSE);
				}
			}

			// Subdivide master vertex buffer into child vertex buffers
			vbInfo.m_vbArray = ruNEW IRuVertexBuffer * [vbInfo.m_numVBs];
			vbInfo.m_masterVB->Subdivide(vbInfo.m_numVBs, vbInfo.m_vbArray);

			// Add newly allocated vertex buffer to the queue
			vbInfo.m_iterationNumber = mesh->GetMeshIteration();
			m_vbQueue->Add(vertDecl, vertDeclHash, vbKey, vbKeyHash, vbInfo, masterVBSize);

			// Mark vertex buffer for re-instancing
			reinstanceVB = TRUE;
		}
	}

	if(ibIsDynamic)
	{
		for(INT32 i = 0; i < s_dynamicIBSizesCount; ++i)
		{
			if(s_dynamicIBSizes[i] >= ibSize)
			{
				ibInfo.m_ib = m_dynamicIndexBuffers[i];
				break;
			}
		}

		if(ibInfo.m_ib == NULL)
		{
			// Severe error
			assert(FALSE);
		}

		// Make sure the dynamic buffer is not lost
		if(ibInfo.m_ib->IsInvalidated())
			ibInfo.m_ib->Restore();

		reinstanceIB = TRUE;
	}
	else
	{
		const CRuGUID &ibKey = mesh->GetGUID();
		DWORD ibKeyHash = ibKey.HashCode();

		// Try to find an existing index buffer
		if(m_ibQueue->Get(ibKey, ibKeyHash, ibInfo))
		{
			// If the existing IB is too small or has been invalidated, destroy it
			if(ibSize > ibInfo.m_ib->GetSize() || ibInfo.m_ib->IsInvalidated())
			{
				m_ibQueue->Remove(ibKey, ibKeyHash);
				ibInfo.m_ib->Release();
				ibInfo.m_ib = NULL;
			}
			// Otherwise, the size is fine, so check to make sure the iteration numbers are the same
			else if(ibInfo.m_iterationNumber != mesh->GetTopologyIteration())
			{
				// Update iteration number back into the hash
				ibInfo.m_iterationNumber = mesh->GetTopologyIteration();
				m_ibQueue->Set(ibKey, ibKeyHash, ibInfo);

				// Iteration numbers are different, so re-instance the index buffer
				reinstanceIB = TRUE;
			}
		}

		if(ibInfo.m_ib == NULL)
		{
			++m_metric_NumIBAllocations;

			// Allocate index buffer
			while((ibInfo.m_ib = m_engine->CreateIndexBuffer(ibSize)) == NULL)
			{
				if(m_ibQueue->ReclaimUnconditional(ibInfo))
				{
					ibInfo.m_ib->Release();
					ibInfo.m_ib = NULL;
				}
				else
				{
					// MAJOR ERROR.... cannot reclaim but there is no space!
					assert(FALSE);
				}
			}

			// Add newly allocated index buffer to the queue
			ibInfo.m_iterationNumber = mesh->GetTopologyIteration();
			m_ibQueue->Add(ibKey, ibKeyHash, ibInfo, ibSize);

			// Mark index buffer for re-instancing
			reinstanceIB = TRUE;
		}
	}

	// Update instanced mesh buffers
	m_lastInstancedMesh.SetBuffers(vbInfo.m_vbArray, ibInfo.m_ib, vbInfo.m_numVBs, shader->GetVertexDeclaration()->GetStreamStride(0), mesh->GetNumVertices(), mesh->GetNumPrimitives(), mesh->GetPrimitiveType());

	// Re-instance if necessary
	if(reinstanceVB)
	{
		++m_metric_NumVBInstanceOps;

		// Iterate through all morph targets
		for(INT32 i = 0; i < vbInfo.m_numVBs; ++i)
		{
			// Instance vertices for the current morph target
			void *verticesOut = vbInfo.m_vbArray[i]->Lock();
			shader->Instance(0, mesh, i, verticesOut);
			vbInfo.m_vbArray[i]->Unlock();
		}
	}

	if(reinstanceIB)
	{
		++m_metric_NumIBInstanceOps;

		// Instance indices
		if(mesh->GetIndices() && mesh->GetNumIndices() > 0)
		{
			UINT16 *indicesOut = (UINT16 *) ibInfo.m_ib->Lock();
			memcpy(indicesOut, mesh->GetIndices(), sizeof(UINT16) * mesh->GetNumIndices());
			ibInfo.m_ib->Unlock();
		}
	}

	return &m_lastInstancedMesh;
}

BOOL CRuInstanceManagerD3D::ClearCache()
{
	if(m_vbQueue)
	{
		m_vbQueue->ForAllElements(VBClearCacheCallback, this);
		m_vbQueue->Clear();
	}

	if(m_ibQueue)
	{
		m_ibQueue->ForAllElements(IBClearCacheCallback, this);
		m_ibQueue->Clear();
	}

	return TRUE;
}

INT32 CRuInstanceManagerD3D::GetNumMetricStrings()
{
	return 1;
}

const char *CRuInstanceManagerD3D::GetMetricString(INT32 stringIndex)
{
	switch(stringIndex)
	{
		case 0:
			sprintf(m_metric_String, "[Instance Manager] [VB Alloc : %d] [IB Alloc : %d] [VB Instance Op : %d] [IB Instance Op : %d]", m_metric_NumVBAllocations, m_metric_NumIBAllocations, m_metric_NumVBInstanceOps, m_metric_NumIBInstanceOps);
			return m_metric_String;
	}

	sprintf(m_metric_String, "[Instance Manager] [VB Alloc : %d] [IB Alloc : %d] [VB Instance Op : %d] [IB Instance Op : %d]", m_metric_NumVBAllocations, m_metric_NumIBAllocations, m_metric_NumVBInstanceOps, m_metric_NumIBInstanceOps);
	return m_metric_String;
}

BOOL CRuInstanceManagerD3D::VBClearCacheCallback(const void *key, const void *data, void *userData)
{
	VBInfo vb = *((VBInfo *) data);

	vb.DiscardResources();

	return TRUE;
}

BOOL CRuInstanceManagerD3D::IBClearCacheCallback(const void *key, const void *data, void *userData)
{
	IBInfo ib = *((IBInfo *) data);

	ib.m_ib->Release();

	return TRUE;
}

// ************************************************************************************************************************************************************

#pragma managed
