//
// Basic Threading abstraction (for pthread / win32 based systems)
//
// Author: Florian Wilkemeyer <fw@f-ws.de>
//
// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL
// For more information, see LICENCE in the main folder

#ifdef WIN32
#include "winapi.h"
#define getpagesize() 4096 // @TODO: implement this properly (GetSystemInfo .. dwPageSize..). (Atm as on all supported win platforms its 4k its static.)
#define __thread __declspec( thread ) 
#else
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <sched.h>
#endif

#include "cbasetypes.h"
#include "malloc.h"
#include "showmsg.h"
#include "thread.h"

// When Compiling using MSC (on win32..) we know we have support in any case!
#ifdef _MSC_VER 
#define HAS_TLS 
#endif


#define RA_THREADS_MAX 64

struct rAthread {
	unsigned int myID;
	
	RATHREAD_PRIO  prio;
	rAthreadProc proc;
	void *param; 

	#ifdef WIN32
	HANDLE hThread;
	#else
	pthread_t hThread;
	#endif
};


#ifdef HAS_TLS
__thread int g_rathread_ID = -1;
#endif


///
/// Subystem Code
///
static struct rAthread l_threads[RA_THREADS_MAX];

void rathread_init(){
	register unsigned int i;
	memset(&l_threads, 0x00, RA_THREADS_MAX * sizeof(struct rAthread) );
	
	for(i = 0; i < RA_THREADS_MAX; i++){
		l_threads[i].myID = i;
	}

	// now lets init thread id 0, which represnts the main thread
#ifdef HAS_TLS
	g_rathread_ID = 0;
#endif
	l_threads[0].prio = RAT_PRIO_NORMAL;
	l_threads[0].proc = (rAthreadProc)0xDEADCAFE;

}//end: rathread_init()



void rathread_final(){
	register unsigned int i;
	
	// Unterminated Threads Left? 
	// Should'nt happen ..
	// Kill 'em all! 
	//
	for(i = 1; i < RA_THREADS_MAX; i++){
		if(l_threads[i].proc != NULL){
			ShowWarning("rAthread_final: unterminated Thread (tid %u entryPoint %p) - forcing to terminate (kill)\n", i, l_threads[i].proc);
			rathread_destroy(&l_threads[i]);
		}
	}
	
	
}//end: rathread_final()



// gets called whenever a thread terminated ..
static void rat_thread_terminated( prAthread handle ){

	int id_backup = handle->myID;

	// Simply set all members to 0 (except the id)
	memset(handle, 0x00, sizeof(struct rAthread));
	
	handle->myID = id_backup; // done ;)

}//end: rat_thread_terminated()

#ifdef WIN32
DWORD WINAPI _raThreadMainRedirector(LPVOID p){
#else
static void *_raThreadMainRedirector( void *p ){
	sigset_t set; // on Posix Thread platforms
#endif
	void *ret;
	
	// Update myID @ TLS to right id.
#ifdef HAS_TLS
	g_rathread_ID = ((prAthread)p)->myID; 
#endif

#ifndef WIN32
	// When using posix threads
	// the threads inherits the Signal mask from the thread which's spawned 
	// this thread
	// so we've to block everything we dont care about.
	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	sigaddset(&set, SIGTERM);
	sigaddset(&set, SIGPIPE);

	pthread_sigmask(SIG_BLOCK, &set, NULL);
		
#endif


	ret = ((prAthread)p)->proc( ((prAthread)p)->param ) ;

#ifdef WIN32	
	CloseHandle( ((prAthread)p)->hThread );
#endif

	rat_thread_terminated( (prAthread)p );
#ifdef WIN32
	return (DWORD)ret;
#else
	return ret;
#endif
}//end: _raThreadMainRedirector()





///
/// API Level
/// 
prAthread rathread_create( rAthreadProc entryPoint,  void *param ){
	return rathread_createEx( entryPoint, param,  (1<<23) /*8MB*/,  RAT_PRIO_NORMAL );
}//end: rathread_create()


prAthread rathread_createEx( rAthreadProc entryPoint,  void *param,  size_t szStack,  RATHREAD_PRIO prio ){
#ifndef WIN32
	pthread_attr_t attr;
#endif
	size_t tmp;
	unsigned int i;
	prAthread handle = NULL;


	// given stacksize aligned to systems pagesize?
	tmp = szStack % getpagesize();
	if(tmp != 0)
		szStack += tmp;


	// Get a free Thread Slot. 
	for(i = 0; i < RA_THREADS_MAX; i++){
		if(l_threads[i].proc == NULL){
			handle = &l_threads[i];
			break;
		}
	}
	
	if(handle == NULL){
		ShowError("rAthread: cannot create new thread (entryPoint: %p) - no free thread slot found!", entryPoint);
		return NULL;
	}
	
	
	
	handle->proc = entryPoint;
	handle->param = param;

#ifdef WIN32
	handle->hThread = CreateThread(NULL, szStack, _raThreadMainRedirector, (void*)handle, 0, NULL);
#else
	pthread_attr_init(&attr);
	pthread_attr_setstacksize(&attr, szStack);
	
	if(pthread_create(&handle->hThread, &attr, _raThreadMainRedirector, (void*)handle) != 0){
		handle->proc = NULL;
		handle->param = NULL;
		return NULL;
	}
	pthread_attr_destroy(&attr);
#endif

	rathread_prio_set( handle,  prio );
	
	return handle;
}//end: rathread_createEx


void rathread_destroy ( prAthread handle ){
#ifdef WIN32
	if( TerminateThread(handle->hThread, 0) != FALSE){
		CloseHandle(handle->hThread);
		rat_thread_terminated(handle);
	}
#else
	if( pthread_cancel( handle->hThread ) == 0){
	
		// We have to join it, otherwise pthread wont re-cycle its internal ressources assoc. with this thread.
		// 
		pthread_join( handle->hThread, NULL );
		
		// Tell our manager to release ressources ;)
		rat_thread_terminated(handle);
	}
#endif
}//end: rathread_destroy()

prAthread rathread_self( ){
#ifdef HAS_TLS
	prAthread handle = &l_threads[g_rathread_ID];
	
	if(handle->proc != NULL) // entry point set, so its used!	
		return handle;
#else
	// .. so no tls means we have to search the thread by its api-handle .. 
	int i;

	#ifdef WIN32
		HANDLE hSelf;
		hSelf = GetCurrent = GetCurrentThread();
	#else
		pthread_t hSelf;
		hSelf = pthread_self();
	#endif
	
	for(i = 0; i < RA_THREADS_MAX; i++){
		if(l_threads[i].hThread == hSelf  &&  l_threads[i].proc != NULL)
			return &l_threads[i];
	}
	
#endif
		
	return NULL;	
}//end: rathread_self()


int rathread_get_tid(){

#ifdef HAS_TLS	
	return g_rathread_ID;
#else
	// todo
	#ifdef WIN32
		return (int)GetCurrentThreadId();
	#else
		return (intptr_t)pthread_self();
	#endif
	
#endif
	
}//end: rathread_get_tid()


bool rathread_wait( prAthread handle,  void* *out_exitCode ){
	
	// Hint:
	// no thread data cleanup routine call here!
	// its managed by the callProxy itself..
	//
#ifdef WIN32
	WaitForSingleObject(handle->hThread, INFINITE);
	return true; 
#else
	if(pthread_join(handle->hThread, out_exitCode) == 0)
		return true;
	return false;
#endif

}//end: rathread_wait()


void rathread_prio_set( prAthread handle, RATHREAD_PRIO prio ){
	handle->prio = RAT_PRIO_NORMAL; 
	//@TODO 
}//end: rathread_prio_set()


RATHREAD_PRIO rathread_prio_get( prAthread handle){
	return handle->prio;
}//end: rathread_prio_get()


void rathread_yield(){
#ifdef WIN32 
	SwitchToThread();
#else
	sched_yield();
#endif	
}//end: rathread_yield()
