/*******************************************************************************
* Copyright 2003-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#if !defined(__TARGET_THREAD_MIC)

#include "vm_thread.h"

#if defined _WIN32
    #if !defined _WIN32_WCE
        #include <process.h>
        #include <memory.h>
    #endif
#else
    #include <pthread.h>
    #include <unistd.h>
    #include <sys/time.h>
    #include <sched.h>
#endif

#ifdef __INTEL_CLANG_COMPILER
    #define __try try
    #define __except(EXCEPTION_EXECUTE_HANDLER) catch(int e)
#endif

/*
// Mutexes operations
*/
void vm_mutex_construct(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return;

    memset(&m->csection, 0, sizeof(CRITICAL_SECTION));
    m->valid = 0;
#else
    if(!m)
        return;

    m->valid = 0;
#endif
}

vm_status vm_mutex_init(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return VM_NULL_PTR;
    if(m->valid)
        return VM_IMPLICIT_DESTRUCTION;

#if _WIN32_WINNT >= 0x0600
    if(InitializeCriticalSectionEx(&m->csection, 0, 0) == 0)
        return VM_OPERATION_FAILED;
#else
    __try
    {
        InitializeCriticalSection(&m->csection);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return VM_OPERATION_FAILED;
    }
#endif
    m->valid = 1;

    return VM_OK;
#else
    pthread_mutexattr_t mutex_attr;

    if(!m)
        return VM_NULL_PTR;
    if(m->valid)
        return VM_IMPLICIT_DESTRUCTION;

    if(pthread_mutexattr_init(&mutex_attr) != 0)
        return VM_OPERATION_FAILED;
    if(pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE) != 0)
    {
        pthread_mutexattr_destroy(&mutex_attr);
        return VM_OPERATION_FAILED;
    }
    if(pthread_mutex_init(&m->mutex, &mutex_attr) != 0)
    {
        pthread_mutexattr_destroy(&mutex_attr);
        return VM_OPERATION_FAILED;
    }
    if(pthread_mutexattr_destroy(&mutex_attr) != 0)
    {
        pthread_mutex_destroy(&m->mutex);
        return VM_OPERATION_FAILED;
    }
    m->valid = 1;

    return VM_OK;
#endif
}

/* TODO: add waiters release on object destruction */
vm_status vm_mutex_destroy(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return VM_NULL_PTR;
    if(m->valid == 0)
        return VM_OK;

    __try
    {
        DeleteCriticalSection(&m->csection);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return VM_OPERATION_FAILED;
    }
    vm_mutex_construct(m);

    return VM_OK;
#else
    if(!m)
        return VM_NULL_PTR;
    if(m->valid == 0)
        return VM_OK;

    if(pthread_mutex_destroy(&m->mutex) != 0)
        return VM_OPERATION_FAILED;

    vm_mutex_construct(m);

    return VM_OK;
#endif
}

vm_status vm_mutex_lock(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    __try
    {
        EnterCriticalSection(&m->csection);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return VM_OPERATION_FAILED;
    }

    return VM_OK;
#else
    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    if(pthread_mutex_lock(&m->mutex) != 0)
        return VM_OPERATION_FAILED;

    return VM_OK;
#endif
}

vm_status vm_mutex_trylock(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    __try
    {
        TryEnterCriticalSection(&m->csection);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return VM_OPERATION_FAILED;
    }

    return VM_OK;
#else
    int res;

    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    res = pthread_mutex_trylock(&m->mutex);
    if(EBUSY == res)
        return VM_TIMEOUT;
    else if(res)
        return VM_OPERATION_FAILED;

    return VM_OK;
#endif
}

vm_status vm_mutex_unlock(vm_mutex *m)
{
#if defined _WIN32
    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    __try
    {
        LeaveCriticalSection(&m->csection);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return VM_OPERATION_FAILED;
    }

    return VM_OK;
#else
    if(!m)
        return VM_NULL_PTR;
    if(!m->valid)
        return VM_NOT_INITIALIZED;

    if(pthread_mutex_unlock(&m->mutex) != 0)
        return VM_OPERATION_FAILED;

    return VM_OK;
#endif
}

/*
// Events operations
*/
void vm_event_construct(vm_event *e)
{
#if defined _WIN32
    if(!e)
        return;

    e->handle = NULL;
#else
    if(!e)
        return;

    e->state  = -1;
    e->manual = 0;
#endif
}

vm_status vm_event_init(vm_event *e, int manual_reset, int state)
{
#if defined _WIN32
    if(!e)
        return VM_NULL_PTR;
    if(e->handle)
        return VM_IMPLICIT_DESTRUCTION;

    e->handle = CreateEvent(NULL, manual_reset, state, NULL);
    if(!e->handle)
        return VM_OPERATION_FAILED;

    return VM_OK;
#else
    if(!e)
        return VM_NULL_PTR;
    if(e->state != -1)
        return VM_IMPLICIT_DESTRUCTION;

    if(pthread_cond_init(&e->cond, 0) != 0)
        return VM_OPERATION_FAILED;
    if(pthread_mutex_init(&e->mutex, 0) != 0)
    {
        pthread_cond_destroy(&e->cond);
        return VM_OPERATION_FAILED;
    }
    e->manual = manual_reset;
    e->state  = (state ? 1 : 0);

    return VM_OK;
#endif
}

/* TODO: add waiters release on object destruction */
vm_status vm_event_destroy(vm_event *e)
{
#if defined _WIN32
    if(!e)
        return VM_NULL_PTR;
    if(e->handle == NULL)
        return VM_OK;

    if(CloseHandle(e->handle) == 0)
        return VM_OPERATION_FAILED;

    vm_event_construct(e);

    return VM_OK;
#else
    if(!e)
        return VM_NULL_PTR;
    if(e->state == -1)
        return VM_OK;

    if(pthread_cond_destroy(&e->cond) != 0)
    {
        pthread_mutex_destroy(&e->mutex);
        return VM_OPERATION_FAILED;
    }
    if(pthread_mutex_destroy(&e->mutex) != 0)
        return VM_OPERATION_FAILED;

    vm_event_construct(e);

    return VM_OK;
#endif
}

vm_status vm_event_set(vm_event *e, int state)
{
#if defined _WIN32
    if(!e)
        return VM_NULL_PTR;
    if(!e->handle)
        return VM_NOT_INITIALIZED;

    if(state)
    {
        if(SetEvent(e->handle) == 0)
            return VM_OPERATION_FAILED;
    }
    else
    {
        if(ResetEvent(e->handle) == 0)
            return VM_OPERATION_FAILED;
    }
    return VM_OK;
#else
    int signal    = 0;
    int broadcast = 0;

    if(!e)
        return VM_NULL_PTR;
    if(e->state < 0)
        return VM_NOT_INITIALIZED;

    if(pthread_mutex_lock(&e->mutex) != 0)
        return VM_OPERATION_FAILED;

    if(state)
    {
        if(!e->state)
        {
            e->state = 1;
            if(e->manual)
                broadcast = 1;
            else
                signal    = 1;
        }
    }
    else
        e->state = 0;

    if(pthread_mutex_unlock(&e->mutex) != 0)
        return VM_OPERATION_FAILED;

    if(broadcast)
    {
        if(pthread_cond_broadcast(&e->cond) != 0)
            return VM_OPERATION_FAILED;
    }
    else if(signal)
    {
        if(pthread_cond_signal(&e->cond) != 0)
            return VM_OPERATION_FAILED;
    }
    return VM_OK;
#endif
}

vm_status vm_event_pulse(vm_event *e)
{
#if defined _WIN32
    if(!e)
        return VM_NULL_PTR;
    if(!e->handle)
        return VM_NOT_INITIALIZED;

    if(PulseEvent(e->handle) == 0)
        return VM_OPERATION_FAILED;

    return VM_OK;
#else
    vm_status status;

    status = vm_event_set(e, 1);
    if(status < VM_OK)
        return status;

    status = vm_event_set(e, 0);
    if(status < VM_OK)
        return status;

    return VM_OK;
#endif
}

vm_status vm_event_wait(vm_event *e, unsigned long long msec)
{
#if defined _WIN32
    DWORD res;

    if(!e)
        return VM_NULL_PTR;
    if(!e->handle)
        return VM_NOT_INITIALIZED;

    if(msec > (DWORD)msec)
        msec = INFINITE;

    res = WaitForSingleObject(e->handle, (DWORD)msec);
    if(WAIT_OBJECT_0 == res)
        return VM_OK;
    else if(WAIT_TIMEOUT == res)
        return VM_TIMEOUT;
    else
        return VM_OPERATION_FAILED;
#else
    vm_status status = VM_OK;
    int       res;

    if(!e)
        return VM_NULL_PTR;
    if(e->state < 0)
        return VM_NOT_INITIALIZED;

    if(!msec)
    {
        res = pthread_mutex_trylock(&e->mutex);
        if(EBUSY == res)
            return VM_TIMEOUT;
        else if(res)
            return VM_OPERATION_FAILED;
    }
    else
    {
        if(pthread_mutex_lock(&e->mutex) != 0)
            return VM_OPERATION_FAILED;
    }

    if(!e->state)
    {
        if(msec == VM_WAIT_INFINITE)
        {
            do
            {
                res = pthread_cond_wait(&e->cond, &e->mutex);
            } while(!res && !e->state); /* Check for spurious wakeup */
        }
        else
        {
            struct timespec tspec;
            struct timeval  tval;
            gettimeofday(&tval, NULL);

            msec = 1000 * msec + tval.tv_usec;
            tspec.tv_sec = tval.tv_sec + msec / 1000000;
            tspec.tv_nsec = (msec % 1000000) * 1000;

            do
            {
                res = pthread_cond_timedwait(&e->cond, &e->mutex, &tspec);
            } while(!res && !e->state); /* Check for spurious wakeup */
        }
        if(ETIMEDOUT == res)
            status = VM_TIMEOUT;
        else if(res)
            status = VM_OPERATION_FAILED;
    }

    if(status == VM_OK && !e->manual)
        e->state = 0;

    if(pthread_mutex_unlock(&e->mutex) != 0)
        return VM_OPERATION_FAILED;

    return status;
#endif
}

/*
// Threads operations
*/
void vm_thread_construct(vm_thread *t)
{
#if defined _WIN32
    if(!t)
        return;

    t->handle = NULL;
#else
    if(!t)
        return;

    vm_mutex_construct(&t->mutex_wait);

    t->handle     = 0;
    t->alive      = 0;
    t->valid      = 0;
    t->closure    = 0;
#endif

    t->p_thread_func = NULL;
    t->p_arg         = NULL;
}

#if defined _WIN32
static unsigned int __stdcall vm_thread_proc(void* params)
{
    vm_thread* desc = (vm_thread*)params;
    if(!desc)
        return (unsigned int)(-1);

    __try
    {
        return desc->p_thread_func(desc->p_arg);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return (unsigned int)(-1);
    };
}
#else
static void vm_thread_cleanup(void *params)
{
    ((vm_thread*)params)->alive = 0;
}

static void* vm_thread_proc(void *params)
{
    unsigned int ret = 0;
    vm_thread  *desc = (vm_thread*)params;

    if(!params)
        pthread_exit((void*)((size_t)-1));

    pthread_cleanup_push(vm_thread_cleanup, params);
    ret = desc->p_thread_func(desc->p_arg);
    pthread_cleanup_pop(1);

    pthread_exit((void*)(size_t)ret);
}
#endif

vm_status vm_thread_create(vm_thread *t, vm_thread_callback func, void *arg)
{
#if defined _WIN32
    if(!t || !func)
        return VM_NULL_PTR;
    if(t->handle)
        return VM_IMPLICIT_DESTRUCTION;

    t->p_thread_func = func;
    t->p_arg         = arg;

#if defined _WIN32_WCE
    t->handle = (HANDLE)CreateThread(0, 0, (LPTHREAD_START_ROUTINE)vm_thread_proc, (void*)t, 0, 0);
#else
    t->handle = (HANDLE)_beginthreadex(0, 0, (unsigned int (__stdcall *)(void*))vm_thread_proc, (void*)t, 0, 0);
#endif

    if(!t->handle)
        return VM_OPERATION_FAILED;

    return VM_OK;
#else
    vm_status      status;
    pthread_attr_t attr;

    if(!t || !func)
        return VM_NULL_PTR;
    if(t->valid)
        return VM_IMPLICIT_DESTRUCTION;

    status = vm_mutex_init(&t->mutex_wait);
    if(status < VM_OK)
        return status;

    if(pthread_attr_init(&attr) != 0)
    {
        vm_mutex_destroy(&t->mutex_wait);
        return VM_OPERATION_FAILED;
    }

    if(pthread_attr_setschedpolicy(&attr, SCHED_OTHER) != 0)
    {
        vm_mutex_destroy(&t->mutex_wait);
        pthread_attr_destroy(&attr);
        return VM_OPERATION_FAILED;
    }

    t->p_thread_func = func;
    t->p_arg         = arg;
    t->alive         = 1;

    if(pthread_create(&t->handle, &attr, vm_thread_proc, (void*)t) != 0)
    {
        vm_mutex_destroy(&t->mutex_wait);
        pthread_attr_destroy(&attr);
        return VM_OPERATION_FAILED;
    }

    t->valid = 1;

    if(pthread_attr_destroy(&attr) != 0)
    {
        vm_thread_destroy(t);
        return VM_OPERATION_FAILED;
    }

    return VM_OK;
#endif
}

vm_status vm_thread_destroy(vm_thread *t)
{
#if defined _WIN32
    vm_status status;

    if(!t)
        return VM_NULL_PTR;
    if(!t->handle)
        return VM_OK;

    status = vm_thread_wait(t, 0);
    if(status < VM_OK)
    {
        CloseHandle(t->handle);
        return status;
    }

    if(CloseHandle(t->handle) == 0)
        return VM_OPERATION_FAILED;

    vm_thread_construct(t);

    return VM_OK;
#else
    vm_status status;

    if(!t)
        return VM_NULL_PTR;
    if(!t->valid)
        return VM_OK;

    status = vm_thread_wait(t, 0);
    if(status < VM_OK)
    {
        t->valid = 0;
        vm_mutex_destroy(&t->mutex_wait);
        return status;
    }

    status = vm_mutex_destroy(&t->mutex_wait);
    if(status < VM_OK)
    {
        t->valid = 0;
        return status;
    }

    vm_thread_construct(t);

    return VM_OK;
#endif
}

vm_status vm_thread_wait(vm_thread *t, int check)
{
#if defined _WIN32
    DWORD res;

    if(!t)
        return VM_NULL_PTR;
    if(!t->handle)
        return VM_NOT_INITIALIZED;

    if(check)
        res = WaitForSingleObject(t->handle, 0);
    else
        res = WaitForSingleObject(t->handle, INFINITE);
    if(WAIT_OBJECT_0 == res)
        return VM_OK;
    else if(WAIT_TIMEOUT == res)
        return VM_TIMEOUT;
    else
        return VM_OPERATION_FAILED;
#else
    vm_status waitStatus;
    vm_status status;
    int res;

    if(!t)
        return VM_NULL_PTR;
    if(!t->valid)
        return VM_NOT_INITIALIZED;

    if(check)
    {
        int res = t->alive;
        if(res == 1)
            return VM_TIMEOUT;
        else if(res != 0)
            return VM_OPERATION_FAILED;
        /* if res == 0 - thread almost terminated or terminated. Try full join below */
    }

    /* Guard pthread_join since pthread ids are nasty things after thread termination. So we ensure it is called only once. */
    status = vm_mutex_lock(&t->mutex_wait);
    if(status < VM_OK)
        return status;

    if(t->closure) /* Thread was already jointed by other thread */
        waitStatus = VM_OK;
    else
    {
        /* Wait until thread is fully terminated */
        res = pthread_join(t->handle, NULL);
        if(res && EINVAL != res)
            waitStatus = VM_OPERATION_FAILED;
        else
        {
            waitStatus = VM_OK;
            t->closure = 1;
        }
    }

    status = vm_mutex_unlock(&t->mutex_wait);
    if(status < VM_OK)
        return status;

    return waitStatus;
#endif
}

vm_status vm_thread_set_priority(vm_thread *t, vm_thread_priority priority)
{
#if defined _WIN32
    if(!t)
        return VM_NULL_PTR;
    if(!t->handle)
        return VM_NOT_INITIALIZED;

    if(SetThreadPriority(t->handle,priority) == 0)
        return VM_OPERATION_FAILED;

    return VM_OK;
#else
    int policy, pmin, pmax, pmean;
    struct sched_param param;

    if(!t)
        return VM_NULL_PTR;
    if(!t->valid)
        return VM_NOT_INITIALIZED;

    if(pthread_getschedparam(t->handle, &policy, &param) != 0)
        return VM_OPERATION_FAILED;

    pmin  = sched_get_priority_min(policy);
    pmax  = sched_get_priority_max(policy);
    pmean = (pmin + pmax) / 2;

    switch(priority)
    {
    case VM_THREAD_PRIORITY_HIGHEST:
        param.sched_priority = pmax;
        break;

    case VM_THREAD_PRIORITY_LOWEST:
        param.sched_priority = pmin;
        break;

    case VM_THREAD_PRIORITY_NORMAL:
        param.sched_priority = pmean;
        break;

    case VM_THREAD_PRIORITY_HIGH:
        param.sched_priority = (pmax + pmean) / 2;
        break;

    case VM_THREAD_PRIORITY_LOW:
        param.sched_priority = (pmin + pmean) / 2;
        break;

    default:
        return VM_OPERATION_FAILED;
        break;
    }

    if(pthread_setschedparam(t->handle, policy, &param) != 0)
        return VM_OPERATION_FAILED;

    return VM_OK;
#endif
}

#endif // !defined(__TARGET_ARCH_MIC)

