/*
 * $Id: modulehandle.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 __MODULE_HANDLE__H__
#define __MODULE_HANDLE__H__

#include <wx/dynlib.h>
//#include <wx/filename.h>
//#include "moddefs.h"
//#include "common.h"
//#include "flexilog.h"
#include "module.h"
//#include "kernel.h"
#include "rendermodule.h"
#ifdef NAVIGATION_INTERNAL
#include "../base_modules/navigation/navigation.h"
#endif
#ifdef IOFHS_INTERNAL
#include "../base_modules/iofhs/iofhs.h"
#endif
#ifdef IOCSB_INTERNAL
#include "../base_modules/iocsb/iocsb.h"
#endif
#ifdef IOOSB_INTERNAL
#include "../base_modules/ioosb/ioosb.h"
#endif
#ifdef IOSTL_INTERNAL
#include "../base_modules/iostl/iostl.h"
#endif
#ifdef IOVRML_INTERNAL
#include "../base_modules/iovrml/iovrml.h"
#endif
#ifdef IOIGES_INTERNAL
#include "../base_modules/ioiges/ioiges.h"
#endif
#ifdef IOOBJ_INTERNAL
#include "../base_modules/ioobj/ioobj.h"
#endif
#ifdef IOIMAGE_INTERNAL
#include "../base_modules/ioimage/ioimage.h"
#endif
#ifdef IOAC3D_INTERNAL
#include "../base_modules/ioac3d/ioac3d.h"
#endif
#ifdef IODOF_INTERNAL
#include "../base_modules/iodof/iodof.h"
#endif
#ifdef IORDF_INTERNAL
#include "../base_modules/iordf/iordf.h"
#endif
#ifdef IOPLY_INTERNAL
#include "../base_modules/ioply/ioply.h"
#endif
#ifdef IOFBX_INTERNAL
#include "../base_modules/iofbx/iofbx.h"
#endif
#ifdef IOJT_INTERNAL
#include "../base_modules/iojt/iojt.h"
#endif
#ifdef IOVRUT_INTERNAL
#include "../base_modules/iovrut/iovrut.h"
#endif
#ifdef RENDERGL_INTERNAL
#include "../base_modules/rendergl/rendergl.h"
#endif
#ifdef RAYTRACER_INTERNAL
#include "../base_modules/RayTracer/RayTracer.h"
#endif
#ifdef C3DCONNEXION_INTERNAL
#include "../base_modules/3Dconnexion/3Dconnexion.h"
#endif
#ifdef AUTOTEST_INTERNAL
#include "../base_modules/AutoTest/AutoTest.h"
#endif
#ifdef CLUSTER_INTERNAL
#include "../base_modules/cluster/Cluster.h"
#endif
#ifdef LIGHTEDITOR_INTERNAL
#include "../base_modules/LightEditor/LightEditor.h"
#endif
#ifdef MATERIALASSIGN_INTERNAL
#include "../base_modules/materialassign/materialassign.h"
#endif
#ifdef MATERIALEDITOR_INTERNAL
#include "../base_modules/materialeditor/materialeditor.h"
#endif
#ifdef CLIPPINGEDITOR_INTERNAL
#include "../base_modules/ClippingEditor/ClippingEditor.h"
#endif
#ifdef OPTIMIZE_INTERNAL
#include "../base_modules/optimize/optimize.h"
#endif
#ifdef SCENEGRAPH_INTERNAL
#include "../base_modules/SceneGraph/SceneGraph.h"
#endif
#ifdef TESSELATOR_INTERNAL
#include "../base_modules/tesselator/tesselator.h"
#endif
#ifdef TEXTUREMANAGER_INTERNAL
#include "../base_modules/TextureManager/TextureManager.h"
#endif
#ifdef TRACKING_INTERNAL
#include "../base_modules/tracking/tracking.h"
#endif
#ifdef TRACKINGMANIPULATOR_INTERNAL
#include "../base_modules/trackingmanipulator/trackingmanipulator.h"
#endif
#ifdef VARIANTS_INTERNAL
#include "../base_modules/variants/variants.h"
#endif
#ifdef WIIMOTE_INTERNAL
#include "../base_modules/Wiimote/Wiimote.h"
#endif


#ifndef EXPORT_VRUT_MODULE_FUNCTIONS
	IMPORT_CREATE_INSTANCE_FUNC;
	IMPORT_RELEASE_INSTANCE_FUNC;
#endif

#define THREAD_WAIT_INTERVAL 50
#define THREAD_WAIT_TIMEOUT  10000


namespace VRUT
{
	class Kernel;

	typedef std::pair<Module *, MODULE_INSTANCE_ID> ModulePair;
	typedef std::vector<ModulePair> ModuleList;

	///Data container and management of dynamic library with module instances
	template <typename T>
	class ModuleHandle
	{
	protected:
		///Module handle ID
		MODULE_HANDLE_ID hid;
		///Server owner ID
		SERVER_ID serverID;
		///File information of dynamic library
		wxFileName fileName;
		///Handled module name
		wxString moduleName;
		///Dynamic library
		wxDynamicLibrary dlib;
		///Module instances list
		std::vector<T *> moduleInstances;
		///Number of active module instances
		unsigned numActModuleInstances;
		///Module is build in
		bool isInternal;

	public:
		///Class constructor
		ModuleHandle(MODULE_HANDLE_ID _hid, SERVER_ID srvID, const wxFileName & fname, MODULE_INSTANCE_ID instOffset = 0, bool isInternal=false)
				: hid(_hid), serverID(srvID), fileName(fname), isInternal(isInternal)
		{
			if (instOffset)
				moduleInstances.resize(instOffset);
			numActModuleInstances = 0;
		}
		///Class destructor
		~ModuleHandle()
		{
			Release();
			wxASSERT_MSG(numActModuleInstances == 0, wxT("<ModuleHandle>Not all module instances released"));
		}

		///Get module handle ID
		MODULE_HANDLE_ID GetID() const
		{
			return hid;
		}

		///Load associated dynamic library
		bool Load()
		{
			if (dlib.IsLoaded())
				return true;

			bool ret = dlib.Load(fileName.GetFullPath());
			if (ret)
				LOG(wxT("<ModuleHandle>Loaded '") + fileName.GetFullPath() + wxT("'"));
			return ret;
		}

		///Get module's \c MODULE_INSTANCE_ID or \c MODULE_INSTANCE_ID_NONE if not found
		MODULE_INSTANCE_ID GetInstanceID(const T * module) const
		{
			typename std::vector<T *>::const_iterator it;
			MODULE_INSTANCE_ID id = 0;
			for (it = moduleInstances.begin(); it != moduleInstances.end(); it++, id++)
				if (*it == module)
					return id;
			return MODULE_INSTANCE_ID_NONE;
		}

		///Release module instance and unload library if no instances left
		///\param[in] id Module instance to release, release all if not defined (default)
		void Release(MODULE_INSTANCE_ID id = MODULE_INSTANCE_ID_NONE)
		{
			RELEASE_INSTANCE_FUNC_PROTO release = NULL;
			if (!isInternal)
			{
				if (dlib.IsLoaded())
				{
					release = RELEASE_INSTANCE_FUNC_PROTO(dlib.GetSymbol(wxT("RELEASE_INSTANCE_FUNC")));
					if (release == (RELEASE_INSTANCE_FUNC_PROTO)NULL)
					{
						LOGERROR(wxT("<ModuleHandle>Could not release module instance, library does not provide release function, module '")+
								fileName.GetFullName()+wxT("' is corrupt"));
						return;
					}
				}
			}

			if (id != MODULE_INSTANCE_ID_NONE)
			{
				if (id < moduleInstances.size() && moduleInstances[id])
				{
					KERNEL->GetMessageSink()->UnregisterListener(moduleInstances[id]->GetID());
					if (!StopModuleThread(moduleInstances[id]))
						LOGWARNING(wxT("<ModuleHandle>Module has not finished in time, forcing release"));

					Parameter::ParameterIdentifier pi(moduleInstances[id]->GetName(), wxT("*"), id);
					KERNEL->GetMessageSink()->ReleaseEventsFromModule(pi);
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_UNREGISTER(pi));
#ifndef VRUT_HPC
					KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_GUIUNREGISTER(pi));
#endif

					if (isInternal)
						delete moduleInstances[id];
					else if (release)
						release(moduleInstances[id]);
					moduleInstances[id] = (T *) NULL;
					numActModuleInstances--;
				}
			}
			else
			{
				wxString modName;
				for (auto &moduleInstance : moduleInstances)
				{
					if (moduleInstance)
					{
						KERNEL->GetMessageSink()->UnregisterListener(moduleInstance->GetID());
						modName = CloneWxString(moduleInstance->GetName());
						if (!StopModuleThread(moduleInstance))
							LOGWARNING(wxT("<ModuleHandle>Module has not finished in time, forcing release"));

						if (isInternal)
							delete moduleInstance;
						else if (release)
							release(moduleInstance);
						moduleInstance = (T *) NULL;
					}
				}
				numActModuleInstances = 0;
				Parameter::ParameterIdentifier pi(modName);
				KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_UNREGISTER(pi));
#ifndef VRUT_HPC
				KERNEL->GetMessageSink()->PostEvent(GET_EVT_PARAM_GUIUNREGISTER(pi));
#endif
			}

			MODULE_ID modID(serverID, hid, id);

			KERNEL->GetMessageSink()->PostEvent(GET_EVT_MODULE_RELEASED(moduleName, modID));

			/*if (numActModuleInstances == 0)
			{
				///Be sure there is no memory allocated on module memory heap here
				LOG(wxT("<ModuleHandle>Preparing to unload '") + fileName.GetFullPath() + wxT("'"));
				KERNEL->GetMessageSink()->ProcessEventsFrom(hid);
				if (!isInternal)
					dlib.Unload();
				LOG(wxT("<ModuleHandle>Unloaded '") + fileName.GetFullPath() + wxT("'"));
			}*/
		}

		///Is dynamic library loaded?
		bool IsLoaded() const
		{
			return dlib.IsLoaded();
		}

		///Is library internal?
		bool IsInternal() const
		{
			return isInternal;
		}

		///Get file information
		const wxFileName & GetFileName() const
		{
			return fileName;
		}

		///Get handled module name, return empty string if no module instantiated yet
		const wxString & GetName() const
		{
			return moduleName;
		}

		///Create new module instance and get its id
		MODULE_INSTANCE_ID InstantiateModule()
		{
			int ver = MODULES_VER_SUPP;
			T * module = (T *)NULL;
			MODULE_ID modID(serverID, hid, MODULE_INSTANCE_ID(moduleInstances.size()));
			if (isInternal)
			{
				if (ver != VRUT::MODULE_VERSION)
					ver = VRUT::MODULE_VERSION;
				else
				{
					try
					{
						if (false)//dummy check
						{}
#ifdef NAVIGATION_INTERNAL
						else if (fileName.GetName().substr(0,10)==wxT("Navigation"))
							module = (T*)new VRUT::Navigation(modID, wxT("Navigation"), KERNEL);
#endif
#ifdef IOFHS_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOFhs"))
							module = (T*)new VRUT::IOFhs(modID, wxT("IOFhs"), KERNEL);
#endif
#ifdef IOCSB_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOCsb"))
							module = (T*)new VRUT::IOCsb(modID, wxT("IOCsb"), KERNEL);
#endif
#ifdef IOOSB_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOOsb"))
							module = (T*)new VRUT::IOOsb(modID, wxT("IOOsb"), KERNEL);
#endif
#ifdef IOSTL_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOStl"))
							module = (T*)new VRUT::IOStl(modID, wxT("IOStl"), KERNEL);
#endif
#ifdef IOVRML_INTERNAL
						else if (fileName.GetName().substr(0,6)==wxT("IOVrml"))
							module = (T*)new VRUT::IOVrml(modID, wxT("IOVrml"), KERNEL);
#endif
#ifdef IOIGES_INTERNAL
						else if (fileName.GetName().substr(0,6)==wxT("IOIges"))
							module = (T*)new VRUT::IOIges(modID, wxT("IOIges"), KERNEL);
#endif
#ifdef IOOBJ_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOObj"))
							module = (T*)new VRUT::IOObj(modID, wxT("IOObj"), KERNEL);
#endif
#ifdef IOIMAGE_INTERNAL
						else if (fileName.GetName().substr(0,7)==wxT("IOImage"))
							module = (T*)new VRUT::IOImage(modID, wxT("IOImage"), KERNEL);
#endif
#ifdef IOAC3D_INTERNAL
						else if (fileName.GetName().substr(0,6)==wxT("IOAc3d"))
							module = (T*)new VRUT::IOAc3d(modID, wxT("IOAc3d"), KERNEL);
#endif
#ifdef IODOF_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IODof"))
							module = (T*)new VRUT::IODof(modID, wxT("IODof"), KERNEL);
#endif
#ifdef IORDF_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IORdf"))
							module = (T*)new VRUT::IORdf(modID, wxT("IORdf"), KERNEL);
#endif
#ifdef IOPLY_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOPly"))
							module = (T*)new VRUT::IOPly(modID, wxT("IOPly"), KERNEL);
#endif
#ifdef IOFBX_INTERNAL
						else if (fileName.GetName().substr(0,5)==wxT("IOFbx"))
							module = (T*)new VRUT::IOFbx(modID, wxT("IOFbx"), KERNEL);
#endif
#ifdef IOJT_INTERNAL
						else if (fileName.GetName().substr(0,4)==wxT("IOJt"))
							module = (T*)new VRUT::IOJt(modID, wxT("IOJt"), KERNEL);
#endif
#ifdef IOVRUT_INTERNAL
						else if (fileName.GetName().substr(0,6)==wxT("IOVrut"))
							module = (T*)new VRUT::IOVrut(modID, wxT("IOVrut"), KERNEL);
#endif
#ifdef RENDERGL_INTERNAL
						else if (fileName.GetName().substr(0,8)==wxT("RenderGl"))
							module = (T*)new VRUT::RenderGl(modID, wxT("RenderGl"), KERNEL);
#endif
#ifdef RAYTRACER_INTERNAL
						else if (fileName.GetName().substr(0,9)==wxT("RayTracer"))
							module = (T*)new VRUT::RayTracer(modID, wxT("RayTracer"), KERNEL);
#endif
#ifdef C3DCONNEXION_INTERNAL
						else if (fileName.GetName().substr(0,12)==wxT("C3Dconnexion"))
							module = (T*)new VRUT::C3Dconnexion(modID, wxT("C3Dconnexion"), KERNEL);
#endif
#ifdef AUTOTEST_INTERNAL
						else if (fileName.GetName().substr(0,8)==wxT("AutoTest"))
							module = (T*)new VRUT::AutoTest(modID, wxT("AutoTest"), KERNEL);
#endif
#ifdef CLUSTER_INTERNAL
						else if (fileName.GetName().substr(0,7)==wxT("Cluster"))
							module = (T*)new VRUT::Cluster(modID, wxT("Cluster"), KERNEL);
#endif
#ifdef LIGHTEDITOR_INTERNAL
						else if (fileName.GetName().substr(0,11)==wxT("LightEditor"))
							module = (T*)new VRUT::LightEditor(modID, wxT("LightEditor"), KERNEL);
#endif
#ifdef MATERIALASSIGN_INTERNAL
						else if (fileName.GetName().substr(0,14)==wxT("MaterialAssign"))
							module = (T*)new VRUT::MaterialAssign(modID, wxT("MaterialAssign"), KERNEL);
#endif
#ifdef MATERIALEDITOR_INTERNAL
						else if (fileName.GetName().substr(0,14)==wxT("MaterialEditor"))
							module = (T*)new VRUT::MaterialEditor(modID, wxT("MaterialEditor"), KERNEL);
#endif
#ifdef CLIPPINGEDITOR_INTERNAL
						else if (fileName.GetName().substr(0,14)==wxT("ClippingEditor"))
							module = (T*)new VRUT::ClippingEditor(modID, wxT("ClippingEditor"), KERNEL);
#endif
#ifdef OPTIMIZE_INTERNAL
						else if (fileName.GetName().substr(0,8)==wxT("Optimize"))
							module = (T*)new VRUT::Optimize(modID, wxT("Optimize"), KERNEL);
#endif
#ifdef SCENEGRAPH_INTERNAL
						else if (fileName.GetName().substr(0,10)==wxT("SceneGraph"))
							module = (T*)new VRUT::SceneGraph(modID, wxT("SceneGraph"), KERNEL);
#endif
#ifdef TESSELATOR_INTERNAL
						else if (fileName.GetName().substr(0,10)==wxT("Tesselator"))
							module = (T*)new VRUT::Tesselator(modID, wxT("Tesselator"), KERNEL);
#endif
#ifdef TEXTUREMANAGER_INTERNAL
						else if (fileName.GetName().substr(0,14)==wxT("TextureManager"))
							module = (T*)new VRUT::TextureManager(modID, wxT("TextureManager"), KERNEL);
#endif
#ifdef TRACKINGMANIPULATOR_INTERNAL
						else if (fileName.GetName().substr(0,19)==wxT("TrackingManipulator"))
							module = (T*)new VRUT::TrackingManipulator(modID, wxT("TrackingManipulator"), KERNEL);
#endif
#ifdef TRACKING_INTERNAL
						else if (fileName.GetName().substr(0,8)==wxT("Tracking"))
							module = (T*)new VRUT::Tracking(modID, wxT("Tracking"), KERNEL);
#endif
#ifdef VARIANTS_INTERNAL
						else if (fileName.GetName().substr(0,8)==wxT("Variants"))
							module = (T*)new VRUT::Variants(modID, wxT("Variants"), KERNEL);
#endif
#ifdef WIIMOTE_INTERNAL
						else if (fileName.GetName().substr(0,7)==wxT("Wiimote"))
							module = (T*)new VRUT::Wiimote(modID, wxT("Wiimote"), KERNEL);
#endif
					}
					catch(char *str)
					{	LOGERROR(wxString::Format(wxT("Module crashed in constructor <%s> "), str)+fileName.GetFullName());}
					catch(...)
					{	LOGERROR(wxT("Module crashed in constructor ")+fileName.GetFullName());}
				}
			}
			else
			{
				if (!Load())
					return MODULE_INSTANCE_ID_NONE;

				CREATE_INSTANCE_FUNC_PROTO instantiate = CREATE_INSTANCE_FUNC_PROTO(dlib.GetSymbol(wxT("CREATE_INSTANCE_FUNC")));
				if (instantiate == (CREATE_INSTANCE_FUNC_PROTO) NULL)
				{
					LOGERROR(wxT("<ModuleHandle>Could not instantiate module, library does not provide create function, module '")+ fileName.GetFullName()+wxT("' is corrupt"));
					return MODULE_INSTANCE_ID_NONE;
				}

				try
				{
					instantiate(&ver, (void **) &module, KERNEL, modID);
				}
				catch(char *str)
				{	LOGERROR(wxString::Format(wxT("Module crashed in constructor <%s> "), str)+fileName.GetFullName());}
				catch(...)
				{	LOGERROR(wxT("Module crashed in constructor ")+fileName.GetFullName());}
			}
			if (!module)
			{
				if (ver != MODULES_VER_SUPP)
					LOGERROR(wxT("<ModuleHandle>Module version incompatible, update needed"));
				else
					LOGERROR(wxT("<ModuleHandle>Module instance creation failed"));
				return MODULE_INSTANCE_ID_NONE;
			}
			else
			{
				if (moduleInstances.empty())
				{
					LOGDEBUG(wxString(wxT("<ModuleHandle>Module description: ")) + module->GetDesc());
				}
				LOGDEBUG(wxT("<ModuleHandle>Module '")+module->GetName()+wxT("' instantiated: ")+modID.ToString());
				moduleInstances.push_back(module);
				//set log level for module directly because wxLog::SetActiveTarget in module constructor cause logLevel=1000 without any reason....
				module->PostEvent(GET_EVT_LOG_LEVEL_SET(wxLog::GetLogLevel()));
				moduleName = module->GetName();

				KERNEL->GetMessageSink()->PostEvent(GET_EVT_MODULE_INSTANTIATED(moduleName, modID));

				numActModuleInstances++;
				return modID.GetInstanceID();
			}
		}

		///Get module instance, must be instantiated first
		T * GetModule(MODULE_INSTANCE_ID id) const
		{
			if (id >= moduleInstances.size())
				return (T *)NULL;

			return moduleInstances[id];
		}

		///Get all module instances
		void GetModules(ModuleList * moduleList) const
		{
			for (size_t it = 0; it < moduleInstances.size(); it++)
				if (moduleInstances[it])
					moduleList->push_back(ModulePair(moduleInstances[it], MODULE_INSTANCE_ID(it)));
		}

		///Exit module thread
		///\param[in] module Module to be stopped
		///\param[in] wait Wait for module thread to finish
		///\return \c false if module thread has not finished in \c THREAD_WAIT_TIMEOUT milliseconds
		static bool StopModuleThread(Module * module, bool wait = true)
		{
			if (!module->IsLooping()) {
				LOG(wxT("<ModuleHandle>Module thread was already finished '")+module->GetName()+wxT(" (")+module->GetID().ToString()+wxT(")'"));
				return true;
			}

			module->StopLoopinig();
			module->PostEvent(GET_EVT_EXIT());//any event, only to solve timeout in ReceiveTimeout
			if (wait)
			{
				unsigned waiting = 0;
				unsigned sec = 0;
				while (!module->IsDone())
				{
					wxThread::Sleep(THREAD_WAIT_INTERVAL);
					waiting += THREAD_WAIT_INTERVAL;
					sec += THREAD_WAIT_INTERVAL;
					if (waiting >= THREAD_WAIT_TIMEOUT)
						return false;

					if (sec >= 1000)
					{
						LOG(wxString::Format(wxT("<ModuleHandle>Waiting %i seconds for '"), (THREAD_WAIT_TIMEOUT - waiting)/1000)+module->GetName()+wxT(" (")+module->GetID().ToString()+wxT(")' module's detached thread to finish"));
						sec = 0;
					}
				}
				LOG(wxT("<ModuleHandle>Module thread finished '")+module->GetName()+wxT(" (")+module->GetID().ToString()+wxT(")'"));
			}
			return true;
		}

		///Create and run thread with module
		static void StartModuleThread(Module * module)
		{
			if (module)
			{
				wxThread * thread = new ModuleThread(module);
				wxThreadError err = thread->Create();
				wxASSERT_MSG(err == wxTHREAD_NO_ERROR, wxT("<ModuleHandle>Failed to create thread"));
				err = thread->Run();
				wxASSERT_MSG(err == wxTHREAD_NO_ERROR, wxT("<ModuleHandle>Failed to start thread"));
				LOG(wxT("<ModuleHandle>Starting module thread '")+module->GetName()+wxT(" (")+module->GetID().ToString()+wxT(")'"));
			}
		}
	};
}

#endif
