/*
 * $Id: module.h 4858 2018-02-20 13:02:33Z 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__H__
#define __MODULE__H__

#include "kernel.h"
#include "events.h" //
#include "evtstructs.h"
#include <wx/thread.h>
#include "rwlock.h"
#include "eventhandler.h"
#include "VrutApi.h"

#define CREATE_INSTANCE_FUNC_ARGS  (int * version, void ** instance, VRUT::Kernel * _kernel, const VRUT::MODULE_ID & id)
#define RELEASE_INSTANCE_FUNC_ARGS (void *)

#define IMPORT_CREATE_INSTANCE_FUNC  extern "C" DLLIMPORT void CREATE_INSTANCE_FUNC CREATE_INSTANCE_FUNC_ARGS
#define IMPORT_RELEASE_INSTANCE_FUNC extern "C" DLLIMPORT void RELEASE_INSTANCE_FUNC RELEASE_INSTANCE_FUNC_ARGS

#define EXPORT_VRUT_MODULE_FUNCTIONS( moduleClassName ) \
	extern "C" DLLEXPORT void CREATE_INSTANCE_FUNC CREATE_INSTANCE_FUNC_ARGS \
	{ \
		if (instance) \
		{ \
			if (*instance) \
			{ \
				delete (VRUT::moduleClassName *)instance; \
				*instance = 0; \
			} \
			if (*version != VRUT::MODULE_VERSION) \
				*version = VRUT::MODULE_VERSION; \
			else \
				*instance = new VRUT::moduleClassName(id, wxT( # moduleClassName ), _kernel); \
		} \
	} \
\
	extern "C" DLLEXPORT void RELEASE_INSTANCE_FUNC(void * instance) \
	{ \
		if (instance) \
			delete (VRUT::moduleClassName *)instance; \
	}

#ifdef BASE_MODULES_INTERNAL
#define EXPORT_VRUT_BASE_MODULE_FUNCTIONS( moduleClassName )
#else
#define EXPORT_VRUT_BASE_MODULE_FUNCTIONS( moduleClassName ) EXPORT_VRUT_MODULE_FUNCTIONS( moduleClassName )
#endif

#ifndef REGISTER_LISTENER
#define REGISTER_LISTENER(evtType) \
	{ \
		KERNEL->GetMessageSink()->RegisterListener(GetID(), evtType); \
	}
#endif
#ifndef UNREGISTER_LISTENER
#define UNREGISTER_LISTENER(evtType) \
	{ \
		KERNEL->GetMessageSink()->UnregisterListener(GetID(), evtType); \
	}
#endif


namespace VRUT
{
	///Module version number
	const int MODULE_VERSION = 1;

	///General type module - abstract
	class VRUT_CORE_API Module
	{
	private:
		///Module ID
		const MODULE_ID id;
		///Module name
		const wxString name;
		///Module type
		/// TODO What is a type of a module? What are possible values?
		const unsigned type;
		///Should main loop keep on?
		volatile bool isLooping;
		///Has loop finished (or never started)?
		///NOTE: Associated thread is inactive if \c true
		volatile bool isDone;
		///wxInitializer needs to be used for some operations with threads etc.
		wxInitializer *wxinit;
		///unset the log
		bool unsetTheLog;

	protected:
		///Message queue for general events posted to module
		EventQueue evtQueue;
		///Amount of time in milliseconds to wait for incoming event every loop
		///0 .. no waiting, module is looping until event arrives,
		///     continues after event is processed (eg. manipulator)
		///T .. waits at least T ms, if no event arrives \c run method is called (or other queue processing)
		///     (eg. import/export or other 'one-time purpose' modules should have \c T high
		unsigned long eventTimeout;
		///\c eventTimeout parameter identifier
		Parameter::ParameterIdentifier eventTimeoutParamID;

		///Process general event
		///Overload to process other events in derived module but do not forget to call this method
		virtual void processEvent(Event *evt)
		{
			switch (evt->type)
			{
			case Event::EVT_LOG_LEVEL_SET:
				FlexiLog::SetLogLevel(((EventLogLevelSet*)evt)->loglevel);
				break;
			case Event::EVT_EXIT:
				if (unsetTheLog)
					wxLog::SetActiveTarget(NULL);
				isLooping = false;
				break;
			case Event::EVT_PARAM_SET:
				UPDATE_PARAM_FROM_EVENT_ULONG(eventTimeoutParamID, eventTimeout, evt);
				break;
			default:
				break;
			}
		}
		///Process next event
		///Overwrite only if you have more message queues to process
		virtual void processNextEvent()
		{
			Event *evt = evtQueue.ReceiveTimeout(eventTimeout);
			if (evt)
			{
				processEvent(evt);
				ReleaseEvent(evt);
			}
		}
		///Discard and release all pending events
		void releasePendingEvents()
		{
			while (!evtQueue.IsEmpty())
			{
				Event *evt = evtQueue.ReceiveTimeout(0);
				if (evt)
					ReleaseEvent(evt);
			}
		}
		///Run method - is called every iteration of \c loop
		///Overload if you need something to be done every iteration
		virtual void run()
		{
		}
		///onDone method - is called when the main module thread terminates
		///here it is possible to postpone the termination of the module by waiting
		///for other module threads to terminate
		virtual void onDone()
		{
		}

        virtual void initBeforeLoop(){}
        virtual void cleanAfterLoop(){}

		///Main loop
		void loop()
		{
			isDone = false;
			isLooping = true;

            initBeforeLoop();

			while (isLooping)
			{
				run();
				processNextEvent();
			}

            cleanAfterLoop();

			onDone();
		}

	public:
		///Class constructor
		Module(const MODULE_ID & _id, const wxString & _name, unsigned _type, Kernel * _kernel) : id(_id), name(_name), type(_type), isLooping(true), isDone(true)
		{
			///Don't use default log instance
			wxLog::DontCreateOnDemand();
			// this must be called if new threads should be created...
			if (wxTheApp)
				wxinit = NULL;
			else
				wxinit = new wxInitializer;
			unsetTheLog = (wxLog::GetActiveTarget() != _kernel->flexiLog);
			if (unsetTheLog)
				wxLog::SetActiveTarget(_kernel->flexiLog);
			if (wxinit && !wxinit->IsOk())
				LOGERROR(wxT("<Module>Error: Cannot initialize wxWidgets, module will not work properly."));

			Kernel::SetInstance(_kernel); // FIXME This is not a good solution.
			///Event driven by default so no \c run() method needed to be called often
			wxString eventTimeoutTmp(wxT("1000"));
			REGISTER_PARAM(eventTimeoutParamID, wxT("eventTimeout"), eventTimeoutTmp, wxT("Timeout for waiting on new event"));
			TextParser(eventTimeoutTmp.utf8_str()).ParseULong(&eventTimeout);
			///Register listener
			REGISTER_LISTENER(Event::EVT_EXIT);
			REGISTER_LISTENER(Event::EVT_LOG_LEVEL_SET);
			REGISTER_LISTENER(Event::EVT_PARAM_SET);
		}

		///Class destructor
		virtual ~Module()
		{
			KERNEL->GetMessageSink()->UnregisterListener(id);//only for sure
			releasePendingEvents();
			if (unsetTheLog)
				wxLog::SetActiveTarget(NULL);
			SAFE_DELETE(wxinit);
		}

		///Get module ID
		MODULE_ID GetID() const
		{
			return id;
		}

		///Is associated thread running this module?
		bool IsDone() const
		{
			return isDone;
		}

		///Is associated thread looping?
		bool IsLooping() const
		{
			return isLooping;
		}

		virtual void StopLoopinig()
		{
			isLooping=false;
		}

		///Post general event
		///\param[in] evt Event to post
		///\param[in] highPriority Event will be processed before any others in queue if \c true
		void PostEvent(Event *evt, bool highPriority = false)
		{
			evtQueue.Post(evt, highPriority);
		}

		///Send message/event to kernel
		void PostToKernel(EventMain *evt, bool highPriority = false)
		{
			KERNEL->GetMessageSink()->PostEvent(evt, highPriority);
		}
		///Send message/event to kernel
		void PostToKernel(EventNormal *evt, bool highPriority = false)
		{
			KERNEL->GetMessageSink()->PostEvent(evt, highPriority);
		}
		///Synchronize with kernel - wait for specific event arrival
		///\param[in] evtType Wait for arrival of event with this type
		///\param[in] timeout Timeout limit to wait in milliseconds
		///\param[in] evtID Only event with this ID is accepted, -1 if don't care
		///\param[in] evtInt Only event with this integer member is accepted, -1 if don't care
		///\param[in] evtStr Only event with this string member is accepted, empty if don't care
		///\return \c wxEVT_NULL type event if timeout or exit event arrived (always check \c TestExit), awaited event otherwise (event is removed from queue)
		Event* WaitForEvent(Event * evt, long timeout)
		{
			return evtQueue.WaitForEvent(evt, timeout);
		}

		///Filter events, return last event matching specific criteria, remove other matching events from evtQueue
		///\param[in] evt Filter event
		Event* FilterEvent(Event *evt)
		{
			return evtQueue.FilterEvent(evt);
		}

		///Get module name (name should be similar to library filename)
		const wxString & GetName() const
		{
			return name;
		}

		///Get module description
		virtual wxString GetDesc() const = 0;

		///Get module type
		bool IsOfType(MODULE_TYPE _type) const
		{
			return ( type & _type ) != 0;
		}

		enum ProcessResult
		{
			Allowed,			// SyncEvent was processed and it had no problem
			Denied,				// SyncEvent was processed and it had a problem disallowing further processing
			Changed,			// SyncEvent was processed and it had had a problem, so the data had been changed to resolve it
		};

		/// Process an event, caller waits for the result
		/// Implementation must be able to run from multiple threads simultaneously (or lock on module side)
		virtual ProcessResult processSyncEvent(Event::EVENT_TYPE /*evtType*/, void * /*data*/)
		{
			return Allowed;
		}

		friend class ModuleThread;
	};

	///Module thread
	class ModuleThread : public wxThread
	{
	protected:
		///Module associated with thread
		Module * module;

	public:
		///Class constructor
		ModuleThread(Module * _module) : wxThread(), module(_module)   {}
		///Class destructor
		virtual ~ModuleThread()
		{
			module->isDone=true;
		}

		///Entry method
		virtual ExitCode Entry()
		{
#ifdef VRUT_SIGNAL_HANDLER
			typedef void (*SignalHandlerPointer)(int);
			SignalHandlerPointer previousHandler = signal(SIGSEGV, SignalHandler);

			try {
#endif
				if (module && module->IsDone())
					module->loop();

				return 0;
#ifdef VRUT_SIGNAL_HANDLER
			}
			catch(char *str) {LOGERROR(wxString::Format(wxT("Module crashed <%s> "), str)+module->GetName());}
			catch(...) {LOGERROR(wxT("Module crashed ")+module->GetName());}
			module->isLooping = false;
			module->isDone = true;
			return 0;
#endif
		}
	};

	typedef void (* CREATE_INSTANCE_FUNC_PROTO)CREATE_INSTANCE_FUNC_ARGS;
	typedef void (* RELEASE_INSTANCE_FUNC_PROTO)RELEASE_INSTANCE_FUNC_ARGS;
}

#endif
