/*
 * $Id: serverbase.h 4661 2017-08-23 20:40:02Z 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.
 */


#ifndef __SERVERBASE_H__
#define __SERVERBASE_H__

#include <wx/dir.h>
//#include <vector>
#include "modulehandle.h"
//#include "moddefs.h"
//#include "kernel.h"


namespace VRUT
{
	///Server base class - abstract, template
	template <class T>
	class ServerBase
	{
	private:
		typedef std::vector<ModuleHandle<T> *> ModuleHandleList;
		///List of available scene import/export modules
		ModuleHandleList moduleHandles;

	protected:
		///Server type ID
		SERVER_ID serverID;
		///Instance offset - all module instance IDs will start from it
		MODULE_INSTANCE_ID instanceOffset;

		///Find module handles with library filename matching \c name (supports \c '*' and \c '?'), case insensitive
		///Does not update available modules by calling \c AddAvailableModules
		///\return IDs of found module handles
		std::vector<MODULE_HANDLE_ID> findModuleHandles(const wxString & name);
		///Get module handle of given id
		ModuleHandle<T> * getModuleHandle(MODULE_HANDLE_ID id) const;
		///Get or create (if not exists) new module instance and get its id, only exact match of the mask is used
		MODULE_ID getOrCreateModuleInstanceWithMask(wxString nameMask, bool createNew=true);
		///Get or create (if not exists) new module instance only if preferred module found (Kernel::preferedModules)
		MODULE_ID getOrCreatePreferedModuleInstance(bool createNew=true);
		/*bool moduleExist(wxString &libName);
		bool getModuleName(wxString &libName);*/
		///Create new module instance and get its id
		///\param[in] libName Create instance of module matching given name, default value searches for preferred names and then any name
		///\return ID of instantiated module or \c MODULE_ID_NONE if no matching module found
		MODULE_ID getOrCreateModuleInstance(const wxString &libName = wxT("*"), bool createNew=true);
		///Get module
		T * getModule(const MODULE_ID & moduleID) const;
		///Release given module
		void releaseModule(const MODULE_ID & id);
		///Release all loaded modules
		void releaseModules();
		///Add module (check if it is already added)
		bool addModule(const wxFileName &fileName, const bool isInternal=false);

	public:
		///Class constructor
		ServerBase(SERVER_ID srvID, MODULE_INSTANCE_ID instOffset = 0);
		///Class destructor
		virtual ~ServerBase();

		///Get instantiated modules with given module name (and instance ID optionally)
		///\param[out] moduleList Will be filled with corresponding modules
		///\param[in] moduleName Module name (or module filename without extension), '*' and '?' allowed
		///\param[in] instanceID If defined one or no module is returned, otherwise instance ID is ignored
		///\return \c false if no module with \c moduleName name exists
		bool GetModules(ModuleList * moduleList, const wxString & moduleName, MODULE_INSTANCE_ID instanceID = MODULE_INSTANCE_ID_NONE);
		///Get filenames of available (instantiated or not) modules
		std::vector<wxFileName> GetAvailableModules();
		///Update lists of currently available modules
		virtual size_t AddAvailableModules(const wxString & pathToModules);
		///Get server ID
		SERVER_ID GetID() const
		{
			return serverID;
		}
		//Find and create module, then start its thread
		virtual MODULE_ID RunModule(const wxString & moduleName, bool reportError=true);
		///Stop modules' main loops
		void StopModules();
		///Cleanup manager data to allow safe application exit
		virtual void ReleaseManagerData();


		friend class Kernel;
		friend class MainWindow;
	};


	template <class T>
	ServerBase<T>::ServerBase(SERVER_ID srvID, MODULE_INSTANCE_ID instOffset) : serverID(srvID), instanceOffset(instOffset)
	{
		if ((serverID==1) || (serverID==4))
		{
#ifdef NAVIGATION_INTERNAL
			addModule(wxFileName(wxT("Navigation")), true);
#endif
		}
		if ((serverID==2) || (serverID==4))
		{
#ifdef IOFHS_INTERNAL
			addModule(wxFileName(wxT("IOFhs")), true);
#endif
#ifdef IOCSB_INTERNAL
			addModule(wxFileName(wxT("IOCsb")), true);
#endif
#ifdef IOOSB_INTERNAL
			addModule(wxFileName(wxT("IOOsb")), true);
#endif
#ifdef IOSTL_INTERNAL
			addModule(wxFileName(wxT("IOStl")), true);
#endif
#ifdef IOVRML_INTERNAL
			addModule(wxFileName(wxT("IOVrml")), true);
#endif
#ifdef IOIGES_INTERNAL
			addModule(wxFileName(wxT("IOIges")), true);
#endif
#ifdef IOOBJ_INTERNAL
			addModule(wxFileName(wxT("IOObj")), true);
#endif
#ifdef IOIMAGE_INTERNAL
			addModule(wxFileName(wxT("IOImage")), true);
#endif
#ifdef IOAC3D_INTERNAL
			addModule(wxFileName(wxT("IOAc3d")), true);
#endif
#ifdef IODOF_INTERNAL
			addModule(wxFileName(wxT("IODof")), true);
#endif
#ifdef IORDF_INTERNAL
			addModule(wxFileName(wxT("IORdf")), true);
#endif
#ifdef IOPLY_INTERNAL
			addModule(wxFileName(wxT("IOPly")), true);
#endif
#ifdef IOFBX_INTERNAL
			addModule(wxFileName(wxT("IOFbx")), true);
#endif
#ifdef IOJT_INTERNAL
			addModule(wxFileName(wxT("IOJt")), true);
#endif
#ifdef IOVRUT_INTERNAL
			addModule(wxFileName(wxT("IOVrut")), true);
#endif
		}
		if ((serverID == 3) || (serverID==4))
		{
#ifdef RENDERGL_INTERNAL
			addModule(wxFileName(wxT("RenderGl")), true);
#endif
#ifdef RAYTRACER_INTERNAL
			addModule(wxFileName(wxT("RayTracer")), true);
#endif
		}
		if (serverID == 4)
		{
#ifdef C3DCONNEXION_INTERNAL
			addModule(wxFileName(wxT("C3Dconnexion")), true);
#endif
#ifdef AUTOTEST_INTERNAL
			addModule(wxFileName(wxT("AutoTest")), true);
#endif
#ifdef CLUSTER_INTERNAL
			addModule(wxFileName(wxT("Cluster")), true);
#endif
#ifdef LIGHTEDITOR_INTERNAL
			addModule(wxFileName(wxT("LightEditor")), true);
#endif
#ifdef MATERIALASSIGN_INTERNAL
			addModule(wxFileName(wxT("MaterialAssign")), true);
#endif
#ifdef MATERIALEDITOR_INTERNAL
			addModule(wxFileName(wxT("MaterialEditor")), true);
#endif
#ifdef CLIPPINGEDITOR_INTERNAL
			addModule(wxFileName(wxT("ClippingEditor")), true);
#endif
#ifdef OPTIMIZE_INTERNAL
			addModule(wxFileName(wxT("Optimize")), true);
#endif
#ifdef SCENEGRAPH_INTERNAL
			addModule(wxFileName(wxT("SceneGraph")), true);
#endif
#ifdef TESSELATOR_INTERNAL
			addModule(wxFileName(wxT("Tesselator")), true);
#endif
#ifdef TEXTUREMANAGER_INTERNAL
			addModule(wxFileName(wxT("TextureManager")), true);
#endif
#ifdef TRACKING_INTERNAL
			addModule(wxFileName(wxT("Tracking")), true);
#endif
#ifdef TRACKINGMANIPULATOR_INTERNAL
			addModule(wxFileName(wxT("TrackingManipulator")), true);
#endif
#ifdef VARIANTS_INTERNAL
			addModule(wxFileName(wxT("Variants")), true);
#endif
#ifdef WIIMOTE_INTERNAL
			addModule(wxFileName(wxT("Wiimote")), true);
#endif
		}

		AddAvailableModules(KERNEL->GetAppPath()+MODULE_PATHS[serverID]);
	}


	template <class T>
	ServerBase<T>::~ServerBase()
	{
		releaseModules();
	}


	template <class T>
	std::vector<MODULE_HANDLE_ID> ServerBase<T>::findModuleHandles(const wxString & name)
	{
		wxString nameU = name.Upper();
#ifndef NDEBUG
		if (!nameU.EndsWith(wxT("_D")))
			nameU.Append(wxT("_D"));
#endif // NDEBUG
		std::vector<MODULE_HANDLE_ID> foundHandles;
		for (MODULE_HANDLE_ID i = 0; i < moduleHandles.size(); i++)
		{
			const wxString modName = moduleHandles[i]->GetFileName().GetName().Upper();
			if (modName.Matches(nameU) || modName.Matches(wxT("LIB") + nameU))
				foundHandles.push_back(i);
		}
		return foundHandles;
	}


	template <class T>
	ModuleHandle<T> * ServerBase<T>::getModuleHandle(MODULE_HANDLE_ID index) const
	{
		return ( index >= moduleHandles.size() ? (ModuleHandle<T> *)NULL : moduleHandles[index] );
	}


	template <class T>
	MODULE_ID ServerBase<T>::getOrCreateModuleInstanceWithMask(wxString nameMask, bool createNew)
	{
		nameMask=nameMask.Upper();
#ifndef NDEBUG
		if (!nameMask.Matches(wxT("*_D")))
			nameMask+=wxT("_D");
#endif // NDEBUG
		for (auto &moduleHandle : moduleHandles)
		{
			wxString libName(moduleHandle->GetFileName().GetName().Upper());
			if (!KERNEL->IsForbidden(libName))
			{
				if (libName.Upper().Matches(nameMask.c_str()))
				{
					if (!createNew)
					{
						ModuleList moduleInstances;
						moduleHandle->GetModules(&moduleInstances);
						if (!moduleInstances.empty())
							return moduleInstances[0].first->GetID();
					}
					MODULE_INSTANCE_ID modInstID = moduleHandle->InstantiateModule();
					if (modInstID != MODULE_INSTANCE_ID_NONE)
						return MODULE_ID(serverID, moduleHandle->GetID(), modInstID);
					else
					{
						LOGWARNING(SERVER_CLASS_NAMES[serverID]+wxT(" module '")+moduleHandle->GetFileName().GetName()+wxT("' is invalid, adding to forbidden list"));
						KERNEL->ForbidModule(moduleHandle->GetFileName().GetName());
					}
				}
			}
		}
		return MODULE_ID_NONE;
	}


	template <class T>
	MODULE_ID ServerBase<T>::getOrCreatePreferedModuleInstance(bool createNew)
	{
		const Kernel::ModuleNamesList * preferedModules = KERNEL->GetPreferedModules();
		//try exact match
		for (auto &preferedModule : *preferedModules)
		{
			MODULE_ID modID = getOrCreateModuleInstanceWithMask(preferedModule, createNew);
			if (modID != MODULE_ID_NONE)
				return modID;
		}
		//try modules starting with the name
		for (auto &preferedModule : *preferedModules)
		{
			MODULE_ID modID = getOrCreateModuleInstanceWithMask(preferedModule + wxT("*"), createNew);
			if (modID != MODULE_ID_NONE)
				return modID;
		}
		//try modules containing the name
		for (auto &preferedModule : *preferedModules)
		{
			MODULE_ID modID = getOrCreateModuleInstanceWithMask(wxT("*") + preferedModule + wxT("*"), createNew);
			if (modID != MODULE_ID_NONE)
				return modID;
		}
		return MODULE_ID_NONE;
	}

/*
	template <class T>
	bool ServerBase<T>::moduleExist(wxString &nameMask)
	{
		wxString nameMaskTmp=nameMask.Upper();
#ifndef NDEBUG
		if (!nameMaskTmp.Matches(wxT("*_D")))
			nameMaskTmp+=wxT("_D");
#endif // NDEBUG
		for (typename ModuleHandleList::iterator it = moduleHandles.begin(); it != moduleHandles.end(); it++)
		{
			wxString libName((*it)->GetFileName().GetName().Upper());
			if (!KERNEL->IsForbidden(libName))
			{
				if (libName.Upper().Matches(nameMaskTmp.c_str()))
				{
					nameMask=(*it)->GetFileName().GetName();
					return true;
				}
			}
		}
		return false;
	}


	template <class T>
	bool ServerBase<T>::getModuleName(wxString &libName)
	{
		if (libName.IsSameAs(wxT("*")) || libName.empty())
		{
			///Try preferred
			const Kernel::ModuleNamesList * preferedModules = KERNEL->GetPreferedModules();
			if (!preferedModules->empty())
			{
				wxString libNameTmp(*preferedModules->begin());
				if (moduleExist(libNameTmp))
				{
					libName=libNameTmp;
					return true;
				}
			}

			///Try any
			wxString libNameTmp(wxT("*"));
			if (moduleExist(libNameTmp))
			{
				libName=libNameTmp;
				return true;
			}
		}
		else
		{
			//try exact match
			wxString libNameTmp(libName);
			if (moduleExist(libNameTmp))
			{
				libName=libNameTmp;
				return true;
			}
		}
		//module not found, try to load new modules
		if (AddAvailableModules(KERNEL->GetAppPath()+MODULE_PATHS[serverID]))
			return getModuleName(libName);
		return false;
	}*/


	template <class T>
	MODULE_ID ServerBase<T>::getOrCreateModuleInstance(const wxString &libName, bool createNew)
	{
		if (libName.IsSameAs(wxT("*")))
		{
			///Try preferred
			MODULE_ID modID = getOrCreatePreferedModuleInstance(createNew);
			if (modID != MODULE_ID_NONE)
				return modID;

			///Try any
			modID=getOrCreateModuleInstanceWithMask(libName, createNew);
			if (modID!=MODULE_ID_NONE)
				return modID;
		}
		else
		{
			//try exact match
			MODULE_ID modID=getOrCreateModuleInstanceWithMask(libName, createNew);
			if (modID!=MODULE_ID_NONE)
				return modID;
			//try modules starting with the name
			modID=getOrCreateModuleInstanceWithMask(libName+wxT("*"), createNew);
			if (modID!=MODULE_ID_NONE)
				return modID;
			//try modules containing the name
			modID=getOrCreateModuleInstanceWithMask(wxT("*")+libName+wxT("*"), createNew);
			if (modID!=MODULE_ID_NONE)
				return modID;
		}
		//module not found, try to load new modules
		if (AddAvailableModules(KERNEL->GetAppPath()+MODULE_PATHS[serverID]))
			return getOrCreateModuleInstance(libName, createNew);
		return MODULE_ID_NONE;
	}

	template <class T>
	T * ServerBase<T>::getModule(const MODULE_ID & moduleID) const
	{
		ModuleHandle<T> * moduleHandle = getModuleHandle(moduleID.GetHandleID());
		if (moduleHandle)
			return moduleHandle->GetModule(moduleID.GetInstanceID());

		return (T *)NULL;
	}


	template <class T>
	void ServerBase<T>::releaseModule(const MODULE_ID & id)
	{
		ModuleHandle<T> * modHandle = getModuleHandle(id.GetHandleID());
		if (modHandle)
			modHandle->Release(id.GetInstanceID());
	}


	template <class T>
	bool ServerBase<T>::GetModules(ModuleList * moduleList, const wxString & moduleName, MODULE_INSTANCE_ID instanceID)
	{
		bool found = false;
		for (auto &moduleHandle : moduleHandles)
		{
			if (moduleHandle && (moduleHandle->IsLoaded() || moduleHandle->IsInternal()))
			{
				wxString modFname = moduleHandle->GetFileName().GetName();
				wxString modName = moduleHandle->GetName();
				if (modFname.Matches(moduleName) || (!modName.IsEmpty() && modName.Matches(moduleName)))
				{
					if (instanceID != MODULE_INSTANCE_ID_NONE)
					{
						Module * module = moduleHandle->GetModule(instanceID);
						if (module)
							moduleList->push_back(ModulePair(module, instanceID));
					}
					else
						moduleHandle->GetModules(moduleList);
					found = true;
				}
			}
		}
		return found;
	}


	template <class T>
	std::vector<wxFileName> ServerBase<T>::GetAvailableModules()
	{
		std::vector<wxFileName> mods;
		for (auto &moduleHandle : moduleHandles)
			mods.push_back(moduleHandle->GetFileName());

		return mods;
	}


	template<class T>
	bool ServerBase<T>::addModule(const wxFileName &fileName, const bool isInternal)
	{
		wxString filename2(fileName.GetFullPath());
		bool alreadyAdded = false;
		for (auto &moduleHandle : moduleHandles)
		{
			if (filename2 == moduleHandle->GetFileName().GetFullPath())
			{
				alreadyAdded = true;
				break;
			}
		}
		if (!alreadyAdded)
		{
			if (isInternal)
			{
#ifndef NDEBUG
				wxFileName fileNameTmp(fileName.GetName()+wxT("_D"));
				moduleHandles.push_back(new ModuleHandle<T>(MODULE_HANDLE_ID(moduleHandles.size()), serverID, fileNameTmp, instanceOffset, isInternal));
#else
				moduleHandles.push_back(new ModuleHandle<T>(MODULE_HANDLE_ID(moduleHandles.size()), serverID, fileName, instanceOffset, isInternal));
#endif
				LOG(fileName.GetFullPath(wxPATH_NATIVE));
				return true;
			}
#ifndef NDEBUG
			else if (fileName.GetName().Matches(wxT("*_d")))
#else
			else if (!fileName.GetName().Matches(wxT("*_d")))
#endif // NDEBUG
			{
				moduleHandles.push_back(new ModuleHandle<T>(MODULE_HANDLE_ID(moduleHandles.size()), serverID, fileName, instanceOffset, isInternal));
				LOG(fileName.GetFullPath(wxPATH_NATIVE));
				return true;
			}
		}
		return false;
	}


	template <class T>
	size_t ServerBase<T>::AddAvailableModules(const wxString & pathToModules)
	{
		LOGVERBOSE(SERVER_CLASS_NAMES[serverID] + wxT("Searching for available modules"));

		wxString mask = wxT("*") + wxDynamicLibrary::CanonicalizeName(wxT(""), wxDL_MODULE);
		unsigned modulesAdded = 0;
		wxDir dir(pathToModules);
		if (dir.IsOpened())
		{
			wxString filename;
			bool cont = dir.GetFirst(&filename, mask, wxDIR_FILES);
			while (cont)
			{
				if (addModule(wxFileName(pathToModules + wxFileName::GetPathSeparator() + filename)))
					modulesAdded++;
				cont = dir.GetNext(&filename);
			}
		}

		if (modulesAdded)
			LOG(SERVER_CLASS_NAMES[serverID]+wxString::Format(wxT("%i "), modulesAdded) + wxPLURAL("module", "modules", modulesAdded) + wxT(" added"));

		return modulesAdded;
	}


	template <class T>
	void ServerBase<T>::releaseModules()
	{
		SAFE_DELETE_ARR_EACH(moduleHandles, moduleHandles.size());
		moduleHandles.clear();
	}

	template <class T>
	void ServerBase<T>::StopModules()
	{
		for (auto &moduleHandle : moduleHandles)
		{
			ModuleList moduleList;
			moduleHandle->GetModules(&moduleList);
			for (auto &module : moduleList)
			{
				ModuleHandle<T>::StopModuleThread(module.first, true);
			}
		}
	}

	template <class T>
	MODULE_ID ServerBase<T>::RunModule(const wxString & moduleName, bool reportError)
	{
		///Create module instance
		MODULE_ID moduleID = getOrCreateModuleInstance(moduleName);
		Module * module = getModule(moduleID);
		if (!module)
		{
			if (reportError)
				LOGERROR(wxT("<ServerBase<T>>Module '")+moduleName+wxT("' could not be started"));
			return MODULE_ID_NONE;
		}
		ModuleHandle<Module>::StartModuleThread(module);

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

	template <class T>
	void ServerBase<T>::ReleaseManagerData()
	{
		releaseModules();
	}
}

#endif
