/*
 * zThread.c
 * 
 * The Zen API 
 * Copyright (C) 2008-2009 by LMI Technologies Inc.
 * 
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
#include <z/zzThread.h>
#include <stdlib.h>

ZAPI_EXPORT(zStatus) zThread_Construct(zThread *thread)
{
    zThreadClass *obj = zNULL; 
    
    zCHECK(zCalloc(&obj, 1, sizeof(zThreadClass)));

    obj->returnValue = zOK;
    
    zHANDLE(thread, obj); 
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zThread_Destroy(zThread thread)
{
    zThreadClass* obj = zHANDLE_POINTER(thread);
 
    if (!zIS_NULL(obj))
    {
        zFree(obj);
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zThread_Start(zThread thread, zThreadCallback callback, zHandle context)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
    zThreadStartInfo* info = zNULL; 
    zStatus exception; 
    
    zEXCEPT_TRY
    {
        if (!zIS_NULL(obj->thread))
        {
            zEXCEPT_THROW(zERROR);
        }
		
        zEXCEPT_CHECK(zCalloc(&info, 1, sizeof(zThreadStartInfo))); 

        info->callback = callback; 
        info->context = context; 
    
        zEXCEPT_CHECK(zThread_StartHelper(thread, info));
    }
    zEXCEPT_CATCH(&exception)
    {
        zFree(info); 
        zEXCEPT_SET(exception); 
    }
    zEXCEPT_FINALLY
    {
        zEXCEPT_CLOSE(); 
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zThread_ExitCode(zThread thread)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
     
    return obj->returnValue;
}

#if defined(ZAPI_WINDOWS)

ZAPI_EXPORT(zStatus) zThread_Sleep(z64u duration)
{
    DWORD durationMs = (DWORD) ((duration + 999)/1000); 
    
    Sleep(durationMs); 
    
    return zOK; 
}

ZAPI_EXPORT(zStatus) zThread_StartHelper(zThread thread, zThreadStartInfo* info)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
   
    if (!zIS_NIL(obj->thread)) 
    {
        return zERROR;
    }
	
    obj->thread = (zTHREAD_HANDLE) _beginthreadex(0, 0, zThread_EntryPoint, info, 0, 0); 
    
    if (zIS_NIL(obj->thread)) 
    {
        return zERROR;
    }

    return zOK;
}

ZAPI_EXPORT(zStatus) zThread_Join(zThread thread)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
    DWORD exitCode;
      
    if (!zIS_NULL(obj->thread))
    {
        switch (WaitForSingleObject(obj->thread, INFINITE))
        {
            case WAIT_OBJECT_0:     break;
            case WAIT_TIMEOUT:      return zERROR_TIMEOUT; 
            default:                return zERROR; 
        }
        obj->returnValue = GetExitCodeThread(obj->thread, &exitCode) ? (zStatus) exitCode : zERROR; 
        obj->thread = zNULL;
    }
    
    return zOK;
}

static zTHREAD_SPEC zThread_EntryPoint(void *arg)
{
    zThreadStartInfo* obj = arg;    
    zThreadCallback callback = obj->callback;
    zHandle context = obj->context;
    
    zFree(obj);

    return (zTHREAD_RETURN) callback(context);
}

#elif defined(ZAPI_LINUX)

ZAPI_EXPORT(zStatus) zThread_Sleep(z64u duration)
{
    unsigned long durationMs = (unsigned long) ((duration + 999)/1000); 
    
    usleep(durationMs*1000); 
    
    return zOK; 
}

ZAPI_EXPORT(zStatus) zThread_StartHelper(zThread thread, zThreadStartInfo* info)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
    
    if (!zIS_NIL(obj->thread))
    {
        return zERROR;
    }
    
    pthread_create(&obj->thread, zNULL, zThread_EntryPoint, info);   
    
    if (zIS_NIL(obj->thread)) 
    {
        return zERROR;
    }
    
    return zOK;
}

ZAPI_EXPORT(zStatus) zThread_Join(zThread thread)
{
    zThreadClass* obj = zHANDLE_POINTER(thread); 
    
    if (!zIS_NULL(obj->thread))
    {
        zCHECK(pthread_join(obj->thread, (void*)&obj->returnValue) == 0);
        obj->thread = zNULL;
    }
    
    return zOK; 
}

static zTHREAD_SPEC zThread_EntryPoint(void *arg)
{
    zThreadStartInfo* obj = arg;    
    zThreadCallback callback = obj->callback;
    zHandle context = obj->context;
    
    zFree(obj);
    
    pthread_exit((void*) (zIntPtr)callback(context));
}

#endif
