#include <../arch/arch.h>

#ifdef	ARCH_THREADING_SUPPORTED

#ifdef	CONFIG_ARCH_MUTEX_SYSVIPC

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

/*
 * SysV - based implementation provides no means for recursive mutexes
 */

V_LOCAL arch_thread_id_t
archThisThread(void)
{
	return getpid();
}

vboolean	V_CALL
archCreateMutex(arch_mutex_p mutex_ret)
{
	ARCH_CALLER_NAME(archCreateMutex)
	mutex_ret->sem_fd = semget(IPC_PRIVATE, 1, (0600 | IPC_CREAT));
	if(mutex_ret->sem_fd == -1)
	{
		SYSCALL_FAILED("semget");
		return false;
	}
	if(semctl(mutex_ret->sem_fd, 0, SETVAL, 1) == -1)
	{
		semctl(mutex_ret->sem_fd,0,IPC_RMID);
		SYSCALL_FAILED("semctl");
		return false;
	}
	mutex_ret->owner = ARCH_INVALID_THREAD_ID;
	mutex_ret->lock_count = 0;
	return true;
}

vboolean	V_CALL
archDestroyMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archDestroyMutex)
#ifdef	ARCH_VERBOSE
	/* todo: if we own the mutex, unlock it first */
	if(mutex->owner != ARCH_INVALID_THREAD_ID)
		ARCH_WARNING("mutex still locked");
#endif	/* ARCH_VERBOSE */
	if(mutex->sem_fd != -1)
	{
		if(semctl(mutex->sem_fd,0,IPC_RMID) == -1)
		{
			SYSCALL_FAILED("semctl");
			return false;
		}
	}
	return true;
}

V_LOCAL	vboolean
archTryLockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archTryLockMutexNR)
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = -1;
	op.sem_flg = IPC_NOWAIT;
	if(semop(mutex->sem_fd,&op,1) == -1)
	{
#ifdef	ARCH_VERBOSE
		if(errno != EAGAIN)
			SYSCALL_FAILED("semop");
#endif	/* ARCH_VERBOSE */
		return false;
	}
	return true;
}

V_LOCAL	vboolean
archLockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archLockMutexNR)
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = -1;
	op.sem_flg = 0;
	if(semop(mutex->sem_fd,&op,1) == -1)
	{
		SYSCALL_FAILED("semop");
		return false;
	}
	return true;
}

V_LOCAL vboolean
archUnlockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archUnlockMutexNR)
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = 1;
	op.sem_flg = 0;
	if(semop(mutex->sem_fd,&op,1) == -1)
	{
		SYSCALL_FAILED("semop");
		return false;
	}
#ifdef	ARCH_VERBOSE
	if(semctl(mutex->sem_fd,0,GETVAL) > 1)
		ARCH_WARNING("unbalanced unlock");
#endif	/* ARCH_VERBOSE */
	return true;
}

#elif	defined(CONFIG_ARCH_MUTEX_PTHREAD) /* !CONFIG_ARCH_MUTEX_SYSVIPC */

#include <pthread.h>

#ifdef	CONFIG_ARCH_MUTEX_PTHREAD_USE_SETTYPE

V_LOCAL	vboolean
set_mutex_recursive(pthread_mutexattr_t *attr)
{
	return pthread_mutexattr_settype(attr,PTHREAD_MUTEX_RECURSIVE) 
		? false : true;
}

#elif	defined(CONFIG_ARCH_MUTEX_PTHREAD_USE_SETKIND_NP)

V_LOCAL	vboolean
set_mutex_recursive(pthread_mutexattr_t *attr)
{
	return pthread_mutexattr_setkind_np(attr,PTHREAD_MUTEX_RECURSIVE_NP)
		? false : true;
}

#else	/* !CONFIG_ARCH_MUTEX_PTHREAD_USE_SETKIND_NP */

V_LOCAL arch_thread_id_t
archThisThread(void)
{
	return pthread_self();
}

#endif	/* !CONFIG_ARCH_MUTEX_PTHREAD_USE_SETKIND_NP */

vboolean	V_CALL
archCreateMutex(arch_mutex_p mutex_ret)
{
#ifdef	ARCH_RECURSIVE_MUTEX
	ARCH_CALLER_NAME(archCreateMutex)
#endif	/* ARCH_RECURSIVE_MUTEX */
	pthread_mutexattr_init(&mutex_ret->attr);
#ifdef	ARCH_RECURSIVE_MUTEX
	if(!set_mutex_recursive(&mutex_ret->attr))
	{
		pthread_mutexattr_destroy(&mutex_ret->attr);
		SYSCALL_FAILED("set_mutex_recursive");
		return false;
	}
#endif	/* ARCH_RECURSIVE_MUTEX */
	pthread_mutex_init(&mutex_ret->mutex,&mutex_ret->attr);
#ifndef	ARCH_RECURSIVE_MUTEX
	mutex_ret->owner = ARCH_INVALID_THREAD_ID;
	mutex_ret->lock_count = 0;
#endif	/* !ARCH_RECURSIVE_MUTEX */
	return true;
}

vboolean	V_CALL
archDestroyMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archDestroyMutex)
#ifdef ARCH_VERBOSE
	u32_t unlock_count = 0;
#endif /* ARCH_VERBOSE */
#if	defined(ARCH_VERBOSE) && !defined(ARCH_RECURSIVE_MUTEX)
	/* todo: if we own the mutex, unlock it first */
	if(mutex->owner != ARCH_INVALID_THREAD_ID)
		ARCH_WARNING("mutex still locked");
#endif	/* ARCH_VERBOSE */
	while(pthread_mutex_destroy(&mutex->mutex) == EBUSY)
	{
#ifdef CHECKED_BUILD
		unlock_count++;
#endif /* CHECKED_BUILD */
		pthread_mutex_unlock(&mutex->mutex);
		timeDelay(1); /* let buggy code lock it again ! */
	}
	pthread_mutexattr_destroy(&mutex->attr);
#ifdef ARCH_VERBOSE
	if(unlock_count > 0)
	{
		conlPrintf(LOG_WARNING,"WARNING: %s(): released %u times.\n",
			arch_caller_name,unlock_count);
	}
#endif /* ARCH_VERBOSE */
	return true;
}

#ifndef	ARCH_RECURSIVE_MUTEX

V_LOCAL	vboolean
archTryLockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archTryLockMutexNR)
	int errcode = pthread_mutex_trylock(&mutex->mutex);
	if(errcode)
	{
		if(errcode != EBUSY)
			SYSCALL_FAILED("pthread_mutex_trylock");
		return false;
	}
	return true;
}

V_LOCAL	vboolean
archLockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archLockMutexNR)
	if(pthread_mutex_lock(&mutex->mutex))
	{
		SYSCALL_FAILED("pthread_mutex_lock");
		return false;
	}
	return true;
}

V_LOCAL vboolean
archUnlockMutexNR(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archUnlockMutexNR)
	if(pthread_mutex_unlock(&mutex->mutex))
	{
		SYSCALL_FAILED("pthread_mutex_unlock");
		return false;
	}
	return true;
}

#else	/* ARCH_RECURSIVE_MUTEX */

vboolean	V_CALL
archTryLockMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archTryLockMutex)
	int errcode = pthread_mutex_trylock(&mutex->mutex);
	if(errcode)
	{
		if(errcode != EBUSY)
			SYSCALL_FAILED("pthread_mutex_trylock");
		return false;
	}
	return true;
}

vboolean	V_CALL
archLockMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archLockMutex)
	if(pthread_mutex_lock(&mutex->mutex) == -1)
	{
		SYSCALL_FAILED("pthread_mutex_lock");
		return false;
	}
	return true;
}

vboolean	V_CALL
archUnlockMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archUnlockMutex)
	if(pthread_mutex_unlock(&mutex->mutex))
	{
		SYSCALL_FAILED("pthread_mutex_unlock");
		return false;
	}
	return true;
}

#endif	/* ARCH_RECURSIVE_MUTEX */

#endif	/* !CONFIG_ARCH_MUTEX_PTHREAD */

#ifndef	ARCH_RECURSIVE_MUTEX

/*
 * There is no OS support for recursive mutexes, so
 * we have to implement it in software.
 */

/*
 * Recursion extension taken from SDL.
 * SDL Copyright (C) Sam Lantinga <slouken@libsdl.org>
 */

vboolean	V_CALL
archTryLockMutex(arch_mutex_p mutex)
{
	arch_thread_id_t this_thread = archThisThread();
	if(mutex->owner == this_thread)
	{
		/* already locked by this thread */
		++mutex->lock_count;
		return true;
	}
	/*
	 * The order of operations is important.
	 * We set the locking thread id after we obtain the lock
	 * so unlocks from other threads will fail.
	 */
	if(archTryLockMutexNR(mutex))
	{
		mutex->owner = this_thread;
		mutex->lock_count = 1;
		return true;
	}
	return false;
}

vboolean	V_CALL
archLockMutex(arch_mutex_p mutex)
{
	arch_thread_id_t this_thread = archThisThread();
	if(mutex->owner == this_thread)
	{
		++mutex->lock_count;
		return true;
	}
	if(archLockMutexNR(mutex))
	{
		mutex->owner = this_thread;
		mutex->lock_count = 1;
		return true;
	}
	return false;
}

vboolean	V_CALL
archUnlockMutex(arch_mutex_p mutex)
{
	ARCH_CALLER_NAME(archUnlockMutex)
	/* We can only unlock the mutex if we own it */
	if(archThisThread() != mutex->owner)
	{
		ARCH_ERROR("calling thread doesn't own the mutex");
		return false;
	}
	if(--mutex->lock_count)
		return true; /* successfully decremented lock count */
	/*
	 * The order of operations is important.
	 * First reset the owner so another thread doesn't lock
	 * the mutex and set the ownership before we reset it,
	 * then release the lock semaphore.
	 */
	mutex->owner = ARCH_INVALID_THREAD_ID;
	archUnlockMutexNR(mutex);
	return true;
}

#endif	/* !ARCH_RECURSIVE_MUTEX */

#endif	/* ARCH_THREADING_SUPPORTED */

