#include "../../Scene/Terrain/RuWorld_EntitySystem.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuWorld_ObjectContainer, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_ObjectContainer, "CRuWorld_ObjectContainer", "IRuObject")
ruCLASSGUID_DEFINE(CRuWorld_ObjectContainer, 0x83E36878, 0x3C324a32, 0x97AD4DD7, 0x6A7E7350)

ruRTTI_DEFINE_SUBCLASS(CRuWorld_ObjectManager, CRuEntity_Container_Impl)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_ObjectManager, "CRuWorld_ObjectManager", "CRuEntity_Container_Impl")
ruCLASSGUID_DEFINE(CRuWorld_ObjectManager, 0xBF6CF945, 0x01654a7e, 0x8328944D, 0x087D9ACA)

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

static const char *worldEntityContainerDS_TableName_Object = "worldEntityContainer_EntityDescriptor";	// need to rename this.. somehow...
static const char *worldObjectContainerDS_TableName_ObjectLink = "worldObjectContainer_ObjectLink";

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

CRuWorld_ObjectContainer::CRuWorld_ObjectContainer()
:	m_dataStore(NULL),
	m_objectQueue(NULL),
	m_instanceQueue(NULL)
{
}

CRuWorld_ObjectContainer::~CRuWorld_ObjectContainer()
{
	// Release data store
	ruSAFE_RELEASE(m_dataStore);

	// Release object queue
	ClearObjectQueue();
	delete m_objectQueue;

	// Release instancing queue
	ClearInstanceQueue();
	delete m_instanceQueue;

	// Release missing object hash
	delete m_missingObjectHash;

	// Release any remaining references in the change list
	for(INT32 i = 0; i < m_changedObjectList.Count(); ++i)
	{
		ruSAFE_RELEASE(m_changedObjectList[i]);
	}
}

BOOL CRuWorld_ObjectContainer::Attach(CRuDataStore_Base *dataStore)
{
	// Save reference to data store
	m_dataStore = dataStore;
	m_dataStore->AddRef();

	// Get data tables
	m_tableHandle_Object = m_dataStore->GetTableHandle(worldEntityContainerDS_TableName_Object);

	if(m_tableHandle_Object < 0)
	{
		m_tableHandle_Object = m_dataStore->CreateTable(worldEntityContainerDS_TableName_Object, 16);
	}

	// Construct queues
	m_objectQueue = ruNEW CRuLRUQueue<CRuGUID, IRuObject *>(4096, 8192);
	m_instanceQueue = ruNEW CRuLRUQueue<CRuGUID, CRuEntity *>(2048, 4096);
	m_missingObjectHash = ruNEW CRuKeyHash<CRuGUID>(1024);

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::SaveChanges()
{
	CRuMemoryStream *bufferStream = ruNEW CRuMemoryStream();

	// Save all changed descriptors
	for(INT32 i = 0; i < m_changedObjectList.Count(); ++i)
	{
		// Reset buffer and serialize descriptor
		bufferStream->SetStreamSize(0);
		m_changedObjectList[i]->SerializeTo(bufferStream);

		// Store stream
		m_dataStore->Store(m_tableHandle_Object, m_changedObjectList[i]->GetGUID().Data(), bufferStream);
	}

	// Delete all deleted descriptors
	for(INT32 i = 0; i < m_deletedObjectList.Count(); ++i)
	{
		m_dataStore->Delete(m_tableHandle_Object, m_deletedObjectList[i].Data());
	}

	delete bufferStream;

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::CopyTo(CRuDataStore_Base *targetDataStore)
{
	if(m_dataStore)
	{
		INT32 srcTableList[1];
		srcTableList[0] = m_tableHandle_Object;

		// Create tables
		INT32 targetTableList[1];
		targetTableList[0] = targetDataStore->CreateTable(worldEntityContainerDS_TableName_Object, 16);

		// Copy data in each table
		for(INT32 i = 0; i < 1; ++i)
		{
			INT32 numEntries = m_dataStore->GetNumRecords(srcTableList[i]);
			INT32 keyLength = m_dataStore->GetTableKeyLength(srcTableList[i]);
			BYTE *key = ruNEW BYTE [keyLength];

			for(INT32 j = 0; j < numEntries; ++j)
			{
				// Retrieve key and data
				IRuStream *stream = m_dataStore->RetrieveByIndex(srcTableList[i], j);

				if(stream)
				{
					m_dataStore->RetrieveKeyByIndex(srcTableList[i], j, key);
					targetDataStore->Store(targetTableList[i], key, stream);
					delete stream;
				}
			}

			delete[] key;
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuWorld_ObjectContainer::InsertObject(IRuObject *object)
{
	// Verify that the object GUID does not already exist
	if(GetObjectFromChangeList(object->GetGUID()))
	{
		return FALSE;
	}

	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Object, object->GetGUID().Data());
	if(stream)
	{
		delete stream;
		return FALSE;
	}

	// Ensure the delete list does not include the object
	m_deletedObjectList.Remove(object->GetGUID());

	// Insert object into change list
	m_changedObjectList.Add(object);

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::DeleteObject(const CRuGUID &guid)
{
	if(m_deletedObjectList.Contains(guid) == FALSE)
	{
		// Remove object from change list
		IRuObject *object = GetObjectFromChangeList(guid);
		if(object)
		{
			m_changedObjectList.Remove(object);
			ruSAFE_RELEASE(object);
		}

		// Remove object from queue
		m_objectQueue->Remove(guid, guid.HashCode());

		// Add object GUID to delete list
		m_deletedObjectList.Add(guid);
	}

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::MarkObjectForUpdate(const CRuGUID &guid)
{
	if(GetObjectFromChangeList(guid) == NULL)
	{
		// Retrieve object for the specified GUID
		IRuObject *object = GetObject(guid);

		// Add object GUID to change list
		if(object)
		{
			object->AddRef();
			m_changedObjectList.Add(object);
		}
	}

	return TRUE;
}

IRuObject *CRuWorld_ObjectContainer::GetObject(const CRuGUID &guid)
{
	IRuObject *object = NULL;

	// Search the change list
	if(object = GetObjectFromChangeList(guid))
	{
		return object;
	}

	// Search the object queue for matching GUID
	if(m_objectQueue->ContainsKey(guid, guid.HashCode()))
	{
		return m_objectQueue->Get(guid, guid.HashCode());
	}

	// Make sure the object is not in the delete list
	if(m_deletedObjectList.Contains(guid))
	{
		return NULL;
	}

	// If a previous attempt to load the object failed, then do not try to load it again
	if(m_missingObjectHash->ContainsKey(guid, guid.HashCode()))
	{
		return NULL;
	}

	// Retrieve object stream
	IRuStream *stream = m_dataStore->Retrieve(m_tableHandle_Object, guid.Data());
	if(stream)
	{
		// Create object from stream using object factory
		IRuObject *object = g_ruObjectFactory.CreateObject(stream);
		delete stream;

		if(object)
		{
			// Make space in the LRU queue
			IRuObject *reclaimedObject;
			while(m_objectQueue->Reclaim(guid, guid.HashCode(), 1, reclaimedObject))
			{
				// Release reclaimed object
				ruSAFE_RELEASE(reclaimedObject);
			}

			// Add object to LRU queue
			m_objectQueue->Add(guid, guid.HashCode(), object, 1);

			return object;
		}
	}
	else
	{
		m_missingObjectHash->Add(guid, guid.HashCode());
	}

	return NULL;
}

CRuWorld_EntityDescriptor *CRuWorld_ObjectContainer::GetEntityDescriptor(const CRuGUID &guid)
{
	IRuObject *object = this->GetObject(guid);

	if(object && object->GetType() == CRuWorld_EntityDescriptor::Type)
	{
		return static_cast<CRuWorld_EntityDescriptor *>(object);
	}

	return NULL;
}

CRuEntity *CRuWorld_ObjectContainer::GetEntityInstance(const CRuGUID &guid)
{
	// Search the instance queue for matching GUID
	if(m_instanceQueue->ContainsKey(guid, guid.HashCode()))
	{
		return m_instanceQueue->Get(guid, guid.HashCode());
	}

	const CRuWorld_EntityDescriptor *descriptor = GetEntityDescriptor(guid);
	if(descriptor)
	{
		CRuEntity *entity = g_ruResourceManager->LoadEntity(descriptor->GetResourceName());
		if(entity)
		{
			// Setup frame orientation
			entity->ResetLocalTransform();
			entity->Scale(descriptor->GetScale());
			entity->Rotate(descriptor->GetRotation());
			entity->Translate(descriptor->GetTranslation());
			entity->Update(0.0f);

			// Make space in the LRU queue
			CRuEntity *reclaimedEntity;
			while(m_instanceQueue->Reclaim(guid, guid.HashCode(), 1, reclaimedEntity))
			{
				// Release reclaimed descriptor
				ruSAFE_RELEASE(reclaimedEntity);
			}

			// Add descriptor to LRU queue
			m_instanceQueue->Add(guid, guid.HashCode(), entity, 1);

			return entity;
		}
	}

	return NULL;
}

BOOL CRuWorld_ObjectContainer::ClearObjectQueue()
{
	// Iterate through each object and destroy
	m_objectQueue->ForAllElements(boost::bind(&CRuWorld_ObjectContainer::ClearObjectQueueCallback, this, _1, _2));

	// Clear object queue
	m_objectQueue->Clear();

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::ClearInstanceQueue()
{
	// Iterate through each entity instance and destroy
	m_instanceQueue->ForAllElements(boost::bind(&CRuWorld_ObjectContainer::ClearInstanceQueueCallback, this, _1, _2));

	// Clear instance queue
	m_instanceQueue->Clear();

	return TRUE;
}

IRuObject *CRuWorld_ObjectContainer::GetObjectFromChangeList(const CRuGUID &guid)
{
	for(INT32 i = 0; i < m_changedObjectList.Count(); ++i)
	{
		if(m_changedObjectList[i]->GetGUID() == guid)
		{
			return m_changedObjectList[i];
		}
	}

	return NULL;
}

BOOL CRuWorld_ObjectContainer::ClearObjectQueueCallback(const void *key, void *data)
{
	IRuObject *object = *((IRuObject **) data);

	ruSAFE_RELEASE(object);

	return TRUE;
}

BOOL CRuWorld_ObjectContainer::ClearInstanceQueueCallback(const void *key, void *data)
{
	CRuEntity *entity = *((CRuEntity **) data);

	ruSAFE_RELEASE(entity);

	return TRUE;
}

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

CRuWorld_ObjectManager::CRuWorld_ObjectManager()
:	m_objectContainer(NULL),
	m_enumerationMarginSize(120.0f),
	m_enableTriggerDisplay(FALSE)
{
	m_enumerationHash = ruNEW CRuKeyHash<CRuGUID>(2048);
}

CRuWorld_ObjectManager::~CRuWorld_ObjectManager()
{
	ruSAFE_RELEASE(m_objectContainer);
	delete m_enumerationHash;
}

BOOL CRuWorld_ObjectManager::Attach(CRuWorld_ObjectContainer *container)
{
	m_objectContainer = container;
	m_objectContainer->AddRef();

	return TRUE;
}

BOOL CRuWorld_ObjectManager::SetEnumerator(RuWorld::EntityEnumerator enumerator)
{
	m_enumerator = enumerator;
	return TRUE;
}

BOOL CRuWorld_ObjectManager::ClearEnumerationCache()
{
	m_enumerationBounds.Set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
	this->ClearEntities();
	m_enumerationHash->Clear();

	return TRUE;
}

BOOL CRuWorld_ObjectManager::EnableTriggerDisplay(BOOL enable)
{
	if(m_enableTriggerDisplay != enable)
	{
		m_enableTriggerDisplay = enable;
		ClearEnumerationCache();
	}

	return TRUE;
}

BOOL CRuWorld_ObjectManager::UpdateEnumeration(const CRuAABB &enumerationBounds)
{
	if(m_enumerationBounds.Contains(enumerationBounds) == FALSE)
	{
		// Clear enumeration cache
		this->ClearEnumerationCache();

		// Set enumeration bounds to the specified bounds plus the margin
		m_enumerationBounds = enumerationBounds;
		m_enumerationBounds.Minima().m_x -= m_enumerationMarginSize;
		m_enumerationBounds.Minima().m_y -= m_enumerationMarginSize;
		m_enumerationBounds.Minima().m_z -= m_enumerationMarginSize;
		m_enumerationBounds.Maxima().m_x += m_enumerationMarginSize;
		m_enumerationBounds.Maxima().m_y += m_enumerationMarginSize;
		m_enumerationBounds.Maxima().m_z += m_enumerationMarginSize;

		// Iterate and find all unique GUIDs
		CRuArrayList<CRuGUID> &entityGUIDList = m_enumerator();
		for(INT32 i = 0; i < entityGUIDList.Count(); ++i)
		{
			if(m_enumerationHash->ContainsKey(entityGUIDList[i], entityGUIDList[i].HashCode()) == FALSE)
			{
				IRuObject *object = m_objectContainer->GetObject(entityGUIDList[i]);

				if(object)
				{
					if(object->GetType() == CRuWorld_EntityDescriptor::Type)
					{
						CRuWorld_EntityDescriptor *descriptor = static_cast<CRuWorld_EntityDescriptor *>(object);

						if(descriptor && Intersects(enumerationBounds, descriptor->GetWorldBounds()))
						{
							CRuEntity *instancedEntity = m_objectContainer->GetEntityInstance(entityGUIDList[i]);
							if(instancedEntity)
							{
								// Instance entity
								this->AddEntity(instancedEntity);

								// Append GUID to unique list
								m_enumerationHash->Add(entityGUIDList[i], entityGUIDList[i].HashCode());
							}
						}
					}
					else if(object->GetType().IsSubClassOf(CRuEntity::Type) && m_enableTriggerDisplay)
					{
						// Instance entity
						this->AddEntity(static_cast<CRuEntity *>(object));

						// Append GUID to unique list
						m_enumerationHash->Add(entityGUIDList[i], entityGUIDList[i].HashCode());
					}
				}
			}
		}
	}

	return TRUE;
}

BOOL CRuWorld_ObjectManager::Update(REAL elapsedTime)
{
	return CRuEntity_Container_Impl::Update(elapsedTime);
}

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

#pragma managed
