///////////////////////////////////////////////////////////////////////////////
/// @file deResourceManager.cpp
///
/// @brief Global resource handler
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Aug 2001
/// @author Assassin
/// @remarks Creation
///
/// @date Feb 2002
/// @author Hootie
/// @remarks Level 2 Singleton DLL Wrap
///
///////////////////////////////////////////////////////////////////////////////


//=================================================================================
// Includes
//=================================================================================

#include "deResourceManager.hpp"
#include "deArray.hpp"
#include "deFileSystem.hpp"
#include "deFile.hpp"


//=================================================================================
// Defines
//=================================================================================
const int TypeHashSize =		17;
const int ResourceHashSize =	151;
const int ThreadedHashSize =	23;



//=================================================================================
// deResourceManager::deResourceManager()
//
// Constructor 
//=================================================================================
deResourceManager::deResourceManager(void)
:	m_RegisteredTypes(TypeHashSize),
	m_Resources(ResourceHashSize),
	m_LoadingResources(ThreadedHashSize),
	m_UniqueTypes(0)
{
	m_LoadLockObj = IdeThreadLock_CreateThreadLock(1024);
	m_GlobalLockObj = IdeThreadLock_CreateThreadLock(0);
}



//=================================================================================
// deResourceManager::~deResourceManager()
//
// Destructor 
//=================================================================================
deResourceManager::~deResourceManager(void)
{
	RemoveAllResources();
	m_LoadLockObj->Release();
	m_GlobalLockObj->Release();
}



//=================================================================================
// deResourceManager::RemoveAllResources()
//
// Remove all held resources
//=================================================================================
deBoolean deResourceManager::RemoveAllResources()
{
	deThreadScopeLock scopelock(m_GlobalLockObj);
	#ifdef _DEBUG
		int deleted = 0;
	#endif
	deResource * rsc = NULL;
	deTHashString<deResource>** hash;
	deTArray <deTHashString<deResource>**> array1;
	deTArray <deResource*> array2;
	int len1, len2, i,j;

	m_Resources.GetAllElements(array1);
	len1 = array1.Length();
	for (i = 0; i < len1; ++i)
	{
		hash = array1[i];
		(*hash)->GetAllElements(array2);
		len2 = array2.Length();
		for (j = 0; j < len2; ++j)
		{
			rsc = array2[j];
			if (!rsc)
				break;
			if (rsc->ClassPtr)
			{
				rsc->ClassPtr->SetManager(NULL);
				while (rsc->ClassPtr->Release() > 0)
				{
					// do nothing;
				}
			}
			#ifdef _DEBUG
				deleted++;
			#endif
		}
		(*hash)->RemoveAllElements();
		delete (*hash);
	}
	m_Resources.RemoveAllElements();

#ifdef _DEBUG
	deleted = deleted;
#endif

	return deTRUE;
}



//=================================================================================
// deResourceManager::RegisterResourceType()
//
// Stores resource file extension
//=================================================================================
deBoolean deResourceManager::RegisterResourceType(const char *extension, deResourceMakerCB maker)
{
	char upperextension[24];
	strncpy(upperextension, extension, 23);
	upperextension[23] = '\0';
	_strupr(upperextension);

	deThreadScopeLock scopelock(m_GlobalLockObj);
	m_RegisteredTypes.AddElement(maker, upperextension);

	return deTRUE;
}


//=================================================================================
// deResourceManager::AddResource()
//
// Add a new resource
//=================================================================================
deBoolean deResourceManager::AddResource(char *extension, IdeResourceBase *classptr)
{
	deResource NewResource;

	NewResource.ClassPtr = classptr;
	strncpy(NewResource.extension, extension, 23);
	NewResource.extension[23] = '\0';

	deThreadScopeLock scopelock(m_GlobalLockObj);

	deTHashString <deResource> *hash = NULL;

	deTHashString <deResource> **pHash;
	pHash = m_Resources.FindElement(extension);

	if (pHash)
		hash = *pHash;

	if (!hash)
	{
		hash = new deTHashString<deResource>(ResourceHashSize);
		m_Resources.AddElement(hash, extension);
	}

	NewResource.ClassPtr->SetHash(hash);
	NewResource.ClassPtr->SetManager(this);
	hash->AddElement(NewResource, NewResource.ClassPtr->GetFilename());

	return deTRUE;
}



//=================================================================================
// deResourceManager::RemoveResource()
//
// Removes a resource
//=================================================================================
deBoolean deResourceManager::RemoveResource(IdeResourceBase *rsc)
{
	if (rsc == NULL)
		return deFALSE;

	deThreadScopeLock scopelock(m_GlobalLockObj);
	void * ptr = rsc->GetHash();
	if (rsc->GetManager() != this || ptr == NULL)
		return deFALSE;

	if (((deTHashString <deResource> *)ptr)->RemoveElement(rsc->GetFilename()))
	{
		// so it doesn't try to remove itself again
		rsc->SetManager(NULL);
		// release our claim to this object, although other claims might be held
		// so it might not delete itself right away
//		rsc->Release();
	}

	return deTRUE;
}



//=================================================================================
// deResourceManager::ParseExtension()
//
// Parses the file extension
//=================================================================================
void deResourceManager::ParseExtension(char *filename, char buffer[])
{
	int i,j;

	for (i = 0; filename[i]; i++);

	for (j = i-1; j>=0; j--)
	{
		if (filename[j] == '.')
			break;
	}
	if (j < 0)
	{
		buffer[0] = '\0';
		return;
	}
	strncpy(buffer, &(filename[j+1]), 23);
	buffer[23] = '\0';
}



//=================================================================================
// deResourceManager::GetResource()
//
// Get a resource
//=================================================================================
IdeResourceBase *
deResourceManager::GetResource(const char *filename,
							   IdeFileSystem *filesystem,
							   deBoolean DoLoad)
{
	if (!filename)
		DEBUGRETURN(NULL, "GetResource: Specified parameter was NULL: 'filename'");

	char extension[24];
	char upperfilename[256];

	strncpy(upperfilename, filename, 255);
	upperfilename[255] = '\0';
	_strupr(upperfilename);
	int k;
	for (k = 0; k < 255; ++k)
	{
		if (upperfilename[k] == '\\')
			upperfilename[k] = '/';
	}

	deThreadScopeLock scopelock(m_GlobalLockObj);
	// test if it's currently loading
	{
		m_LoadLockObj->Lock();
		LoadingResource_t* loading = m_LoadingResources.FindElement(upperfilename, NULL);
		while (loading)
		{
			// it is loading...
			// see if it's finished
			IdeResourceBase* object = NULL;
			if (loading && loading->ready)
			{
				object = loading->rsc;
				m_LoadingResources.RemoveElement(upperfilename);
				DE_ASSERT(object != NULL);
				if (!AddResource(extension, object))
				{
					DEBUGMSG1("GetResource: Failed to add threaded resource '%s'", filename);
					object->Release();
				}
			}
			if (object || (loading && loading->failed))
			{
				m_LoadLockObj->Unlock();
				return object;
			}
			m_LoadLockObj->Unlock();
			m_LoadLockObj->Lock();
			// well they obviously want this resource, so we'll need to wait for it to load now
		}
		m_LoadLockObj->Unlock();
	}
	
	// grab the file extension to look up in the Maker list
	ParseExtension(upperfilename, extension);

	deTHashString <deResource> *hash = NULL;
	deResource *rsc = NULL;
	deResourceMakerCB *Maker = NULL;

	deTHashString <deResource> **pHash;
	// look in the hash-table that sorts by extension
	pHash = m_Resources.FindElement(extension);
	if (pHash)
	{
		// look for the specific filename in the extension list
		hash = *pHash;
		rsc = hash->FindElement(upperfilename);
		if (rsc)
		{
			// add a claim to the resource - this must be released!
			rsc->ClassPtr->Claim();
			return rsc->ClassPtr;
		}
	}

	// by now, the object has not been found in the hash-tables
	if (!DoLoad)
		return NULL;

	// grab a function that was registered with the file extension
	Maker = m_RegisteredTypes.FindElement(extension);
	if (!Maker)
	{
		DEBUGMSG1("GetResource: unrecognized extension: '%s'", extension);
		return NULL;
	}

	// create an object using the function
	IdeFile* file = LoadFile(upperfilename, filesystem, deTRUE, deFALSE);
	IdeResourceBase *object = (*Maker)(upperfilename, file, deFALSE);
	if (file)
	{
		file->Close();
		file->Release();
	}
	if (object)
	{
		if (!AddResource(extension, object))
		{
			object->Release();
			object = NULL;
		}
	}
	else
	{
		DEBUGMSG1("GetResource: Unable to load resource: '%s'", upperfilename);
	}

	return object;
}

#pragma todo(perhaps refactor GetResourceFromFile with GetResource - and similar action for GetResourceFromFileThreaded)
IdeResourceBase*
deResourceManager::GetResourceFromFile(IdeFile* file, const char* filename, deBoolean record)
{
	if (!filename)
		DEBUGRETURN(NULL, "GetResourceFromFile: Specified parameter was NULL: 'filename'");

	char extension[24];
	char upperfilename[256];

	strncpy(upperfilename, filename, 255);
	upperfilename[255] = '\0';
	_strupr(upperfilename);
	int k;
	for (k = 0; k < 255; ++k)
	{
		if (upperfilename[k] == '\\')
			upperfilename[k] = '/';
	}

	// grab the file extension to look up in the Maker list
	ParseExtension(upperfilename, extension);
	
	deResourceMakerCB *Maker = NULL;
	
	// grab a function that was registered with the file extension
	Maker = m_RegisteredTypes.FindElement(extension);
	if (!Maker)
	{
		DEBUGMSG1("GetResource: unrecognized extension: '%s'", extension);
		return NULL;
	}
	
	IdeResourceBase *object = (*Maker)(upperfilename, file, deFALSE);
	if (object)
	{
		if (record && !AddResource(extension, object))
		{
			object->Release();
			object = NULL;
		}
	}
	else
	{
		DEBUGMSG1("GetResourceFromFile: Unable to load resource: '%s'", upperfilename);
	}
	
	return object;
}

IdeResourceBase*
deResourceManager::GetResourceFromFileThreaded(IdeFile* file, const char* filename, deBoolean /*record*/)
{
	if (!file)
		DEBUGRETURN(NULL, "GetResourceFromFileThreaded: Specified parameter was NULL: 'file'");
	if (!filename)
		DEBUGRETURN(NULL, "GetResourceFromFileThreaded: Specified parameter was NULL: 'filename'");
#pragma todo (implement GetResourceFromFileThreaded)
	DEBUGRETURN(NULL, "GetResourceFromFileThreaded: Function not implemented");
}

IdeResourceBase *
deResourceManager::GetResourceThreaded(const char *filename,
									   IdeFileSystem *filesystem,
									   deBoolean DoLoad)
{
	if (!filename)
		DEBUGRETURN(NULL, "GetResourceThreaded: Specified parameter was NULL: 'filename'");
	
	char extension[24];
	char upperfilename[256];
	
	strncpy(upperfilename, filename, 255);
	upperfilename[255] = '\0';
	_strupr(upperfilename);
	int k;
	for (k = 0; k < 255; ++k)
	{
		if (upperfilename[k] == '\\')
			upperfilename[k] = '/';
	}

	deThreadScopeLock scopelock(m_GlobalLockObj);
	// test if it's currently loading
	{
		deThreadScopeLock loadscopelock(m_LoadLockObj);
		LoadingResource_t* loading = m_LoadingResources.FindElement(upperfilename, NULL);
		// it is loading...
		if (loading)
		{
			// see if it's finished loading
			if (loading->failed || !loading->ready)
			{
				DEBUGMSG1("GetResourceThreaded: still loading or failed: '%s'", filename);
				return NULL;
			}
			if (loading->ready)
			{
				IdeResourceBase* object = loading->rsc;
				DE_ASSERT(object != NULL);
				if (AddResource(extension, object))
					return object;
				else
				{
					DEBUGMSG1("GetResourceThreaded: Failed to add threaded resource '%s'", filename);
					object->Release();
				}
				return object;
			}
		}
	}
	
	// grab the file extension to look up in the Maker list
	ParseExtension(upperfilename, extension);
	
	deTHashString <deResource> *hash = NULL;
	deResource *rsc = NULL;
	deResourceMakerCB *Maker = NULL;
	
	deTHashString <deResource> **pHash;
	// look in the hash-table that sorts by extension
	pHash = m_Resources.FindElement(extension);
	if (pHash)
	{
		// look for the specific filename in the extension list
		hash = *pHash;
		rsc = hash->FindElement(upperfilename);
		if (rsc)
		{
			// add a claim to the resource - this must be released!
			rsc->ClassPtr->Claim();
			return rsc->ClassPtr;
		}
	}
	
	// by now, the object has not been found in the hash-tables
	if (!DoLoad)
		return NULL;
	
	// grab a function that was registered with the file extension
	Maker = m_RegisteredTypes.FindElement(extension);
	if (!Maker)
	{
		DEBUGMSG1("GetResourceThreaded: unrecognized extension: '%s'", extension);
		return NULL;
	}

	// spawn the threaded load
	{
		deThreadScopeLock loadscopelock(m_LoadLockObj);
		LoadingResource_t loading;
		loading.failed = deFALSE;
		loading.ready = deFALSE;
		loading.rsc = NULL;
		m_LoadingResources.AddElement(loading, upperfilename);
		LoadingResourceParams_t* params = new LoadingResourceParams_t;
		params->manager = this;
		params->maker = *Maker;
		params->loadfile = LoadFile(upperfilename, filesystem, deTRUE, deFALSE);
		memcpy(params->filename, upperfilename, 256);
		IdeThreadPool* pool = IdeThreadPool_GetPtr(2);
		pool->QueueUserWorkItem(ThreadedLoadHandler, params);
		DEBUGMSG1("GetResourceThreaded: queued file: '%s'", filename);
	}
	
	return NULL;
}

void deResourceManager::ChangeThreadedResourceStatus(LoadingResourceParams_t* params, LoadingResource_t* rsc)
{
	deThreadScopeLock loadscopelock(m_LoadLockObj);
	LoadingResource_t* loading = m_LoadingResources.FindElement(params->filename, NULL);
	if (!loading)
		return;
	DEBUGMSG1("GetResourceThreaded: loaded file: %s", params->filename);
	*loading = *rsc;
	delete params;
}

// static
u32 
deResourceManager::ThreadedLoadHandler(void* param)
{
	LoadingResourceParams_t* loadparams = (LoadingResourceParams_t*)param;
	DE_ASSERT(loadparams->manager);
	LoadingResource_t loading;
	loading.ready = deFALSE;
	loading.failed = deTRUE;
	loading.rsc = NULL;
	if (loadparams
		&& loadparams->maker
		/*&& !loadparams->loadfile*/
		&& loadparams->filename[0] != '\0')
	{
		// load the resource in this thread
		loading.rsc = loadparams->maker(loadparams->filename, loadparams->loadfile, deTRUE);

		if (loadparams->loadfile)
		{
			loadparams->loadfile->Close();
			loadparams->loadfile->Release();
		}
		
		// update the state in the manager
		if (loading.rsc)
		{
			loading.ready = deTRUE;
			loading.failed = deFALSE;
		}
	}

	loadparams->manager->ChangeThreadedResourceStatus(loadparams, &loading);

	return 0;
}

//static
IdeFile *
deResourceManager::LoadFile(const char * filename, IdeFileSystem* BaseFS, deBoolean Read, deBoolean Write)
{
	IdeFile *		DataFile = NULL;
	long			OpenFlags;
	
	if (BaseFS == NULL)
	{
		BaseFS = IdeFileSystem_CreateFSReal();
		if (!BaseFS)
			return NULL;
	}
	else
	{
		//#pragma todo (handle loading of files from inside another filesystem)
		BaseFS->Claim();
	}
	
	OpenFlags = 0;
	if (Read && Write)
		OpenFlags = deFile_Open_Always | deFile_Open_Read | deFile_Open_Write;
	else if (Read)
		OpenFlags = deFile_Open_Existing | deFile_Open_Read;
	else if (Write)
		OpenFlags = deFile_Open_CreateAlways | deFile_Open_Write;
	
	DataFile = BaseFS->Open(filename, OpenFlags);
	BaseFS->Release();
	if (!DataFile)
	{
		DEBUGMSG1("LoadFile: could not open file '%s'", filename);
		return NULL;
	}
	
	return DataFile;
}
