/*-------------------------------------------------------------------------
 * drawElements Utility Library
 * ----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Periodic timer.
 *//*--------------------------------------------------------------------*/

#include "deTimer.h"
#include "deMemory.h"
#include "deThread.h"

#if (DE_OS == DE_OS_WIN32)

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

struct deTimer_s
{
	deTimerCallback		callback;
	void*				callbackArg;

	HANDLE				timer;
};

static void CALLBACK timerCallback (PVOID lpParameter, BOOLEAN timerOrWaitFired)
{
	const deTimer* timer = (const deTimer*)lpParameter;
	DE_UNREF(timerOrWaitFired);

	timer->callback(timer->callbackArg);
}

deTimer* deTimer_create (deTimerCallback callback, void* arg)
{
	deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));

	if (!timer)
		return DE_NULL;

	timer->callback		= callback;
	timer->callbackArg	= arg;
	timer->timer		= 0;

	return timer;
}

void deTimer_destroy (deTimer* timer)
{
	DE_ASSERT(timer);

	if (deTimer_isActive(timer))
		deTimer_disable(timer);

	deFree(timer);
}

deBool deTimer_isActive (const deTimer* timer)
{
	return timer->timer != 0;
}

deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
{
	BOOL ret;

	DE_ASSERT(timer && milliseconds > 0);

	if (deTimer_isActive(timer))
		return DE_FALSE;

	ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, 0, WT_EXECUTEDEFAULT);

	if (!ret)
	{
		DE_ASSERT(!timer->timer);
		return DE_FALSE;
	}

	return DE_TRUE;
}

deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
{
	BOOL ret;

	DE_ASSERT(timer && milliseconds > 0);

	if (deTimer_isActive(timer))
		return DE_FALSE;

	ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, (DWORD)milliseconds, WT_EXECUTEDEFAULT);

	if (!ret)
	{
		DE_ASSERT(!timer->timer);
		return DE_FALSE;
	}

	return DE_TRUE;
}

void deTimer_disable (deTimer* timer)
{
	if (timer->timer)
	{
		const int	maxTries	= 100;
		HANDLE		waitEvent	= CreateEvent(NULL, FALSE, FALSE, NULL);
		int			tryNdx		= 0;
		DE_ASSERT(waitEvent);

		for (tryNdx = 0; tryNdx < maxTries; tryNdx++)
		{
			BOOL success = DeleteTimerQueueTimer(NULL, timer->timer, waitEvent);
			if (success)
			{
				/* Wait for all callbacks to complete. */
				DWORD res = WaitForSingleObject(waitEvent, INFINITE);
				DE_ASSERT(res == WAIT_OBJECT_0);
				DE_UNREF(res);
				break;
			}
			else
			{
				DWORD err = GetLastError();
				if (err == ERROR_IO_PENDING)
					break; /* \todo [2013-03-21 pyry] Does this mean that callback is still in progress? */
				deYield();
			}
		}

		DE_ASSERT(tryNdx < maxTries);

		CloseHandle(waitEvent);
		timer->timer = 0;
	}
}

#elif (DE_OS == DE_OS_UNIX || DE_OS == DE_OS_ANDROID || DE_OS == DE_OS_SYMBIAN)

#include <signal.h>
#include <time.h>

struct deTimer_s
{
	deTimerCallback		callback;
	void*				callbackArg;

	timer_t				timer;

	deBool				isActive;
};

static void timerCallback (union sigval val)
{
	const deTimer* timer = (const deTimer*)val.sival_ptr;
	timer->callback(timer->callbackArg);
}

deTimer* deTimer_create (deTimerCallback callback, void* arg)
{
	deTimer*		timer = (deTimer*)deCalloc(sizeof(deTimer));
	struct sigevent	sevp;

	if (!timer)
		return DE_NULL;

	deMemset(&sevp, 0, sizeof(sevp));
	sevp.sigev_notify			= SIGEV_THREAD;
	sevp.sigev_value.sival_ptr	= timer;
	sevp.sigev_notify_function	= timerCallback;

	if (timer_create(CLOCK_REALTIME, &sevp, &timer->timer) != 0)
	{
		deFree(timer);
		return DE_NULL;
	}

	timer->callback		= callback;
	timer->callbackArg	= arg;
	timer->isActive		= DE_FALSE;

	return timer;
}

void deTimer_destroy (deTimer* timer)
{
	DE_ASSERT(timer);

	timer_delete(timer->timer);
	deFree(timer);
}

deBool deTimer_isActive (const deTimer* timer)
{
	return timer->isActive;
}

deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
{
	struct itimerspec tspec;

	DE_ASSERT(timer && milliseconds > 0);

	if (timer->isActive)
		return DE_FALSE;

	tspec.it_value.tv_sec		= milliseconds / 1000;
	tspec.it_value.tv_nsec		= (milliseconds % 1000) * 1000;
	tspec.it_interval.tv_sec	= 0;
	tspec.it_interval.tv_nsec	= 0;

	if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
		return DE_FALSE;

	timer->isActive = DE_TRUE;
	return DE_TRUE;
}

deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
{
	struct itimerspec tspec;

	DE_ASSERT(timer && milliseconds > 0);

	if (timer->isActive)
		return DE_FALSE;

	tspec.it_value.tv_sec		= milliseconds / 1000;
	tspec.it_value.tv_nsec		= (milliseconds % 1000) * 1000;
	tspec.it_interval.tv_sec	= tspec.it_value.tv_sec;
	tspec.it_interval.tv_nsec	= tspec.it_value.tv_nsec;

	if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
		return DE_FALSE;

	timer->isActive = DE_TRUE;
	return DE_TRUE;
}

void deTimer_disable (deTimer* timer)
{
	struct itimerspec tspec;

	DE_ASSERT(timer);

	tspec.it_value.tv_sec		= 0;
	tspec.it_value.tv_nsec		= 0;
	tspec.it_interval.tv_sec	= 0;
	tspec.it_interval.tv_nsec	= 0;

	timer_settime(timer->timer, 0, &tspec, DE_NULL);

	/* \todo [2012-07-10 pyry] How to wait until all pending callbacks have finished? */

	timer->isActive = DE_FALSE;
}

#else

/* Generic thread-based implementation for OSes that lack proper timers. */

#include "deThread.h"
#include "deMutex.h"
#include "deClock.h"

typedef enum TimerState_e
{
	TIMERSTATE_INTERVAL = 0,	/*!< Active interval timer.		*/
	TIMERSTATE_SINGLE,			/*!< Single callback timer.		*/
	TIMERSTATE_DISABLED,		/*!< Disabled timer.			*/

	TIMERSTATE_LAST
} TimerState;

typedef struct deTimerThread_s
{
	deTimerCallback		callback;		/*!< Callback function.		*/
	void*				callbackArg;	/*!< User pointer.			*/

	deThread			thread;			/*!< Thread.				*/
	int					interval;		/*!< Timer interval.		*/

	deMutex				lock;			/*!< State lock.			*/
	volatile TimerState	state;			/*!< Timer state.			*/
} deTimerThread;

struct deTimer_s
{
	deTimerCallback		callback;		/*!< Callback function.		*/
	void*				callbackArg;	/*!< User pointer.			*/
	deTimerThread*		curThread;		/*!< Current timer thread.	*/
};

static void timerThread (void* arg)
{
	deTimerThread*	thread			= (deTimerThread*)arg;
	int				numCallbacks	= 0;
	deBool			destroy			= DE_TRUE;
	deInt64			lastCallback	= (deInt64)deGetMicroseconds();

	for (;;)
	{
		int sleepTime = 0;

		deMutex_lock(thread->lock);

		if (thread->state == TIMERSTATE_SINGLE && numCallbacks > 0)
		{
			destroy = DE_FALSE; /* Will be destroyed by deTimer_disable(). */
			thread->state = TIMERSTATE_DISABLED;
			break;
		}
		else if (thread->state == TIMERSTATE_DISABLED)
			break;

		deMutex_unlock(thread->lock);

		sleepTime = thread->interval - (int)(((deInt64)deGetMicroseconds()-lastCallback)/1000);
		if (sleepTime > 0)
			deSleep(sleepTime);

		lastCallback = (deInt64)deGetMicroseconds();
		thread->callback(thread->callbackArg);
		numCallbacks += 1;
	}

	/* State lock is held when loop is exited. */
	deMutex_unlock(thread->lock);

	if (destroy)
	{
		/* Destroy thread except thread->thread. */
		deMutex_destroy(thread->lock);
		deFree(thread);
	}
}

static deTimerThread* deTimerThread_create (deTimerCallback callback, void* arg, int interval, TimerState state)
{
	deTimerThread* thread = (deTimerThread*)deCalloc(sizeof(deTimerThread));

	DE_ASSERT(state == TIMERSTATE_INTERVAL || state == TIMERSTATE_SINGLE);

	if (!thread)
		return DE_NULL;

	thread->callback	= callback;
	thread->callbackArg	= arg;
	thread->interval	= interval;
	thread->lock		= deMutex_create(DE_NULL);
	thread->state		= state;

	thread->thread		= deThread_create(timerThread, thread, DE_NULL);
	if (!thread->thread)
	{
		deMutex_destroy(thread->lock);
		deFree(thread);
		return DE_NULL;
	}

	return thread;
}

deTimer* deTimer_create (deTimerCallback callback, void* arg)
{
	deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));

	if (!timer)
		return DE_NULL;

	timer->callback		= callback;
	timer->callbackArg	= arg;

	return timer;
}

void deTimer_destroy (deTimer* timer)
{
	if (timer->curThread)
		deTimer_disable(timer);
	deFree(timer);
}

deBool deTimer_isActive (const deTimer* timer)
{
	if (timer->curThread)
	{
		deBool isActive = DE_FALSE;

		deMutex_lock(timer->curThread->lock);
		isActive = timer->curThread->state != TIMERSTATE_LAST;
		deMutex_unlock(timer->curThread->lock);

		return isActive;
	}
	else
		return DE_FALSE;
}

deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
{
	if (timer->curThread)
		deTimer_disable(timer);

	DE_ASSERT(!timer->curThread);
	timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_SINGLE);

	return timer->curThread != DE_NULL;
}

deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
{
	if (timer->curThread)
		deTimer_disable(timer);

	DE_ASSERT(!timer->curThread);
	timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_INTERVAL);

	return timer->curThread != DE_NULL;
}

void deTimer_disable (deTimer* timer)
{
	if (!timer->curThread)
		return;

	deMutex_lock(timer->curThread->lock);

	if (timer->curThread->state != TIMERSTATE_DISABLED)
	{
		/* Just set state to disabled and destroy thread handle. */
		/* \note Assumes that deThread_destroy() can be called while thread is still running
		 *       and it will not terminate the thread.
		 */
		timer->curThread->state = TIMERSTATE_DISABLED;
		deThread_destroy(timer->curThread->thread);
		timer->curThread->thread = 0;
		deMutex_unlock(timer->curThread->lock);

		/* Thread will destroy timer->curThread. */
	}
	else
	{
		/* Single timer has expired - we must destroy whole thread structure. */
		deMutex_unlock(timer->curThread->lock);
		deThread_destroy(timer->curThread->thread);
		deMutex_destroy(timer->curThread->lock);
		deFree(timer->curThread);
	}

	timer->curThread = DE_NULL;
}

#endif