/**************************************************************
 * 
 * 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 <osl/time.h>
#include <rtl/alloc.h>
#include <rtl/tencinfo.h>

#define INCL_DOSPROCESS
#define INCL_DOSEXCEPTIONS
#define INCL_DOSMODULEMGR
#include <os2.h>
#define INCL_LOADEXCEPTQ
#include <exceptq.h>

/*
    Thread-data structure hidden behind oslThread:
*/
typedef struct _osl_TThreadImpl
{

    TID                  m_ThreadId;        /* identifier for this thread */
    sal_Int32            m_Flags;
    HEV                  m_hEvent;
    sal_uInt32           m_Timeout;
    oslWorkerFunction    m_WorkerFunction;
    void*                m_pData;
    sal_Bool             m_StartSuspended;
    HAB                  m_hab;
    HMQ                  m_hmq;

} osl_TThreadImpl;

#define THREADIMPL_FLAGS_TERMINATE    0x0001
#define THREADIMPL_FLAGS_SLEEP        0x0002


// static mutex to control access to private members of oslMutexImpl
static HMTX MutexLock = NULL;

/*****************************************************************************/

HAB osl_getPMinternal_HAB(oslThread hThread)
{
    osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)hThread;

    if(pThreadImpl == NULL) /* valid ptr? */
    {
        return NULL;
    }
    else
    {
        return pThreadImpl->m_hab;
    }
}

HMQ osl_getPMinternal_HMQ(oslThread hThread)
{
    osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)hThread;

    if(pThreadImpl == NULL) /* valid ptr? */
    {
        return NULL;
    }
    else
    {
        return pThreadImpl->m_hmq;
    }
}


/*****************************************************************************/
/* oslWorkerWrapperFunction */
/*****************************************************************************/
static void oslWorkerWrapperFunction(void* pData)
{
    BOOL rc;
    osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)pData;
    EXCEPTIONREGISTRATIONRECORD exRegRec = {0};
    LoadExceptq(&exRegRec, NULL, NULL);

#if OSL_DEBUG_LEVEL>0
printf("oslWorkerWrapperFunction pThreadImpl %x, pThreadImpl->m_ThreadId %d\n", pThreadImpl, pThreadImpl->m_ThreadId);
#endif
    /* Inizialize PM for this thread */
    pThreadImpl->m_hab = WinInitialize( 0 );
#if OSL_DEBUG_LEVEL>0
printf("pThreadImpl->m_ThreadId %d, pThreadImpl->m_hab %x\n", pThreadImpl->m_ThreadId,pThreadImpl->m_hab);
#endif
    pThreadImpl->m_hmq = WinCreateMsgQueue( pThreadImpl->m_hab, 0 );
#if OSL_DEBUG_LEVEL>0
printf("pThreadImpl->m_ThreadId %d, pThreadImpl->m_hmq %x\n", pThreadImpl->m_ThreadId,pThreadImpl->m_hmq);
#endif

    /* call worker-function with data */
    pThreadImpl->m_WorkerFunction( pThreadImpl->m_pData );

	/* Free all PM-resources for this thread */
#if OSL_DEBUG_LEVEL>0
printf("pThreadImpl->m_ThreadId %d, about to destroy queue\n", pThreadImpl->m_ThreadId);
#endif
    rc = WinDestroyMsgQueue( pThreadImpl->m_hmq );
#if OSL_DEBUG_LEVEL>0
printf("pThreadImpl->m_ThreadId %d, WinDestroyMsgQueue rc=%d (should be 1)\n", pThreadImpl->m_ThreadId, rc);
printf("pThreadImpl->m_ThreadId %d, about to terminate hab\n", pThreadImpl->m_ThreadId);
#endif
    rc = WinTerminate( pThreadImpl->m_hab );
#if OSL_DEBUG_LEVEL>0
printf("pThreadImpl->m_ThreadId %d, WinTerminate rc=%d (should be 1)\n", pThreadImpl->m_ThreadId, rc);

    UninstallExceptq(&exRegRec);

#endif
}


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

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

    OSL_ASSERT(pThreadImpl);

    pThreadImpl->m_WorkerFunction= pWorker;
    pThreadImpl->m_pData= pThreadData;

    pThreadImpl->m_Flags   = 0;
    pThreadImpl->m_hEvent  = 0;
    pThreadImpl->m_Timeout = 0;
    pThreadImpl->m_StartSuspended = nFlags;
    pThreadImpl->m_hab = 0;
    pThreadImpl->m_hmq = 0;

    if ( nFlags == sal_True )
    {    
		DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );
    }
    
    pThreadImpl->m_ThreadId = (TID) _beginthread( oslWorkerWrapperFunction,    /* worker-function */
                                                  NULL,                        /* unused parameter */
                                                  1024*1024,                   /* max. Stacksize */
                                                  pThreadImpl );
    if ( nFlags == sal_True )
    {    
        if( pThreadImpl->m_ThreadId != -1 )
            DosSuspendThread( pThreadImpl->m_ThreadId );
		DosReleaseMutexSem( MutexLock);
    }
#if OSL_DEBUG_LEVEL>0
printf("oslCreateThread pThreadImpl %x, pThreadImpl->m_ThreadId %d\n", pThreadImpl, pThreadImpl->m_ThreadId);
#endif
    if(pThreadImpl->m_ThreadId == -1)
    {
        /* create failed */
        if (pThreadImpl->m_hEvent != 0)
            DosCloseEventSem(pThreadImpl->m_hEvent);

        free(pThreadImpl);
        return 0;
    }

    pThreadImpl->m_hEvent= 0;

    return pThreadImpl;

}

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

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

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

	if (pThreadImpl != NULL)
		return ((oslThreadIdentifier)pThreadImpl->m_ThreadId);
	else
        {
        PTIB pptib = NULL;
        PPIB pppib = NULL;

        DosGetInfoBlocks( &pptib, &pppib );
        return ((oslThreadIdentifier) pptib->tib_ptib2->tib2_ultid );
        }
}

/*****************************************************************************/
/* 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;
    }

    if(pThreadImpl->m_ThreadId != -1)    /* valid handle ? */
    {
        /* cancel thread  */
        DosKillThread( pThreadImpl->m_ThreadId );
    }
}

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

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

    if (pThreadImpl->m_hEvent != 0)
        DosCloseEventSem(pThreadImpl->m_hEvent);

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

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

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

    DosResumeThread( pThreadImpl->m_ThreadId );
}

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

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

    DosSuspendThread( pThreadImpl->m_ThreadId );
}

/*****************************************************************************/
/* osl_setThreadPriority */
/*****************************************************************************/
void SAL_CALL osl_setThreadPriority(oslThread Thread,
                           oslThreadPriority Priority)
{
    ULONG nOs2PriorityClass;
    ULONG nOs2PriorityDelta;
    osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

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

    switch(Priority) {

    case osl_Thread_PriorityHighest:

        nOs2PriorityClass = PRTYC_REGULAR;
        nOs2PriorityDelta = PRTYD_MAXIMUM;
        break;

    case osl_Thread_PriorityAboveNormal:

        nOs2PriorityClass = PRTYC_REGULAR;
        nOs2PriorityDelta = 16;
        break;

    case osl_Thread_PriorityNormal:

        nOs2PriorityClass = PRTYC_REGULAR;
        nOs2PriorityDelta = 0;
        break;

    case osl_Thread_PriorityBelowNormal:

        nOs2PriorityClass = PRTYC_REGULAR;
        nOs2PriorityDelta = -16;
        break;

    case osl_Thread_PriorityLowest:

        nOs2PriorityClass = PRTYC_REGULAR;
        nOs2PriorityDelta = PRTYD_MINIMUM;
        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;
    }

    DosSetPriority( PRTYS_THREAD,
                    nOs2PriorityClass, nOs2PriorityDelta,
                    pThreadImpl->m_ThreadId );

}

/*****************************************************************************/
/* osl_getThreadPriority  */
/*****************************************************************************/

#define BYTE1FROMULONG(ul) ((UCHAR) (ul))
#define BYTE2FROMULONG(ul) ((UCHAR) ((ULONG) ul >> 8))

oslThreadPriority  SAL_CALL osl_getThreadPriority(const oslThread Thread)
{
    ULONG nOs2PriorityClass;
    ULONG nOs2PriorityDelta;

    oslThreadPriority Priority;

    osl_TThreadImpl* pThreadImpl= (osl_TThreadImpl*)Thread;

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

    /* get current priorities */
    {
    PTIB pptib = NULL;
    PPIB pppib = NULL;

    DosGetInfoBlocks( &pptib, &pppib );
    nOs2PriorityClass = BYTE1FROMULONG( pptib->tib_ptib2->tib2_ulpri );
    nOs2PriorityDelta = BYTE2FROMULONG( pptib->tib_ptib2->tib2_ulpri );
    }

    /* map OS2 priority to enum */
    switch(nOs2PriorityClass)
    {
    case PRTYC_TIMECRITICAL:
        Priority= osl_Thread_PriorityHighest;
        break;

    case PRTYC_REGULAR:

        if( nOs2PriorityDelta == 0 )
        {
            Priority= osl_Thread_PriorityNormal;
            break;
        }

        if( nOs2PriorityDelta < -16 )
        {
            Priority= osl_Thread_PriorityLowest;
            break;
        }

        if( nOs2PriorityDelta < 0 )
        {
            Priority= osl_Thread_PriorityBelowNormal;
            break;
        }

        if( nOs2PriorityDelta > 0 )
        {
            Priority= osl_Thread_PriorityAboveNormal;
            break;
        }

        Priority= osl_Thread_PriorityHighest;
        break;

    case PRTYC_IDLETIME:
        Priority= osl_Thread_PriorityLowest;
        break;

    default:
        OSL_ASSERT(FALSE);        /* OS/2 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;
    APIRET rc;

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

	if( osl_getThreadIdentifier( 0 ) == osl_getThreadIdentifier( Thread ) )
		return sal_True;

    rc = DosWaitThread( &pThreadImpl->m_ThreadId, DCWW_NOWAIT );

    return( rc != ERROR_INVALID_THREADID );
}

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

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

    DosWaitThread( &pThreadImpl->m_ThreadId, DCWW_WAIT );
}

/*****************************************************************************/
/* osl_waitThread */
/*****************************************************************************/
void SAL_CALL osl_waitThread(const TimeValue* pDelay)
{
    int millisecs;

    OSL_ASSERT(pDelay);

    millisecs = pDelay->Seconds * 1000 + pDelay->Nanosec / 1000000;

    DosSleep(millisecs);
}

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

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

    DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );
    pThreadImpl->m_Flags |= THREADIMPL_FLAGS_TERMINATE;
    DosReleaseMutexSem( MutexLock);
}


/*****************************************************************************/
/* 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_ThreadId==-1)
    {
        /* assume thread is not running */
        return sal_False;
    }

    if (pThreadImpl->m_Flags & THREADIMPL_FLAGS_SLEEP)
    {
        OSL_ASSERT (pThreadImpl->m_hEvent != 0);

        DosWaitEventSem(pThreadImpl->m_hEvent, pThreadImpl->m_Timeout);

		DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );

        pThreadImpl->m_Timeout = 0;

        pThreadImpl->m_Flags &= ~THREADIMPL_FLAGS_SLEEP;

		DosReleaseMutexSem( MutexLock);
    }

    return ((pThreadImpl->m_Flags & THREADIMPL_FLAGS_TERMINATE) == 0);
}

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

void osl_setThreadName(char const * name) {
    (void) name;
}

typedef struct _TLS
{
	PULONG							pulPtr;
	oslThreadKeyCallbackFunction	pfnCallback;
	struct _TLS						*pNext, *pPrev;
} TLS, *PTLS;

static	PTLS		g_pThreadKeyList = NULL;

static void AddKeyToList( PTLS pTls )
{
	if ( pTls )
	{
		DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );

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

		g_pThreadKeyList = pTls;

		DosReleaseMutexSem( MutexLock);
	}
}

static void RemoveKeyFromList( PTLS pTls )
{
	if ( pTls )
	{
		DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );
		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;
		DosReleaseMutexSem( MutexLock);
	}
}

void SAL_CALL _osl_callThreadKeyCallbackOnThreadDetach(void)
{
	PTLS	pTls;
	
    DosRequestMutexSem( MutexLock, SEM_INDEFINITE_WAIT );
	pTls = g_pThreadKeyList;
	while ( pTls )
	{
		if ( pTls->pfnCallback )
		{
			void	*pValue	= (void*)*pTls->pulPtr;

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

		pTls = pTls->pNext;
	}
    DosReleaseMutexSem( MutexLock);
}

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

	if ( pTls )
	{
		pTls->pfnCallback = pCallback;
		if (DosAllocThreadLocalMemory(1, &pTls->pulPtr) != NO_ERROR)
		{
			rtl_freeMemory( pTls );
			pTls = 0;
		}
		else 
		{
			*pTls->pulPtr = 0;
			AddKeyToList( pTls );
		}
	}

	return ((oslThreadKey)pTls);
}

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

		RemoveKeyFromList( pTls );
		DosFreeThreadLocalMemory(pTls->pulPtr);     
		rtl_freeMemory( pTls );
	}
}

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

		return ((void *) *pTls->pulPtr);
	}

	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 = TRUE; //YD cannot fail

		if ( pTls->pfnCallback )
			pOldData = (void*)*pTls->pulPtr;

		*pTls->pulPtr = (ULONG)pData;

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

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

	return (sal_False);
}



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

ULONG	g_dwTLSTextEncodingIndex = (ULONG)-1;

sal_uInt32 SAL_CALL _GetACP( void)
{
	APIRET	rc;
	ULONG  	aulCpList[8]  = {0};
	ULONG	ulListSize;
	
	rc = DosQueryCp( sizeof( aulCpList), aulCpList, &ulListSize);	
	if (rc)
		return 437;	// in case of error, return codepage EN_US
	// current codepage is first of list, others are the prepared codepages.
	return aulCpList[0];
}

rtl_TextEncoding SAL_CALL osl_getThreadTextEncoding(void)
{
	rtl_TextEncoding	_encoding;

	if ( (ULONG)-1 == g_dwTLSTextEncodingIndex ) {
		rtl_TextEncoding defaultEncoding;
		const char *     pszEncoding;
	
		/* create thread specific data key */
		g_dwTLSTextEncodingIndex = osl_createThreadKey( NULL);
	
		/* determine default text encoding */
		pszEncoding = getenv ("SOLAR_USER_RTL_TEXTENCODING");
		if (pszEncoding)
			defaultEncoding = atoi(pszEncoding);
		else
			defaultEncoding = rtl_getTextEncodingFromWindowsCodePage( _GetACP());
	
		//OSL_ASSERT(defaultEncoding != RTL_TEXTENCODING_DONTKNOW);
		//g_thread.m_textencoding.m_default = defaultEncoding;
		osl_setThreadKeyData( g_dwTLSTextEncodingIndex, (void*)defaultEncoding);
	}

	_encoding = (rtl_TextEncoding)osl_getThreadKeyData( g_dwTLSTextEncodingIndex );
	if (0 == _encoding) {
		const char *     pszEncoding;
		/* determine default text encoding */
		pszEncoding = getenv ("SOLAR_USER_RTL_TEXTENCODING");
		if (pszEncoding)
			_encoding = atoi(pszEncoding);
		else
			_encoding = rtl_getTextEncodingFromWindowsCodePage( _GetACP());
		/* save for future reference */
		osl_setThreadKeyData( g_dwTLSTextEncodingIndex, (void*)_encoding);
	}

	return _encoding;
}

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

	osl_setThreadKeyData( g_dwTLSTextEncodingIndex, (void*)Encoding);

	return oldEncoding;
}



