/*
 * $Id: iomanager.cpp 4915 2018-04-13 14:30:42Z antonin.misek-skoda-auto.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 *
 * Purpose      :
 *     Long description of what the file is for.
 */

#include "kernel.h"
#include "iomanager.h"
//#include "kernel.h"


using namespace VRUT;


IOManager::IOManager() : ServerBase<IOModule>(IO_MANAGER)
{
}


IOManager::~IOManager()
{
}


bool IOManager::extensionIsInList(const wxString & ext, const wxString &extList)
{
	TextParser textParser(extList.utf8_str());
	while (!textParser.IsEmpty(" ,;|"))
	{
		if (textParser.GetWxStringUtf8(" ,;|").IsSameAs(ext, false))
			return true;
	}
	return false;
}


IOModule * IOManager::moduleSupportsExt(MODULE_HANDLE_ID modulehandleID, const wxString &ext)
{
	if ((extensionsCache.size()>modulehandleID) && !extensionsCache[modulehandleID].empty())
	{
		if (extensionIsInList(ext, extensionsCache[modulehandleID]))
		{
			ModuleHandle<IOModule> * handle = getModuleHandle(modulehandleID);
			if (handle)
				return getModule(getOrCreateModuleInstance(handle->GetFileName().GetName(), false));//TODO: after refactoring of IO modules change to false
		}
		return NULL;
	}
	ModuleHandle<IOModule> * handle = getModuleHandle(modulehandleID);
	if (handle)
	{
		MODULE_ID modID = getOrCreateModuleInstance(handle->GetFileName().GetName(), false);//TODO: after refactoring of IO modules change to false
		IOModule * mod = getModule(modID);
		if (mod)
		{
			if (extensionsCache.size()<=modulehandleID)
				extensionsCache.resize(modulehandleID+1, wxT(""));
			extensionsCache[modulehandleID]=mod->GetSupportedExts();
			if (mod->IsDone())
				ModuleHandle<IOModule>::StartModuleThread(mod);
			if (extensionIsInList(ext, extensionsCache[modulehandleID]))
				return mod;
		}
	}
	return NULL;
}


IOModule * IOManager::findModule(const wxString & extension)
{
	///Try preferred module
	MODULE_ID modID = getOrCreatePreferedModuleInstance(false);//TODO: after refactoring of IO modules change to false
	IOModule * mod = getModule(modID);
	if (mod)
	{
		if (extensionIsInList(extension, mod->GetSupportedExts()))
			return mod;
		LOGVERBOSE(SERVER_CLASS_NAMES[serverID]+wxT("Module '")+mod->GetName()+wxT("' available and valid but without support for '")+extension+wxT("' file format, skipping"));
		releaseModule(modID);
	}

	std::vector<MODULE_HANDLE_ID> handles = findModuleHandles(wxT("*"));

	///Try scene IO modules with name containing extension
	wxString lookFor(wxT("*")+extension.Upper()+wxT("*"));
	for (size_t i=0; i<handles.size(); i++)
	{
		ModuleHandle<IOModule> * handle = getModuleHandle(handles[i]);
		if (handle->GetFileName().GetName().Upper().Matches(lookFor))
		{
			mod = moduleSupportsExt(handles[i], extension);
			if (mod)
				return mod;
			handles.erase(handles.begin()+i);
		}
	}

	///Try other scene IO modules
	for (auto &handle : handles)
	{
		mod = moduleSupportsExt(handle, extension);
		if (mod)
			return mod;
	}

	//module not found, try to load new modules
	if (AddAvailableModules(KERNEL->GetAppPath()+MODULE_PATHS[serverID]))
		return findModule(extension);

	return (IOModule *)NULL;
}


SCENE_ID IOManager::ImportScene(const wxString &fname, SCENE_ID sceneID, NODE_ID parentID)
{
	LOG(SERVER_CLASS_NAMES[serverID] + wxT("Importing scene from '") + fname + wxT("'"));

	wxFileName wxFname(fname);
	if (!wxFname.FileExists())
	{
		LOGERROR(wxT("<IOModule>File '") + fname + wxT("' not found"));
		return SCENE_ID_NONE;
	}

	///Is bzip2 or gzip archive?
	const wxFilterClassFactory * ffactory = wxFilterClassFactory::Find(wxFname.GetFullName(), wxSTREAM_FILEEXT);
	if (ffactory)
		wxFname.SetFullName(ffactory->PopExtension(wxFname.GetFullName()));
	///Is tar or zip archive?
	const wxArchiveClassFactory * afactory = wxArchiveClassFactory::Find(wxFname.GetFullName(), wxSTREAM_FILEEXT);
	if (afactory)
	{
		wxFileInputStream fis(wxFname.GetFullPath());
		wxArchiveInputStream * arcin(afactory->NewStream(fis));
		if (arcin)
		{
			wxArchiveEntry * entry(arcin->GetNextEntry());
			if (entry)
			{
				wxFname = wxFileName(entry->GetInternalName());
				delete entry;
			}
			delete arcin;
		}
	}

	IOModule * module = findModule(wxFname.GetExt());
	if (!module)
	{
		LOGERROR(SERVER_CLASS_NAMES[serverID] + wxT("File '") + fname + wxT("' not supported"));
		return SCENE_ID_NONE;
	}

	if (module->IsDone())
		ModuleHandle<IOModule>::StartModuleThread(module);
	if (sceneID==SCENE_ID_NONE)
	{
		sceneID = KERNEL->sceneManager->GetNewSceneID();
		parentID = NODE_ID_NONE;
	}
	smart_pointer_v2<Scene> scene;
	KERNEL->sceneManager->GetScene(scene, sceneID);
	if (!scene)
	{
		wxFileName fileName(fname);
        KERNEL->sceneManager->AddScene(fileName.GetFullName() + wxString::Format(wxT(".%lu"), (unsigned long)sceneID), sceneID);
		KERNEL->sceneManager->GetScene(scene, sceneID);
		if (!scene)
			return sceneID;//TODO: or SCENE_ID_NONE?
	}

	smart_pointer<SceneNode> root;
	scene->GetNode(root, parentID);
	if (!root)
		parentID=scene->GetRootID();
	NODE_ID	rootID=scene->Insert(smart_pointer<SceneNode>(new SceneNode(fname, SceneNode::FILE_ROOT)), parentID);
	module->PostEvent(GET_EVT_IO_SCENE_IMPORT_DO(sceneID, rootID, fname));

	return sceneID;
}

void IOManager::ExportScene(smart_pointer_v2<Scene> &scene, const NODE_ID rootID, const wxString &fname)
{
	if (!scene)
	{
		LOGERROR(wxT("IOManager::ExportScene: Cannot save scene, scene not exists"));
		return;
	}

	smart_pointer<SceneNode> snode;
	scene->GetNode(snode, rootID);
	if (!snode)
		scene->GetNode(snode, scene->GetRootID());

	if (!snode)
	{
		LOGERROR(wxT("Cannot save scene, root node not exists"));
		return;
	}

	wxFileName wxFname(fname);
	if (fname.empty())
	{
		wxFname=snode->GetName();
		if (snode->GetType()!=SceneNode::FILE_ROOT)
		{
			smart_pointer<SceneNode> pnode=snode;
			do 
			{
				scene->GetNode(pnode, pnode->GetParentID());
			} while (pnode && (pnode->GetType()!=SceneNode::FILE_ROOT));
			if (pnode)
			{
				wxFname=pnode->GetName();
				wxFname.SetName(snode->GetName());
			}
		}
	}
	
	LOG(SERVER_CLASS_NAMES[serverID]+wxString::Format(wxT(" Saving scene (sceneID=%lu, nodeID=%lu) to '"), (unsigned long)scene->GetID(), (unsigned long)snode->GetID())+wxFname.GetFullPath()+wxT("'"));
	IOModule * module = findModule(wxFname.GetExt());
	if (!module)
	{
		LOGERROR(SERVER_CLASS_NAMES[serverID]+wxT(" File '")+wxFname.GetFullPath()+wxT("' not supported"));
		return;
	}
	wxStopWatch sw;
	if (module->ExportScene(scene, snode->GetID(), wxFname.GetFullPath()))
		LOG(SERVER_CLASS_NAMES[serverID]+wxT(" Scene saved in ")+TextWriter::WriteToWxString(0.001f * sw.Time())+wxT(" secs"));
}


wxString IOManager::GetFileSelector(bool addDefaultWildcard)
{
	///Cache all extensions, the first call can be slow, because all modules must be loaded
	std::vector<MODULE_HANDLE_ID> handles = findModuleHandles(wxT("*"));
	for (auto &handle : handles)
		moduleSupportsExt(handle, wxT("dummyExtension"));

	wxString fileSelector;
	wxString allSupported;

	for (size_t i=0; i<extensionsCache.size(); i++)
	{
		// file selector example "BMP and GIF files (*.bmp;*.gif)|*.bmp;*.gif|PNG files (*.png)|*.png"
		if (!extensionsCache[i].empty())
		{
			bool extensionsAlreadyIncluded = false;

			// check for duplicity within previous cached extensions (some modules are added multiple times -> duplicated records in extensions cache)
			for (size_t j=0; j<i; j++)
			{
				if (extensionsCache[i] == extensionsCache[j])
				{
					extensionsAlreadyIncluded = true;
					break;
				}
			}

			if (extensionsAlreadyIncluded)
				continue;

			// add all extensions supported by module i
			TextParser textParser(extensionsCache[i].utf8_str());
			wxString moduleExtensions;
			while (!textParser.IsEmpty(" ,;|"))
			{
				if (!moduleExtensions.empty())
					moduleExtensions += wxT(";");
				moduleExtensions += wxT("*.") + textParser.GetWxStringUtf8(" ,;|").MakeLower();
			}

			wxString moduleName = getModuleHandle(i)->GetName();
			if (!fileSelector.empty())
				fileSelector += wxT("|");
			fileSelector += wxT("Module ") + moduleName + wxT(" (") + moduleExtensions + wxT(")|") + moduleExtensions;
			//build list of all supported extensions
			if (!allSupported.empty())
				allSupported+=wxT(";");
			allSupported+=moduleExtensions;
		}
	}
	// add all supported files filter
	if (!allSupported.empty())
		fileSelector = wxT("All supported (") + allSupported + wxT(")|") + allSupported + wxT("|") + fileSelector;
	// add all files filter
	if (!fileSelector.empty())
		fileSelector += wxT("|");
	fileSelector += wxT("All files (*.*)|*.*");

	return fileSelector;
}

MODULE_ID IOManager::RunModule(const wxString & moduleName, bool reportError)
{
	MODULE_ID moduleID = getOrCreateModuleInstance(moduleName, false);//TODO: after refactoring of IO modules change to false
	Module * module = getModule(moduleID);
	if (!module)
	{
		if (reportError)
			LOGERROR(wxT("<IOManager::Module '") + moduleName + wxT("' could not be started"));
		return MODULE_ID_NONE;
	}
	if (module->IsDone())
		ModuleHandle<IOModule>::StartModuleThread(module);

	KERNEL->GetMessageSink()->PostEvent(GET_EVT_MODULE_RUN_DONE(moduleName, moduleID));
	return moduleID;
}
