/*
	Copyright (C) 2005-2007 Feeling Software Inc.
	Portions of the code are:
	Copyright (C) 2005-2007 Sony Computer Entertainment America
	
	MIT License: http://www.opensource.org/licenses/mit-license.php
*/

#include "StdAfx.h"
#include "FColladaPlugin.h"
#include "FCDocument/FCDocument.h"
#include "FCDocument/FCDExtra.h"
#include "FUtils/FUFileManager.h"
#include "FUtils/FUPlugin.h"
#include "FUtils/FUPluginManager.h"
#include "FUtils/FUUri.h"

//
// FColladaPlugin
//
ImplementObjectType(FCPExtraTechnique);

//
// FArchivingPlugin
//
ImplementObjectType(FCPArchive);

//
// FColladaPluginManager
//
ImplementObjectType(FColladaPluginManager);

FColladaPluginManager::FColladaPluginManager()
:	loader(NULL)
{
	// Create the plug-in loader and create all the FCollada plug-ins.
	loader = new FUPluginManager(FC("*.fcp|*.fvp"));
	loader->LoadPlugins(FUPlugin::GetClassType());

	// Retrieve and sort the plug-ins.
	size_t pluginCount = loader->GetLoadedPluginCount();
	for (size_t i = 0; i < pluginCount; ++i)
	{
		FUPlugin* _plugin = loader->GetLoadedPlugin(i);
		if (_plugin->HasType(FCPExtraTechnique::GetClassType()))
		{
			FCPExtraTechnique* plugin = (FCPExtraTechnique*) _plugin;
			const char* profileName = plugin->GetProfileName();
			if (profileName != NULL && profileName[0] != 0)
			{
				extraTechniquePlugins.push_back(plugin);
			}
		}
		else if (_plugin->HasType(FCPArchive::GetClassType()))
		{
			archivePlugins.push_back((FCPArchive*)_plugin);
		}
	}

}

FColladaPluginManager::~FColladaPluginManager()
{
	SAFE_DELETE(loader);
}

bool FColladaPluginManager::RegisterPlugin(FUPlugin* plugin)
{
	if (plugin != NULL)
	{
		if (plugin->HasType(FCPArchive::GetClassType()))
		{
			archivePlugins.push_back((FCPArchive*) plugin);
			return true;
		}
		else if (plugin->HasType(FCPExtraTechnique::GetClassType()))
		{
			FCPExtraTechnique* extraPlugin = (FCPExtraTechnique*) plugin;
			const char* profileName = extraPlugin->GetProfileName();
			if (profileName != NULL && *profileName != 0)
			{
				extraTechniquePlugins.push_back(extraPlugin);
				return true;
			}
		}
	}
	return false;
}

void FColladaPluginManager::CreateExtraTechniquePluginMap(FCPExtraMap& map)
{
	for (FCPExtraTechnique** itP = extraTechniquePlugins.begin(); itP != extraTechniquePlugins.end(); ++itP)
	{
		const char* profileName = (*itP)->GetProfileName();
		uint32 crc = FUCrc32::CRC32(profileName);
		map.insert(crc, *itP);
	}
}

bool FColladaPluginManager::LoadDocumentFromFile(FCDocument* document, const fchar* filename)
{
	FCPArchive* archiver = FindArchivePlugin(filename);
	if (archiver != NULL)
	{
		bool success = archiver->ImportFile(filename, document);
		if (success) PostImportDocument(document);
		return success;
	}
	else
	{
		FUError::Error(FUError::ERROR_LEVEL, FUError::NO_MATCHING_PLUGIN, 0);
		return false;
	}
}

bool FColladaPluginManager::LoadDocumentFromMemory(const fchar* filename, FCDocument* document, void* data, size_t length)
{
	FCPArchive* archiver = FindArchivePlugin(filename);
	if (archiver != NULL)
	{
		bool success = archiver->ImportFileFromMemory(filename, document, data, length);
		if (success) PostImportDocument(document);
		return success;
	}
	else
	{
		FUError::Error(FUError::ERROR_LEVEL, FUError::NO_MATCHING_PLUGIN, 0);
		return false;
	}
}

bool FColladaPluginManager::SaveDocumentToFile(FCDocument* document, const fchar* filename)
{
	FCPArchive* archiver = FindArchivePlugin(filename);
	if (archiver != NULL)
	{
		FCDETechniqueList techniques;
		PreExportDocument(document, techniques);
		bool success = archiver->ExportFile(document, filename);
		PostExportDocument(document, techniques);
		return success;
	}
	else
	{
		FUError::Error(FUError::ERROR_LEVEL, FUError::NO_MATCHING_PLUGIN, 0);
		return false;
	}
}

FCPArchive* FColladaPluginManager::FindArchivePlugin(const fchar* filename)
{
	FUUri fileUri(filename);
	fstring extension = FUFileManager::GetFileExtension(fileUri.GetPath());

	for (size_t i = 0; i < archivePlugins.size(); ++i)
	{
		FCPArchive* curArchive = archivePlugins[i];
		for (int j = 0; j < curArchive->GetSupportedExtensionsCount(); ++j)
		{
			fstring targetExt = FUStringConversion::ToFString(curArchive->GetSupportedExtensionAt(j));
			if (IsEquivalentI(extension, targetExt))
			{
				return curArchive;
			}
		}
	}

	return NULL;
}

void FColladaPluginManager::PostImportDocument(FCDocument* document)
{
	// Create a map of the plugin profile names in order to process the extra trees faster.
	FCPExtraMap pluginMap;
	CreateExtraTechniquePluginMap(pluginMap);
	if (pluginMap.empty()) return;

	FCDExtraSet& extraTrees = document->GetExtraTrees();
	for (FCDExtraSet::iterator itE = extraTrees.begin(); itE != extraTrees.end(); ++itE)
	{
		size_t typeCount = itE->first->GetTypeCount();
		for (size_t i = 0; i < typeCount; ++i)
		{
			FCDEType* type = itE->first->GetType(i);
			size_t techniqueCount = type->GetTechniqueCount();
			for (size_t j = 0; j < techniqueCount; ++j)
			{
				FCDETechnique* technique = type->GetTechnique(j);
				uint32 crc = FUCrc32::CRC32(technique->GetProfile());
				FCPExtraMap::iterator itP = pluginMap.find(crc);
				if (itP != pluginMap.end())
				{
					// Call the corresponding plug-in to process this information.
					FUTrackable* customized = itP->second->ReadFromArchive(technique, itE->first->GetParent());
					if (customized != NULL)
					{
						technique->SetPluginObject(customized);
						while (technique->GetChildNodeCount() != 0) technique->GetChildNode(technique->GetChildNodeCount() - 1)->Release();
					}
				}
			}
		}
	}
}

void FColladaPluginManager::PreExportDocument(FCDocument* document, FCDETechniqueList& techniques)
{
	// Create a map of the plugin profile names in order to process the extra trees faster.
	FCPExtraMap pluginMap;
	CreateExtraTechniquePluginMap(pluginMap);
	if (pluginMap.empty()) return;

	FCDExtraSet& extraTrees = document->GetExtraTrees();
	for (FCDExtraSet::iterator itE = extraTrees.begin(); itE != extraTrees.end(); ++itE)
	{
		size_t typeCount = itE->first->GetTypeCount();
		for (size_t i = 0; i < typeCount; ++i)
		{
			FCDEType* type = itE->first->GetType(i);
			size_t techniqueCount = type->GetTechniqueCount();
			for (size_t j = 0; j < techniqueCount; ++j)
			{
				FCDETechnique* technique = type->GetTechnique(j);
				FUTrackable* customized = technique->GetPluginObject();
				if (customized != NULL)
				{
					uint32 crc = FUCrc32::CRC32(technique->GetProfile());
					FCPExtraMap::iterator itP = pluginMap.find(crc);
					if (itP != pluginMap.end())
					{
						techniques.push_back(technique);
						itP->second->WriteToArchive(technique, customized);
					}
				}
			}
		}
	}
}

void FColladaPluginManager::PostExportDocument(FCDocument* UNUSED(document), FCDETechniqueList& techniques)
{
	for (FCDETechnique** itT = techniques.begin(); itT != techniques.end(); ++itT)
	{
		FUAssert((*itT)->GetPluginObject() != NULL, continue);
		while ((*itT)->GetChildNodeCount() != 0) (*itT)->GetChildNode((*itT)->GetChildNodeCount() - 1)->Release();
	}
}
