/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */
/* A simple scheduler for ccoperative multitasking */

#ifndef STDSCHEDULER_H
#define STDSCHEDULER_H

#include "platform/StdSync.h"

// Events are Windows-specific
#ifdef _WIN32
#define STDSCHEDULER_USE_EVENTS
#define HAVE_WINTHREAD
#define STDSCHEDULER_EVENT_MESSAGE INVALID_HANDLE_VALUE
struct pollfd;
#ifndef STDSCHEDULER_USE_EVENTS
#include "platform/C4windowswrapper.h"
#include <winsock2.h>
#endif // STDSCHEDULER_USE_EVENTS
#else // _WIN32
#ifdef HAVE_POLL_H
#include <poll.h>
#else // HAVE_POLL_H
#include <sys/select.h>
#endif // HAVE_POLL_H
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif // HAVE_PTHREAD
#ifdef __APPLE__
#include <sched.h>
#endif
#endif // _WIN32

typedef struct _GMainLoop GMainLoop;

// Abstract class for a process
class StdSchedulerProc
{
private:
	class StdScheduler *scheduler{nullptr};
protected:
	void Changed();
public:

	StdSchedulerProc() = default;
	virtual ~StdSchedulerProc() = default;

	// Do whatever the process wishes to do. Should not block longer than the timeout value.
	// Is called whenever the process is signaled or a timeout occurs.
	virtual bool Execute(int iTimeout = -1, pollfd * readyfds = nullptr) = 0;

	// As Execute, but won't return until the given timeout value has elapsed or a failure occurs
	bool ExecuteUntil(int iTimeout = -1);

	// Signal for calling Execute()
#ifdef STDSCHEDULER_USE_EVENTS
	virtual HANDLE GetEvent() { return nullptr; }
#else
	virtual void GetFDs(std::vector<struct pollfd> &) { }
#endif

	// Call Execute() after this time has elapsed
	virtual C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow);

	// Is the process signal currently set?
	bool IsSignaled();

	// Is this the expensive game tick?
	virtual bool IsLowPriority() { return false; }
	virtual bool IsNotify() { return false; }
	virtual uint32_t TimerInterval() { return 0; }

	friend class StdScheduler;

};

// A simple timer proc
class CStdTimerProc : public StdSchedulerProc
{
public:
	CStdTimerProc(uint32_t iDelay) : tLastTimer(C4TimeMilliseconds::NegativeInfinity), iDelay(iDelay) { }
	~CStdTimerProc() override { Set(); }

private:
	C4TimeMilliseconds tLastTimer;
	uint32_t iDelay;

public:
	void Set()
	{
		tLastTimer = C4TimeMilliseconds::NegativeInfinity;
	}
	void SetDelay(uint32_t inDelay) { iDelay = inDelay; Changed(); }
	bool CheckAndReset()
	{
		C4TimeMilliseconds tTime = C4TimeMilliseconds::Now();
		if (tTime < tLastTimer + iDelay) return false;
		// Compensate light drifting
		int32_t iDrift = tTime - (tLastTimer + iDelay); // a positive time difference because of above check
		tLastTimer = tTime - std::min(iDrift, (int32_t) iDelay / 2);
		return true;
	}

	// StdSchedulerProc override
	C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow) override
	{
		return tLastTimer + iDelay;
	}
	uint32_t TimerInterval() override { return iDelay; }
};

class C4ApplicationSec1Timer : protected CStdTimerProc
{
public:
	C4ApplicationSec1Timer() : CStdTimerProc(1000) { }
	virtual void OnSec1Timer() = 0;
protected:
	bool Execute(int, pollfd *) override
	{
		if (CheckAndReset())
			OnSec1Timer();
		return true;
	}
};

// A simple alertable proc
class CStdNotifyProc : public StdSchedulerProc
{
public:
	CStdNotifyProc();

	void Notify();
	bool CheckAndReset();
	bool IsNotify() override { return true; }

#ifdef STDSCHEDULER_USE_EVENTS
	~CStdNotifyProc() override = default;

	// StdSchedulerProc override
	HANDLE GetEvent() override { return Event.GetEvent(); }

private:
	CStdEvent Event;

#else // STDSCHEDULER_USE_EVENTS
	~CStdNotifyProc() override;

	// StdSchedulerProc override
	void GetFDs(std::vector<struct pollfd> & checkfds) override;

private:
	int fds[2];

#endif
};

#ifdef STDSCHEDULER_USE_EVENTS
class CStdMultimediaTimerProc : public CStdNotifyProc
{
public:
	CStdMultimediaTimerProc(uint32_t iDelay);
	~CStdMultimediaTimerProc() override;

private:
	static int iTimePeriod;
	uint32_t uCriticalTimerDelay;

	UINT idCriticalTimer,uCriticalTimerResolution;
	CStdEvent Event;
	bool Check() { return Event.WaitFor(0); }

public:

	void SetDelay(uint32_t iDelay);
	void Set() { Event.Set(); }
	bool CheckAndReset();

	// StdSchedulerProc overrides
	HANDLE GetEvent() override { return Event.GetEvent(); }

};

#elif defined(HAVE_SYS_TIMERFD_H)
// timer proc using a timerfd
class CStdMultimediaTimerProc : public StdSchedulerProc
{
public:
	CStdMultimediaTimerProc(uint32_t iDelay);
	~CStdMultimediaTimerProc() override;

private:
	int fd;

public:
	void Set();
	void SetDelay(uint32_t inDelay);
	bool CheckAndReset();
	// StdSchedulerProc overrides
	void GetFDs(std::vector<struct pollfd> & checkfds) override;
};

#else
#define CStdMultimediaTimerProc CStdTimerProc
#endif

// A simple process scheduler
class StdScheduler
{
public:
	StdScheduler();
	virtual ~StdScheduler();

private:
	// Process list
	std::vector<StdSchedulerProc*> procs;
	bool isInManualLoop{false};

	// Unblocker
	class NoopNotifyProc : public CStdNotifyProc
	{
	public: bool Execute(int, pollfd *) override { CheckAndReset(); return true; }
	};
	NoopNotifyProc Unblocker;

	// Dummy lists (preserved to reduce allocs)
#ifdef STDSCHEDULER_USE_EVENTS
	std::vector<HANDLE> eventHandles;
	std::vector<StdSchedulerProc*> eventProcs;
#endif

public:
	int getProcCnt() const { return procs.size()-1; } // ignore internal NoopNotifyProc
	bool hasProc(StdSchedulerProc *pProc) { return std::find(procs.begin(), procs.end(), pProc) != procs.end(); }
	bool IsInManualLoop() { return isInManualLoop; }

	void Clear();
	void Set(StdSchedulerProc **ppProcs, int iProcCnt);
	void Add(StdSchedulerProc *pProc);
	void Remove(StdSchedulerProc *pProc);
	
	// extra events for above Add/Remove methods
	void Added(StdSchedulerProc *pProc);
	void Removing(StdSchedulerProc *pProc);
	// called by StdSchedulerProcs when something important about their configuration changed
	void Changed(StdSchedulerProc *pProc);
	// needs to be called on thread tasks for this scheduler are meant to be run on
	void StartOnCurrentThread();

	C4TimeMilliseconds GetNextTick(C4TimeMilliseconds tNow);
	bool ScheduleProcs(int iTimeout = 1000/36);
	void UnBlock();

protected:
	// overridable
	virtual void OnError(StdSchedulerProc *) { }
	virtual bool DoScheduleProcs(int iTimeout);
};

// A simple process scheduler thread
class StdSchedulerThread : public StdScheduler
{
public:
	StdSchedulerThread();
	~StdSchedulerThread() override;

private:

	// thread control
	bool fRunThreadRun;

	bool fThread{false};
#ifdef HAVE_WINTHREAD
	uintptr_t iThread;
#elif defined(HAVE_PTHREAD)
	pthread_t Thread;
#endif

public:
	void Clear();
	void Set(StdSchedulerProc **ppProcs, int iProcCnt);
	void Add(StdSchedulerProc *pProc);
	void Remove(StdSchedulerProc *pProc);

	bool Start();
	void Stop();

private:

	// thread func
#ifdef HAVE_WINTHREAD
	static void __cdecl _ThreadFunc(void *);
#elif defined(HAVE_PTHREAD)
	static void *_ThreadFunc(void *);
#endif
	unsigned int ThreadFunc();

};

class StdThread
{
private:
	bool fStarted{false};
	bool fStopSignaled{false};

#ifdef HAVE_WINTHREAD
	uintptr_t iThread;
#elif defined(HAVE_PTHREAD)
	pthread_t Thread;
#endif

public:
	StdThread();
	virtual ~StdThread() { Stop(); }

	bool Start();
	void SignalStop(); // mark thread to stop but don't wait
	void Stop();

	bool IsStarted() { return fStarted; }

protected:
	virtual void Execute() = 0;

	bool IsStopSignaled();
	virtual bool IsSelfDestruct() { return false; } // whether thread should delete itself after execution finished

private:
	// thread func
#ifdef HAVE_WINTHREAD
	static void __cdecl _ThreadFunc(void *);
#elif defined(HAVE_PTHREAD)
	static void *_ThreadFunc(void *);
#endif
	unsigned int ThreadFunc();
};

#endif
