/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * 
 *************************************************************/



#include "system.h"

#include <osl/diagnose.h>
#include <osl/thread.h>
#include <rtl/alloc.h>
#include <osl/time.h>
#include <osl/interlck.h>
#include <rtl/tencinfo.h>

/*
	Thread-data structure hidden behind oslThread:	
*/
typedef struct _osl_TThreadImpl 
{
	HANDLE				m_hThread;		/* OS-handle used for all thread-functions */
	unsigned			m_ThreadId;		/* identifier for this thread */
	sal_Int32			m_nTerminationRequested;
	oslWorkerFunction	m_WorkerFunction;
	void*				m_pData;

} osl_TThreadImpl;

#define THREADIMPL_FLAGS_TERMINATE	0x0001

static unsigned __stdcall oslWorkerWrapperFunction(void* pData);
static oslThread oslCreateThread(oslWorkerFunction pWorker, void* pThreadData, sal_uInt32 nFlags);

/*****************************************************************************/
/* oslWorkerWrapperFunction */
/*****************************************************************************/
static unsigned __stdcall oslWorkerWrapperFunction(void* pData)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)pData;

	/* Initialize COM */

    CoInitializeEx(NULL, COINIT_MULTITHREADED);

	/* call worker-function with data */

	pThreadImpl->m_WorkerFunction(pThreadImpl->m_pData);

	CoUninitialize();
	
	return (0);
}

/*****************************************************************************/
/* oslCreateThread */
/*****************************************************************************/
static oslThread oslCreateThread(oslWorkerFunction pWorker,
                                 void* pThreadData,
                                 sal_uInt32 nFlags)
{
	osl_TThreadImpl* pThreadImpl;

	/* alloc mem. for our internal data structure */
	pThreadImpl= malloc(sizeof(osl_TThreadImpl));

	OSL_ASSERT(pThreadImpl);

    if ( pThreadImpl == 0 )
    {
        return 0;
    }
    
	pThreadImpl->m_WorkerFunction= pWorker;
	pThreadImpl->m_pData= pThreadData;
	pThreadImpl->m_nTerminationRequested= 0;

	pThreadImpl->m_hThread= 
		(HANDLE)_beginthreadex(NULL,						/* no security */
							   0,							/* default stack-size */
							   oslWorkerWrapperFunction,	/* worker-function */
							   pThreadImpl,					/* provide worker-function with data */
							   nFlags,						/* start thread immediately or suspended */
							   &pThreadImpl->m_ThreadId);

	if(pThreadImpl->m_hThread == 0) 
	{
		/* create failed */
		free(pThreadImpl);
		return 0;
	}

	return (oslThread)pThreadImpl;
}

/*****************************************************************************/
/* osl_createThread */
/*****************************************************************************/
oslThread SAL_CALL osl_createThread(oslWorkerFunction pWorker,
                                    void* pThreadData)
{
    return oslCreateThread(pWorker, pThreadData, 0);
}

/*****************************************************************************/
/* osl_createSuspendedThread */
/*****************************************************************************/
oslThread SAL_CALL osl_createSuspendedThread(oslWorkerFunction pWorker,
                                             void* pThreadData) 
{
    return oslCreateThread(pWorker, pThreadData, CREATE_SUSPENDED);
}

/*****************************************************************************/
/* osl_getThreadIdentifier */
/*****************************************************************************/
oslThreadIdentifier SAL_CALL osl_getThreadIdentifier(oslThread Thread)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	if (pThreadImpl != NULL)
		return ((oslThreadIdentifier)pThreadImpl->m_ThreadId);
	else
		return ((oslThreadIdentifier)GetCurrentThreadId());
}

/*****************************************************************************/
/* osl_destroyThread */
/*****************************************************************************/
void SAL_CALL osl_destroyThread(oslThread Thread) 
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	if (Thread == 0) /* valid ptr? */
	{
		/* thread already destroyed or not created */
		return;
	}

    /* !!!! _exitthreadex does _not_ call CloseHandle !!! */
    CloseHandle( pThreadImpl->m_hThread );

	/* free memory */
	free(Thread);
}

/*****************************************************************************/
/* osl_resumeThread */
/*****************************************************************************/
void SAL_CALL osl_resumeThread(oslThread Thread)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	OSL_ASSERT(pThreadImpl);		/* valid ptr? */

	ResumeThread(pThreadImpl->m_hThread);	
}

/*****************************************************************************/
/* osl_suspendThread */
/*****************************************************************************/
void SAL_CALL osl_suspendThread(oslThread Thread) 
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	OSL_ASSERT(pThreadImpl);		/* valid ptr? */

	SuspendThread(pThreadImpl->m_hThread);	
}

/*****************************************************************************/
/* osl_setThreadPriority */
/*****************************************************************************/
void SAL_CALL osl_setThreadPriority(oslThread Thread,
						   oslThreadPriority Priority)
{
	int winPriority;
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;
	
	OSL_ASSERT(pThreadImpl);		/* valid ptr? */


	/*	map enum to WIN32 levels 
		it would be faster and more elegant to preset
		the enums, but that would require an #ifdef in
		the exported header, which is not desired.
	*/
	switch(Priority) {

	case osl_Thread_PriorityHighest:
		winPriority= THREAD_PRIORITY_HIGHEST;
		break;

	case osl_Thread_PriorityAboveNormal:
		winPriority= THREAD_PRIORITY_ABOVE_NORMAL;
		break;

	case osl_Thread_PriorityNormal:
		winPriority= THREAD_PRIORITY_NORMAL;
		break;

	case osl_Thread_PriorityBelowNormal:
		winPriority= THREAD_PRIORITY_BELOW_NORMAL;
		break;

	case osl_Thread_PriorityLowest:
		winPriority= THREAD_PRIORITY_LOWEST;
		break;

	case osl_Thread_PriorityUnknown:
		OSL_ASSERT(FALSE);		/* only fools try this...*/

		/* let release-version behave friendly */
		return;

	default:
		OSL_ASSERT(FALSE);		/* enum expanded, but forgotten here...*/
		
		/* let release-version behave friendly */
		return;
	}

	SetThreadPriority(pThreadImpl->m_hThread, winPriority);
}

/*****************************************************************************/
/* osl_getThreadPriority  */
/*****************************************************************************/
oslThreadPriority SAL_CALL osl_getThreadPriority(const oslThread Thread)
{
	int winPriority;
	oslThreadPriority Priority;

	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	/* invalid arguments ?*/
	if(pThreadImpl==0 || pThreadImpl->m_hThread==0) 
	{
		return osl_Thread_PriorityUnknown;
	}

	winPriority=
		GetThreadPriority(pThreadImpl->m_hThread);


	if(winPriority == THREAD_PRIORITY_ERROR_RETURN) 
	{
		return osl_Thread_PriorityUnknown;
	}

	/* map WIN32 priority to enum */
	switch(winPriority) 
	{
	case THREAD_PRIORITY_TIME_CRITICAL:
	case THREAD_PRIORITY_HIGHEST:
		Priority= osl_Thread_PriorityHighest;
		break;

	case THREAD_PRIORITY_ABOVE_NORMAL:
		Priority= osl_Thread_PriorityAboveNormal;
		break;

	case THREAD_PRIORITY_NORMAL:
		Priority= osl_Thread_PriorityNormal;
		break;

	case THREAD_PRIORITY_BELOW_NORMAL:
		Priority= osl_Thread_PriorityBelowNormal;
		break;

	case THREAD_PRIORITY_IDLE:
	case THREAD_PRIORITY_LOWEST:
		Priority= osl_Thread_PriorityLowest;
		break;

	default:
		OSL_ASSERT(FALSE);		/* WIN32 API changed, incorporate new prio-level! */

		/* release-version behaves friendly */
		Priority= osl_Thread_PriorityUnknown;
	}

	return Priority;
}

/*****************************************************************************/
/* osl_isThreadRunning */
/*****************************************************************************/
sal_Bool SAL_CALL osl_isThreadRunning(const oslThread Thread) 
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	/* invalid arguments ?*/
	if(pThreadImpl==0 || pThreadImpl->m_hThread==0) 
	{
		return sal_False;
	}

	return (sal_Bool)(WaitForSingleObject(pThreadImpl->m_hThread, 0) != WAIT_OBJECT_0);
}

/*****************************************************************************/
/* osl_joinWithThread */
/*****************************************************************************/
void SAL_CALL osl_joinWithThread(oslThread Thread)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	/* invalid arguments?*/
	if(pThreadImpl==0 || pThreadImpl->m_hThread==0) 
	{
		/* assume thread is not running */
		return;
	}

	WaitForSingleObject(pThreadImpl->m_hThread, INFINITE);
}

/*****************************************************************************/
/* osl_waitThread */
/*****************************************************************************/
void SAL_CALL osl_waitThread(const TimeValue* pDelay)
{
	if (pDelay)
	{
		DWORD millisecs = pDelay->Seconds * 1000L + pDelay->Nanosec / 1000000L;

		Sleep(millisecs);
	}
}

/*****************************************************************************/
/* osl_terminateThread */
/*****************************************************************************/
void SAL_CALL osl_terminateThread(oslThread Thread)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	/* invalid arguments?*/
	if (pThreadImpl==0 || pThreadImpl->m_hThread==0) 
	{
		/* assume thread is not running */
		return;
	}

    osl_incrementInterlockedCount(&(pThreadImpl->m_nTerminationRequested));
}


/*****************************************************************************/
/* osl_scheduleThread */
/*****************************************************************************/
sal_Bool SAL_CALL osl_scheduleThread(oslThread Thread)
{
	osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

	osl_yieldThread();

	/* invalid arguments?*/
	if (pThreadImpl==0 || pThreadImpl->m_hThread==0) 
	{
		/* assume thread is not running */
		return sal_False;
	}

	return (sal_Bool)(0 == pThreadImpl->m_nTerminationRequested);
}

/*****************************************************************************/
/* osl_yieldThread */
/*****************************************************************************/
void SAL_CALL osl_yieldThread(void)
{
	Sleep(0);
}

void SAL_CALL osl_setThreadName(char const * name) {
#ifdef _MSC_VER
    /* See <http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx>: */
#pragma pack(push, 8)
    struct {
        DWORD dwType;
        LPCSTR szName;
        DWORD dwThreadID;
        DWORD dwFlags;
    } info;
#pragma pack(pop)
    info.dwType = 0x1000;
    info.szName = name;
    info.dwThreadID = (DWORD) -1;
    info.dwFlags = 0;
    __try {
        RaiseException(
            0x406D1388, 0, sizeof info / sizeof (ULONG_PTR),
            (ULONG_PTR *) &info);
    } __except (EXCEPTION_EXECUTE_HANDLER) {}
#else
    (void) name;
#endif
}

typedef struct _TLS
{
	DWORD							dwIndex;
	oslThreadKeyCallbackFunction	pfnCallback;
	struct _TLS						*pNext, *pPrev;
} TLS, *PTLS;

static	PTLS		g_pThreadKeyList = NULL;
CRITICAL_SECTION	g_ThreadKeyListCS;

static void AddKeyToList( PTLS pTls )
{
	if ( pTls )
	{
		EnterCriticalSection( &g_ThreadKeyListCS );

		pTls->pNext = g_pThreadKeyList;
		pTls->pPrev = 0;
		
		if ( g_pThreadKeyList )
			g_pThreadKeyList->pPrev = pTls;

		g_pThreadKeyList = pTls;

		LeaveCriticalSection( &g_ThreadKeyListCS );
	}
}

static void RemoveKeyFromList( PTLS pTls )
{
	if ( pTls )
	{
		EnterCriticalSection( &g_ThreadKeyListCS );
		if ( pTls->pPrev )
			pTls->pPrev->pNext = pTls->pNext;
		else
		{
			OSL_ASSERT( pTls == g_pThreadKeyList );
			g_pThreadKeyList = pTls->pNext;
		}

		if ( pTls->pNext )
			pTls->pNext->pPrev = pTls->pPrev;
		LeaveCriticalSection( &g_ThreadKeyListCS );
	}
}

void SAL_CALL _osl_callThreadKeyCallbackOnThreadDetach(void)
{
	PTLS	pTls;
	

	EnterCriticalSection( &g_ThreadKeyListCS );
	pTls = g_pThreadKeyList;
	while ( pTls )
	{
		if ( pTls->pfnCallback )
		{
			void	*pValue	= TlsGetValue( pTls->dwIndex );

			if ( pValue )
				pTls->pfnCallback( pValue );
		}

		pTls = pTls->pNext;
	}
	LeaveCriticalSection( &g_ThreadKeyListCS );
}

/*****************************************************************************/
/* osl_createThreadKey */
/*****************************************************************************/
oslThreadKey SAL_CALL osl_createThreadKey(oslThreadKeyCallbackFunction pCallback)
{
	PTLS	pTls = rtl_allocateMemory( sizeof(TLS) );

	if ( pTls )
	{
		pTls->pfnCallback = pCallback;
		if ( (DWORD)-1 == (pTls->dwIndex = TlsAlloc()) )
		{
			rtl_freeMemory( pTls );
			pTls = 0;
		}
		else
			AddKeyToList( pTls );
	}

	return ((oslThreadKey)pTls);
}

/*****************************************************************************/
/* osl_destroyThreadKey */
/*****************************************************************************/
void SAL_CALL osl_destroyThreadKey(oslThreadKey Key)
{
	if (Key != 0)
	{
		PTLS	pTls = (PTLS)Key;

		RemoveKeyFromList( pTls );
		TlsFree( pTls->dwIndex );
		rtl_freeMemory( pTls );
	}
}

/*****************************************************************************/
/* osl_getThreadKeyData */
/*****************************************************************************/
void* SAL_CALL osl_getThreadKeyData(oslThreadKey Key)
{
	if (Key != 0)
	{
		PTLS	pTls = (PTLS)Key;

		return (TlsGetValue( pTls->dwIndex ));
	}

	return (NULL);
}

/*****************************************************************************/
/* osl_setThreadKeyData */
/*****************************************************************************/
sal_Bool SAL_CALL osl_setThreadKeyData(oslThreadKey Key, void *pData)
{
	if (Key != 0)
	{
		PTLS	pTls = (PTLS)Key;
		void*	pOldData = NULL;
		BOOL	fSuccess;

		if ( pTls->pfnCallback )
			pOldData = TlsGetValue( pTls->dwIndex );

		fSuccess = TlsSetValue( pTls->dwIndex, pData );

		if ( fSuccess && pTls->pfnCallback && pOldData )
			pTls->pfnCallback( pOldData );

		return (sal_Bool)(fSuccess != FALSE);
	}

	return (sal_False);
}


/*****************************************************************************/
/* osl_getThreadTextEncoding */
/*****************************************************************************/

DWORD	g_dwTLSTextEncodingIndex = (DWORD)-1;


rtl_TextEncoding SAL_CALL osl_getThreadTextEncoding(void)
{
	DWORD				dwEncoding;
	rtl_TextEncoding	_encoding;
	BOOL				gotACP;

	if ( (DWORD)-1 == g_dwTLSTextEncodingIndex )
		g_dwTLSTextEncodingIndex = TlsAlloc();

	dwEncoding = (DWORD)TlsGetValue( g_dwTLSTextEncodingIndex );
	_encoding = LOWORD(dwEncoding);
	gotACP = HIWORD(dwEncoding);


	if ( !gotACP )
	{
		char	*pszEncoding;

		if ( NULL != (pszEncoding = getenv( "SOLAR_USER_RTL_TEXTENCODING" )) )
			_encoding = (rtl_TextEncoding)atoi(pszEncoding);
		else
			_encoding = rtl_getTextEncodingFromWindowsCodePage( GetACP() );

		TlsSetValue( g_dwTLSTextEncodingIndex, (LPVOID)MAKELONG( _encoding, TRUE ) );
	}

	return _encoding;
}

/*****************************************************************************/
/* osl_getThreadTextEncoding */
/*****************************************************************************/
rtl_TextEncoding SAL_CALL osl_setThreadTextEncoding( rtl_TextEncoding Encoding )
{
	rtl_TextEncoding oldEncoding = osl_getThreadTextEncoding();

	TlsSetValue( g_dwTLSTextEncodingIndex, (LPVOID)MAKELONG( Encoding, TRUE) );

	return oldEncoding;
}



