/////////////////////////////////////////////////////////////////////
// Created by [Dennis Andersen] [2013]
/////////////////////////////////////////////////////////////////////


#include "ResourceManager.h"
#include "..\Utilities.h"

using namespace Oyster::Resource;

struct ::ResourceData
{
	LoadFunction loadFnc;
	UnloadFunction unloadFnc;
	ResourceType resourcetype;
	HRESOURCE resource;
	unsigned int resourceSize;
	int resourceID;
	Utility::DynamicMemory::ReferenceCount referenceCount;
};

bool ReadFromFile(const wchar_t fileName[], const char openFlag[], std::string& outData, size_t elemSize, bool ANSI = false)
{
	std::string sFilename;
	std::wstring wsFile = fileName;
	::Utility::String::WStringToString(wsFile, sFilename);
	size_t bytesTotal = 0;
	size_t bytesRead = 0;
	FILE *stream;

	if( fopen_s( &stream, sFilename.c_str(), openFlag ) == 0 )
	{
		//Get size of the file
		fseek(stream, 0L, SEEK_END);
		bytesTotal = ftell(stream);
		fseek(stream, 0L, SEEK_SET);
		fflush(stream);

		//Sanity check
		if(bytesTotal == 0) return false;

		//Create the new byte buffer
		char *buff = new char[bytesTotal + 1];

		//Read the bytes to the end
		bytesRead = fread_s( buff, bytesTotal, elemSize, bytesTotal ,stream );
		fclose( stream );

		//Did we read enough bytes (Get the bytes if we read with ANSI since the hidden characters is ignored)
		if(!ANSI && bytesRead != bytesTotal)	return false;

		buff[bytesRead + 1];

		outData.clear();
		outData.resize(bytesRead);
		memcpy(&outData[0], &buff[0], bytesRead);
	
		delete [] buff;
	}
	else
	{
		std::string msg = "Failed to open file: \n";
		msg.append(sFilename.c_str());

		return false;
	}

	return true;
}
const wchar_t* FindResourceKey(std::map<std::wstring, ResourceData*>& resources, const HRESOURCE h)
{
	for (auto i = resources.begin(); i != resources.end() ; i++)
	{
		if(i->second && i->second->resource == h)
		{
			return i->first.c_str();
		}
	}
	return 0;
}
ResourceData* FindResource(std::map<std::wstring, ResourceData*>& resources, const HRESOURCE h)
{
	for (auto i = resources.begin(); i != resources.end() ; i++)
	{
		if(i->second->resource == h)
		{
			return i->second;
		}
	}
	return 0;
}
ResourceData* FindResource(std::map<std::wstring, ResourceData*>& resources, const wchar_t c[])
{
	std::wstring temp = c;
	auto t = resources.find(c);
	if(t == resources.end()) return 0;

	return t->second;
}
void SaveResource( std::map<std::wstring, ResourceData*>& resources, ResourceData* r, const std::wstring& key, bool addNew )
{
	if(addNew)
	{
		resources[key] = r;
	}
	
	r->referenceCount.Incref();
}
bool Release(std::map<std::wstring, ResourceData*>& resources, ResourceData* resource)
{
	if(resource->referenceCount.Decref() < 1)
	{
		const wchar_t* temp = FindResourceKey(resources, resource->resource);

		switch (resource->resourcetype)
		{
			case Oyster::Resource::ResourceType_Byte_Raw:
			case Oyster::Resource::ResourceType_Byte_ANSI:
			case Oyster::Resource::ResourceType_Byte_UTF8:
			case Oyster::Resource::ResourceType_Byte_UNICODE:
			case Oyster::Resource::ResourceType_Byte_UTF16LE:
				delete [] ((char*)resource->resource);
				resource->resource = 0;
			break;

			case Oyster::Resource::ResourceType_CUSTOM:
				resource->unloadFnc(resource->resource);
				resource->resource = 0;
			break;
		}
		
		if(temp)	delete resources[temp];

		return true;
	}
	return false;
}
ResourceData* Load(/*Out*/ResourceData* targetMem, /*in*/const wchar_t source[], /*in*/ResourceType type)
{
	targetMem->resource = 0;
	targetMem->loadFnc = 0;
	targetMem->unloadFnc = 0;
	targetMem->resourceID = 0;
	targetMem->resourcetype = type;
	targetMem->resourceSize = 0;

	std::string sOut;
	bool success = false;

	switch (type)
	{
		case Oyster::Resource::ResourceType_Byte_Raw:
			success = ReadFromFile(source, "rb", sOut, sizeof(char));
		break;
			
		case Oyster::Resource::ResourceType_Byte_ANSI:
			success = ReadFromFile(source, "r", sOut, sizeof(char), true);
		break;

		case Oyster::Resource::ResourceType_Byte_UTF8:
			success = ReadFromFile(source, "r, ccs=UTF-8", sOut, sizeof(char));
		break;

		case Oyster::Resource::ResourceType_Byte_UNICODE:
			success = ReadFromFile(source, "r, ccs=UNICODE", sOut, sizeof(char));
		break;

		case Oyster::Resource::ResourceType_Byte_UTF16LE:
			success = ReadFromFile(source, "r, ccs=UTF-16LE", sOut, sizeof(char));
		break;
	}

	if(!success)	return 0;
	
	if(sOut.size())
	{
		char *data = new char[sOut.size()+1];
		data[sOut.size()] = '\0';
		memcpy(&data[0], &sOut[0], sOut.size());

		targetMem->resource = (HRESOURCE&)data;
		targetMem->loadFnc = 0;
		targetMem->unloadFnc = 0;
		targetMem->resourceID = 0;
		targetMem->resourcetype = type;
	}

	return targetMem;
}
ResourceData* Load(/*Out*/ResourceData* targetMem, /*in*/const wchar_t source[], LoadFunction loadFnc, UnloadFunction unloadFnc)
{
	targetMem->resource = 0;
	targetMem->loadFnc = 0;
	targetMem->unloadFnc = 0;
	targetMem->resourceID = 0;
	targetMem->resourcetype = ResourceType_CUSTOM;
	targetMem->resourceSize = 0;

	if(loadFnc)	
	{
		targetMem->resource = loadFnc(source);
		if(targetMem->resource)
		{
			targetMem->resourceSize = 0;
			targetMem->resourcetype = ResourceType_CUSTOM;
			targetMem->loadFnc = loadFnc;
			targetMem->unloadFnc = unloadFnc;
		}
	}
	return targetMem;
}
ResourceData* Reload(std::map<std::wstring, ResourceData*> resources, ResourceData* resource, const wchar_t* filename)
{
	switch (resource->resourcetype)
	{
		case Oyster::Resource::ResourceType_Byte_Raw:
		case Oyster::Resource::ResourceType_Byte_ANSI:
		case Oyster::Resource::ResourceType_Byte_UTF8:
		case Oyster::Resource::ResourceType_Byte_UNICODE:
		case Oyster::Resource::ResourceType_Byte_UTF16LE:
			if(Release(resources, resource))
				return Load(resource, filename, resource->loadFnc, resource->unloadFnc); 
		break;

		case Oyster::Resource::ResourceType_CUSTOM:
		{
			resource->unloadFnc(resource->resource);

			HRESOURCE r = resource->loadFnc(filename);
			if(!r) return 0;
			resource->resource = r;
		}
		break;
	}

	return resource;
}



ResourceManager::ResourceManager()
{  }
ResourceManager::~ResourceManager()
{ Clean(); }

HBYTEARRAY ResourceManager::LoadBytes(const wchar_t filename[], ResourceType type, int customID, bool force)
{
	if(!filename) return 0;

	ResourceData *t = FindResource(this->resources, filename);

	if(t)	
	{
		if(force)
		{
			return (HBYTEARRAY)Reload(resources, t, filename )->resource;
		}
		else
		{
			//Add new reference
			SaveResource(this->resources, t, filename, false);
			return (HBYTEARRAY)t->resource;
		}
	}
	else
	{
		t = Load(new ResourceData(), filename, type);
		if(t) 
		{
			t->resourceID = (customID);
			SaveResource(this->resources, t, filename, true);
		}
		else
		{
			return 0;
		}
	}

	return (HBYTE*)t->resource;
}
HRESOURCE ResourceManager::LoadResource(const wchar_t filename[], LoadFunction loadFnc, UnloadFunction unloadFnc, int customId, bool force)
{
	if(!filename)	
	{
		return 0;
	}
	if(!loadFnc || !unloadFnc)	
	{
		return 0;
	}

	ResourceData *t = FindResource(this->resources, filename);
	if(t)	
	{
		t->loadFnc = loadFnc;
		t->unloadFnc = unloadFnc;
		if(force)
		{
			return ResourceManager::ReloadResource(filename);
		}
		else
		{
			//Add new reference
			SaveResource(this->resources, t, filename, false);
			return t->resource;
		}
	}
	else
	{
		t = Load(new ResourceData(), filename, loadFnc, unloadFnc );
		if(t && t->resource)
		{
			t->resourceID = (customId);
			SaveResource(this->resources, t, filename, true);
		}
		else
		{
			delete t;
			t = 0;
		}
	}
	if(!t)
	{
		return 0;
	}
	return (HRESOURCE)t->resource;
}

HRESOURCE ResourceManager::ReloadResource(const wchar_t filename[])
{
	ResourceData *t = FindResource(this->resources, filename);
	if(!t) return 0;		//The resource has not been loaded

	return Reload(this->resources, t, filename)->resource;
}
HRESOURCE ResourceManager::ReloadResource(HRESOURCE& resource)
{
	ResourceData *t = FindResource(this->resources, resource);
	if(!t) return 0;
	return Reload(this->resources, t, FindResourceKey(this->resources, resource))->resource;
}

void ResourceManager::Clean()
{
	if(this->resources.empty()) return;

	auto i = this->resources.begin();
	auto last = resources.end();

	for (i; i != last; i++)
	{
		//Remove all the references
		while (!Release(resources, i->second));
	}
	resources.clear();
}
void ResourceManager::ReleaseResource(const HRESOURCE& resourceData)
{
	const wchar_t* temp = FindResourceKey(resources, resourceData);
	
	if(temp)
	{
		ResourceData *t = FindResource(this->resources, resourceData);
		if(Release(resources, t))
		{
			resources.erase(temp);
		}
	}
}
void ResourceManager::ReleaseResource(const wchar_t filename[])
{
	ResourceData *t = FindResource(this->resources, filename);
	if(t)
	{
		if(Release(resources, t))
		{
			resources.erase(filename);
		}
	}
}



void ResourceManager::SetResourceId (const HRESOURCE& resourceData, unsigned int id)
{
	ResourceData *t = FindResource(this->resources, resourceData);

	if(t)	t->resourceID = (id);
}
void ResourceManager::SetResourceId(const wchar_t c[], unsigned int id)
{
	ResourceData *t = FindResource(this->resources, c);

	if(t)	t->resourceID = (id);
}
ResourceType ResourceManager::GetResourceType (const HRESOURCE& resourceData)
{
	ResourceData *t = FindResource(this->resources, resourceData);

	if(t)	return t->resourcetype;

	return ResourceType_INVALID;
}
ResourceType ResourceManager::GetResourceType (const wchar_t c[])
{
	ResourceData *t = FindResource(this->resources, c);

	if(t)	return t->resourcetype;

	return ResourceType_INVALID;
}
const wchar_t* ResourceManager::GetResourceFilename (const HRESOURCE& resourceData)
{
	return FindResourceKey(this->resources, resourceData);
}
HRESOURCE ResourceManager::GetResourceHandle(const wchar_t filename[])
{
	ResourceData *t = FindResource(this->resources, filename);

	if(t)	return t->resource;

	return 0;
}
int ResourceManager::GetResourceId (const HRESOURCE& resourceData)	
{
	ResourceData *t = FindResource(this->resources, resourceData);

	if(t)	return t->resourceID;

	return -1;
}
int ResourceManager::GetResourceId(const wchar_t c[])
{
	ResourceData *t = FindResource(this->resources, c);

	if(t)	return t->resourceID;

	return -1;
}
int	ResourceManager::GetResourceCount(const wchar_t filename[])
{
	ResourceData *t = FindResource(this->resources, filename);

	if(t) return t->referenceCount;

	return 0;
}
int	ResourceManager::GetResourceCount(const HRESOURCE& resource)
{
	ResourceData *t = FindResource(this->resources, resource);

	if(t) return t->referenceCount;

	return 0;
}
int	ResourceManager::GetResourceSize(const wchar_t filename[])
{
	ResourceData *t = FindResource(this->resources, filename);

	if(t) return t->resourceSize;

	return 0;
}
int	ResourceManager::GetResourceSize(const HRESOURCE& resource)
{
	ResourceData *t = FindResource(this->resources, resource);

	if(t) return t->resourceSize;

	return 0;
}