/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_thread.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <sc_mmgr.h>
#include <sc_thread.h>
#include <sc_string.h>

/* =====================================================================
 *  プロトタイプ宣言
 * =====================================================================
 */

static void SC_Thread_start(SC_Thread* thread);
static void SC_Thread_join(SC_Thread* thread);

#if (SC_isWindows)
static unsigned __stdcall SC_Thread_run(void* data);
#else
static void* SC_Thread_run(void* data);
#endif

/**
 * SC_Thread を生成する。
 * スレッドに渡されるデータ data は、コピーしスレッドに渡されます。
 *
 * @param run      スレッドにて実行する関数ポインタ
 * @param data     スレッドに渡すデータ
 * @param dataSize データのサイズ
 * @return スレッドオブジェクト
 */
SC_Thread* SC_Thread_new(void (*run)(void*), void* data, size_t dataSize)
{
	SC_Thread* thread;
	thread = (SC_Thread*) malloc(sizeof(SC_Thread) + dataSize);
	if (thread == NULL)
	{	/* メモリ確保失敗 */
		return NULL;
	}
	if (data == NULL)
	{
		thread->_data = NULL;
	}
	else
	{
		thread->_data = (thread + 1);
		memcpy(thread->_data, data, dataSize);
	}
	thread->_run  = run;
	thread->start = SC_Thread_start;
	thread->join  = SC_Thread_join;
	return thread;
}


/**
 * スレッドのリソースを開放します。
 * [注意]
 * 本メソッドでは、スレッドの生死にかかわらず、
 * thread 管理メモリを破棄します。
 *
 * @param thread スレッド
 */
void SC_Thread_delete(SC_Thread* thread)
{
	free(thread);
}



/**
 * mutex を初期化します。
 *
 * @param mutex mutex
 */
void SC_Mutex_init(mutex_t* mutex)
{
#if (SC_isWindows)
	*mutex = CreateMutex(NULL, FALSE, NULL);
#else
	pthread_mutex_init(mutex, NULL);
#endif
}


/**
 * mutex を破棄します。
 *
 * @param mutex 破棄する mutex
 */
void SC_Mutex_destroy(mutex_t* mutex)
{
#if (SC_isWindows)
	CloseHandle(*mutex);
#else
	pthread_mutex_destroy(mutex);
#endif
}

/**
 * 指定された mutex をロックします。
 *
 * @param mutex ロックする mutex
 */
void SC_Mutex_lock(mutex_t* mutex)
{
#if (SC_isWindows)
	WaitForSingleObject(*mutex, INFINITE);
#else
	pthread_mutex_lock(mutex);
#endif
}


/**
 * 指定された mutex をアンロックします。
 *
 * @param mutex アンロックする mutex
 */
void SC_Mutex_unlock(mutex_t* mutex)
{
#if (SC_isWindows)
	ReleaseMutex(*mutex);
#else
	pthread_mutex_unlock(mutex);
#endif
}


/**
 * 指定された ms 間スリープします。
 */
void SC_sleep(long time)
{
#if (SC_isWindows)
	Sleep(time);
#else
	struct timespec req;
	long            sec  = time / 1000;
	long            msec = time - (sec * 1000);
	req.tv_sec  = sec;
	req.tv_nsec = msec * 1000000;
	nanosleep(&req, NULL);
#endif
}


/* =====================================================================
 *  以下、内部でのみ使用する関数
 * =====================================================================
 */

/**
 * スレッドを開始します。
 *
 * @param thread スレッド
 */
static
void SC_Thread_start(SC_Thread* thread)
{
#if (SC_isWindows)
	thread->_hThread = (HANDLE) _beginthreadex(
			NULL,
			0,
			&SC_Thread_run,
			(LPVOID) thread,
			0,
			&thread->_threadID);
#else
	pthread_create(
			&(thread->_tid),
			NULL,
			SC_Thread_run,
			thread);
#endif
}


/**
 * 指定されたスレッドが終了するのを待ちます。
 *
 * @param thread スレッド
 */
static
void SC_Thread_join(SC_Thread* thread)
{
#if (SC_isWindows)
	WaitForSingleObject(thread->_hThread, INFINITE);
	CloseHandle(thread->_hThread);
#else
	pthread_join(thread->_tid, NULL);
#endif
}


/**
 * スレッドにて実行される関数。
 *
 * @param pArguments SC_Thread が渡されます。
 * @return 0 or NULL ポインタ
 */
#if (SC_isWindows)
static
unsigned __stdcall SC_Thread_run(void* pArguments)
{
	SC_Thread* thread = (SC_Thread*) pArguments;
	thread->_run(thread->_data);
	_endthreadex(0);
	return 0;
}

#else
static
void* SC_Thread_run(void* pArguments)
{
	SC_Thread* thread = (SC_Thread*) pArguments;
	thread->_run(thread->_data);
	pthread_exit(NULL);
}
#endif

