/*****************************************************************************/
// Copyright 2002-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

#include "dng_pthread.h"

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

#if qDNGThreadSafe

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

#include "dng_assertions.h"
#include "dng_auto_ptr.h"

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

#if qWinOS

#pragma warning(disable : 4786)

/* Not supporting Win98, check WINVER if this is still needed

// Nothing in this file requires Unicode,
// However, CreateSemaphore has a path parameter
// (which is NULL always in this code) and thus
// does not work on Win98 if UNICODE is defined.
// So we force it off here.

#undef UNICODE
#undef _UNICODE
*/

#include <windows.h>
#include <process.h>
#include <errno.h>
#include <memory>
#include <new>
#include <map>

#else

#include <sys/time.h>

#endif

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

#if qWinOS

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

// Turning off qDNGUseConditionVariable because the WakeConditionVariable and
// WakeAllConditionVariable Microsoft API routines are only available on
// Vista, and Camera Raw and DNG Converter 8.3 need to continue working on
// Windows XP. -erichan 2013-11-08.

#define qDNGUseConditionVariable 0

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

#ifndef qDNGUseConditionVariable
#if WINVER >= 0x0600 // Vista introduces a real condition variable support
#define qDNGUseConditionVariable 1
#else
#define qDNGUseConditionVariable 0
#endif
#endif

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

#if !qDNGUseConditionVariable
namespace {

    struct waiter {
        struct waiter *prev;
        struct waiter *next;
        HANDLE semaphore;
        bool chosen_by_signal;
    };
}
#endif

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

struct dng_pthread_mutex_impl
{
    CRITICAL_SECTION lock;

    dng_pthread_mutex_impl()   { ::InitializeCriticalSection(&lock); }
    ~dng_pthread_mutex_impl()  { ::DeleteCriticalSection(&lock); }
    void Lock()                { ::EnterCriticalSection(&lock); }
    void Unlock()              { ::LeaveCriticalSection(&lock); }
private:
    dng_pthread_mutex_impl &operator=(const dng_pthread_mutex_impl &);
    dng_pthread_mutex_impl(const dng_pthread_mutex_impl &);
};

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

struct dng_pthread_cond_impl
{
    dng_pthread_mutex_impl lock;        // Mutual exclusion on next two variables

#if qDNGUseConditionVariable
    // so much simpler, but Vista+ only
    CONDITION_VARIABLE     cond;

    dng_pthread_cond_impl()  { InitializeConditionVariable(&cond); }
    ~dng_pthread_cond_impl() { } // no delete listed

#else

    waiter *head_waiter;            // List of threads waiting on this condition
    waiter *tail_waiter;            // Used to get FIFO, rather than LIFO, behavior for pthread_cond_signal
    unsigned int broadcast_generation;  // Used as sort of a separator on broadcasts
                                        // saves having to walk the waiters list setting
                                        // each one's "chosen_by_signal" flag while the condition is locked

    dng_pthread_cond_impl() : head_waiter(NULL), tail_waiter(NULL), broadcast_generation(0) { }
    ~dng_pthread_cond_impl() { }
#endif

// Non copyable
private:
    dng_pthread_cond_impl &operator=(const dng_pthread_cond_impl &);
    dng_pthread_cond_impl(const dng_pthread_cond_impl &);

};

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

namespace
{

    struct ScopedLock
    {
        dng_pthread_mutex_impl *mutex;

        ScopedLock(dng_pthread_mutex_impl *arg) : mutex(arg)
        {
            mutex->Lock();
        }
        ScopedLock(dng_pthread_mutex_impl &arg) : mutex(&arg)
        {
            mutex->Lock();
        }
        ~ScopedLock()
        {
            mutex->Unlock();
        }
    private:
        ScopedLock &operator=(const ScopedLock &);
        ScopedLock(const ScopedLock &);
    };


#if !qDNGUseConditionalVariable
    // DONE: avoid this serialization lock
    //   do allocation at init, and then just assert ?

    dng_pthread_mutex_impl validationLock;

    void ValidateMutex(dng_pthread_mutex_t *mutex)
    {
        if (*mutex != DNG_PTHREAD_MUTEX_INITIALIZER)
            return;

        ScopedLock lock(validationLock);

        if (*mutex == DNG_PTHREAD_MUTEX_INITIALIZER)
            dng_pthread_mutex_init(mutex, NULL);
    }

    void ValidateCond(dng_pthread_cond_t *cond)
    {
        if (*cond != DNG_PTHREAD_COND_INITIALIZER)
            return;

        ScopedLock lock(validationLock);

        if (*cond == DNG_PTHREAD_COND_INITIALIZER)
            dng_pthread_cond_init(cond, NULL);
    }
#endif

    DWORD thread_wait_sema_TLS_index;
    bool thread_wait_sema_inited = false;
    dng_pthread_once_t once_thread_TLS = DNG_PTHREAD_ONCE_INIT;

    void init_thread_TLS()
    {
        thread_wait_sema_TLS_index = ::TlsAlloc();
        thread_wait_sema_inited = true;
    }

    void finalize_thread_TLS()
    {
        if (thread_wait_sema_inited)
            {
            ::TlsFree(thread_wait_sema_TLS_index);
            thread_wait_sema_inited = false;
            }
    }

    dng_pthread_mutex_impl primaryHandleMapLock;

    typedef std::map<DWORD, std::pair<HANDLE, void **> > ThreadMapType;

    // A map to make sure handles are freed and to allow returning a pointer sized result
    // even on 64-bit Windows.
    ThreadMapType primaryHandleMap;

    HANDLE GetThreadSemaphore()
    {
        dng_pthread_once(&once_thread_TLS, init_thread_TLS);

        HANDLE semaphore = ::TlsGetValue(thread_wait_sema_TLS_index);
        if (semaphore == NULL)
        {
            semaphore = ::CreateSemaphore(NULL, 0, 1, NULL);
            ::TlsSetValue(thread_wait_sema_TLS_index, semaphore);
        }

        return semaphore;
    }

    void FreeThreadSemaphore()
    {
        if (thread_wait_sema_inited)
        {
            HANDLE semaphore = (HANDLE)::TlsGetValue(thread_wait_sema_TLS_index);

            if (semaphore != NULL)
            {
                ::TlsSetValue(thread_wait_sema_TLS_index, NULL);
                ::CloseHandle(semaphore);
            }
        }
    }

    struct trampoline_args
    {
        void *(*func)(void *);
        void *arg;
    };

    // This trampoline takes care of the return type being different
    // between pthreads thread funcs and Windows C lib thread funcs
    unsigned __stdcall trampoline(void *arg_arg)
    {
        trampoline_args *args_ptr = (trampoline_args *)arg_arg;
        trampoline_args args = *args_ptr;

        delete args_ptr;

        GetThreadSemaphore();

        void *result = args.func(args.arg);

        {
            ScopedLock lockMap(primaryHandleMapLock);

            ThreadMapType::iterator iter = primaryHandleMap.find(pthread_self());
            if (iter != primaryHandleMap.end())
                *iter->second.second = result;
        }

        FreeThreadSemaphore();

        return S_OK;
    }

}

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

extern "C" {

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

struct dng_pthread_attr_impl
    {
    size_t stacksize;
    };

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

int dng_pthread_attr_init(pthread_attr_t *attr)
    {
    dng_pthread_attr_impl *newAttrs;

    newAttrs = new (std::nothrow) dng_pthread_attr_impl;
    if (newAttrs == NULL)
        return -1; // ENOMEM;

    newAttrs->stacksize = 0;

    *attr = newAttrs;

    return 0;
    }

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

int dng_pthread_attr_destroy(pthread_attr_t *attr)
    {
    if (*attr == NULL)
        return -1; // EINVAL

    delete *attr;

    *attr = NULL;

    return 0;
    }

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

int dng_pthread_attr_setstacksize(dng_pthread_attr_t *attr, size_t stacksize)
    {
    if (attr == NULL || (*attr) == NULL)
        return -1; // EINVAL

    (*attr)->stacksize = stacksize;

    return 0;
    }

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

int dng_pthread_attr_getstacksize(const dng_pthread_attr_t *attr, size_t *stacksize)
    {
    if (attr == NULL || (*attr) == NULL || stacksize == NULL)
        return -1; // EINVAL

    *stacksize = (*attr)->stacksize;

    return 0;
    }

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

int dng_pthread_create(dng_pthread_t *thread, const pthread_attr_t *attrs, void * (*func)(void *), void *arg)
{
    try
    {
        uintptr_t result;
        unsigned threadID;
        AutoPtr<trampoline_args> args(new (std::nothrow) trampoline_args);
        AutoPtr<void *> resultHolder(new (std::nothrow) (void *));

        if (args.Get() == NULL || resultHolder.Get () == NULL)
            return -1; // ENOMEM

        args->func = func;
        args->arg = arg;

        size_t stacksize = 0;

        if (attrs != NULL)
            dng_pthread_attr_getstacksize (attrs, &stacksize);

        {
            ScopedLock lockMap(primaryHandleMapLock);

            result = _beginthreadex(NULL, (unsigned)stacksize, trampoline, args.Get(), 0, &threadID);
            if (result == NULL)
                return -1; // ENOMEM
            (void) args.Release();

            std::pair<DWORD, std::pair<HANDLE, void **> > newMapEntry(threadID,
                                                                     std::pair<HANDLE, void **>((HANDLE)result, resultHolder.Get ()));
            std::pair<ThreadMapType::iterator, bool> insertion = primaryHandleMap.insert(newMapEntry);
            (void) insertion;

            // If there is a handle open on the thread, its ID should not be reused so assert that an insertion was made.
            DNG_ASSERT(insertion.second, "pthread emulation logic error");
        }


        (void) resultHolder.Release ();

        *thread = (dng_pthread_t)threadID;
        return 0;
    }
    catch (const std::bad_alloc &)
    {
        return -1;
    }
}

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

int dng_pthread_detach(dng_pthread_t thread)
{
    HANDLE primaryHandle;
    void **resultHolder = NULL;

    {
        ScopedLock lockMap(primaryHandleMapLock);

        ThreadMapType::iterator iter = primaryHandleMap.find(thread);
        if (iter == primaryHandleMap.end())
            return -1;

        primaryHandle = iter->second.first;

        // A join is waiting on the thread.
        if (primaryHandle == NULL)
            return -1;

        resultHolder = iter->second.second;

        primaryHandleMap.erase(iter);
    }

    delete resultHolder;

#if qWinRT
    if (!::WinRT_CloseThreadHandle(primaryHandle))
#else
    if (!::CloseHandle(primaryHandle))
#endif
        return -1;

    return 0;
}

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

int dng_pthread_join(dng_pthread_t thread, void **result)
{
    bool found = false;
    HANDLE primaryHandle = NULL;
    void **resultHolder = NULL;

    ThreadMapType::iterator iter;

    {
        ScopedLock lockMap(primaryHandleMapLock);

        iter = primaryHandleMap.find(thread);
        found = iter != primaryHandleMap.end();
        if (found)
        {
            primaryHandle = iter->second.first;
            resultHolder = iter->second.second;

            // Set HANDLE to NULL to force any later join or detach to fail.
            iter->second.first = NULL;
        }
    }

    // This case can happens when joining a thread not created with pthread_create,
    // which is a bad idea, but it gets mapped to doing the join, but always returns NULL.
    if (!found)
        primaryHandle = ::OpenThread(SYNCHRONIZE|THREAD_QUERY_INFORMATION, FALSE, thread);

    if (primaryHandle == NULL)
        return -1;

    if (::WaitForSingleObject(primaryHandle, INFINITE) != WAIT_OBJECT_0)
    {
        DWORD err = ::GetLastError();
        (void) err;
        return -1;
    }

    {
        ScopedLock lockMap(primaryHandleMapLock);

        if (iter != primaryHandleMap.end())
            primaryHandleMap.erase(iter);
    }

#if qWinRT
    ::WinRT_CloseThreadHandle(primaryHandle);
#else
    ::CloseHandle(primaryHandle);
#endif
    if (result != NULL && resultHolder != NULL)
        *result = *resultHolder;

    delete resultHolder;

    return 0;
}

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

dng_pthread_t dng_pthread_self()
{
    return (dng_pthread_t)::GetCurrentThreadId();
}

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

void dng_pthread_exit(void *result)
{
    {
        ScopedLock lockMap(primaryHandleMapLock);

        ThreadMapType::iterator iter = primaryHandleMap.find(pthread_self());
        if (iter != primaryHandleMap.end())
            *iter->second.second = result;
    }

    FreeThreadSemaphore();

    _endthreadex(S_OK);
}

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

int dng_pthread_mutex_init(dng_pthread_mutex_t *mutex, void * /* attrs */)
{
    dng_pthread_mutex_t result;
    try {
        result = new(dng_pthread_mutex_impl);
    } catch (const std::bad_alloc &)
    {
        return -1;
    }

    if (result == NULL)
        return -1;
    *mutex = result;
    return 0;
}

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

int dng_pthread_mutex_destroy(dng_pthread_mutex_t *mutex)
{
    if (*mutex == DNG_PTHREAD_MUTEX_INITIALIZER)
    {
        *mutex = NULL;
        return 0;
    }

    delete *mutex;
    *mutex = NULL;
    return 0;
}

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

int dng_pthread_cond_init(dng_pthread_cond_t *cond, void * /* attrs */)
{
    dng_pthread_cond_t result;
    try {
        result = new(dng_pthread_cond_impl);
    } catch (const std::bad_alloc &)
    {
        return -1;
    }

    if (result == NULL)
        return -1;
    *cond = result;
    return 0;
}

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

int dng_pthread_cond_destroy(dng_pthread_cond_t *cond)
{
    if (*cond == DNG_PTHREAD_COND_INITIALIZER)
    {
        *cond = NULL;
        return 0;
    }

    delete *cond;
    *cond = NULL;
    return 0;
}

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

int dng_pthread_mutexattr_init(dng_pthread_mutexattr_t* mutexattr)
{
    return 0;
}

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

int dng_pthread_mutexattr_settype(dng_pthread_mutexattr_t* mutexattr, int type)
{
    return 0;
}

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

int dng_pthread_mutex_lock(dng_pthread_mutex_t *mutex)
{
    ValidateMutex(mutex);
    (*mutex)->Lock();
    return 0;
}

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

int dng_pthread_mutex_unlock(dng_pthread_mutex_t *mutex)
{
    ValidateMutex(mutex);
    (*mutex)->Unlock();
    return 0;
}

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

static int cond_wait_internal(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex, int timeout_milliseconds)
{
#if qDNGUseConditionVariable
    int result = 0;

    BOOL success = SleepConditionVariableCS(&(*cond)->cond, &(*mutex)->lock, timeout_milliseconds);
    if (!success)
        if (GetLastError() == ERROR_TIMEOUT)
            result = DNG_ETIMEDOUT;

    return result;

#else

    dng_pthread_cond_impl &real_cond = **cond;
    dng_pthread_mutex_impl &real_mutex = **mutex;

    waiter this_wait;
    HANDLE semaphore = GetThreadSemaphore();
    int my_generation; // The broadcast generation this waiter is in

    {
        this_wait.next = NULL;
        this_wait.semaphore = semaphore;
        this_wait.chosen_by_signal = 0;

        ScopedLock lock1(real_cond.lock);

        // Add this waiter to the end of the list.
        this_wait.prev = real_cond.tail_waiter;
        if (real_cond.tail_waiter != NULL)
            real_cond.tail_waiter->next = &this_wait;
        real_cond.tail_waiter = &this_wait;

        // If the list was empty, set the head of the list to this waiter.
        if (real_cond.head_waiter == NULL)
            real_cond.head_waiter = &this_wait;

        // Note which broadcast generation this waiter belongs to.
        my_generation = real_cond.broadcast_generation;
    }

    real_mutex.Unlock();

    DWORD result = ::WaitForSingleObject(semaphore, timeout_milliseconds);

    if (result == WAIT_TIMEOUT)
    {
        // If the wait timed out, this thread is likely still on the waiters list
        // of the condition. However, there is a race in that the thread may have been
        // signaled or broadcast between when WaitForSingleObject decided
        // we had timed out and this code running.

        bool mustConsumeSemaphore = false;
        {
            ScopedLock lock2(real_cond.lock);

            bool chosen_by_signal = this_wait.chosen_by_signal;
            bool chosen_by_broadcast = my_generation != real_cond.broadcast_generation;

            if (chosen_by_signal || chosen_by_broadcast)
                mustConsumeSemaphore = true;
            else
            {
                // Still on waiters list. Remove this waiter from list.
                if (this_wait.next != NULL)
                    this_wait.next->prev = this_wait.prev;
                else
                    real_cond.tail_waiter = this_wait.prev;

                if (this_wait.prev != NULL)
                    this_wait.prev->next = this_wait.next;
                else
                    real_cond.head_waiter = this_wait.next;
            }
        }

        if (mustConsumeSemaphore)
        {
            ::WaitForSingleObject(semaphore, INFINITE);
            result = WAIT_OBJECT_0;
        }
    }
    else
        DNG_ASSERT (result == WAIT_OBJECT_0, "pthread emulation logic error");

    // reacquire the mutex
    real_mutex.Lock();

    return (result == WAIT_TIMEOUT) ? DNG_ETIMEDOUT : 0;
#endif
}

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

int dng_pthread_cond_wait(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex)
{
    ValidateCond(cond);

    return cond_wait_internal(cond, mutex, INFINITE);
}

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

int dng_pthread_cond_timedwait(dng_pthread_cond_t *cond, dng_pthread_mutex_t *mutex, struct dng_timespec *latest_time)
{
    ValidateCond(cond);

    struct dng_timespec sys_timespec;

#if defined(_MSC_VER) && _MSC_VER >= 1900

    struct timespec temp;
    dng_pthread_now (&temp);

    sys_timespec.tv_sec = (long)temp.tv_sec;
    sys_timespec.tv_nsec = temp.tv_nsec;

#else

    dng_pthread_now (&sys_timespec);

#endif

    __int64 sys_time  = (__int64)sys_timespec.tv_sec * 1000000000 + sys_timespec.tv_nsec;
    __int64 lock_time = (__int64)latest_time->tv_sec * 1000000000 + latest_time->tv_nsec;

    int wait_millisecs = (int)((lock_time - sys_time + 500000) / 1000000);

    if (wait_millisecs < 0)
        wait_millisecs = 0;

    return cond_wait_internal(cond, mutex, wait_millisecs);
}

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

int dng_pthread_cond_signal(dng_pthread_cond_t *cond)
{
    ValidateCond(cond);

#if qDNGUseConditionVariable

    WakeConditionVariable(&(*cond)->cond);
    return 0;

#else

    waiter *first;
    dng_pthread_cond_impl &real_cond = **cond;

    {
        ScopedLock lock(real_cond.lock);

        first = real_cond.head_waiter;
        if (first != NULL)
        {
            if (first->next != NULL)
                first->next->prev = NULL;
            else
                real_cond.tail_waiter = NULL; // Or first->prev, which is always NULL in this case

            first->chosen_by_signal = true;

            real_cond.head_waiter = first->next;
        }
    }

    if (first != NULL)
        ::ReleaseSemaphore(first->semaphore, 1, NULL);

    return 0;
#endif
}

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

int dng_pthread_cond_broadcast(dng_pthread_cond_t *cond)
{
    ValidateCond(cond);

#if qDNGUseConditionVariable

    WakeAllConditionVariable(&(*cond)->cond);
    return 0;

#else

    waiter *first;
    dng_pthread_cond_impl &real_cond = **cond;

    {
        ScopedLock lock(real_cond.lock);

        first = real_cond.head_waiter;
        real_cond.head_waiter = NULL;
        real_cond.tail_waiter = NULL;

        real_cond.broadcast_generation++;
    }

    while (first != NULL)
    {
        waiter *next = first->next;
        ::ReleaseSemaphore(first->semaphore, 1, NULL);
        first = next;
    }

    return 0;
#endif
}

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

int dng_pthread_once(dng_pthread_once_t *once, void (*init_func)())
{
    if (once == NULL || init_func == NULL)
        return EINVAL;

    if (once->inited)
        return 0;

    if (::InterlockedIncrement(&once->semaphore) == 0)
    {
        init_func();
        once->inited = 1;
    }
    else
    {
        while (!once->inited)
            Sleep(0);
    }

    return 0;
}

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

int dng_pthread_key_create(dng_pthread_key_t * key, void (*destructor) (void *))
{
    if (destructor != NULL)
        return -1;

    DWORD result = ::TlsAlloc();
    if (result == TLS_OUT_OF_INDEXES)
        return -1;
    *key = (unsigned long)result;
    return 0;
}

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

int dng_pthread_key_delete(dng_pthread_key_t key)
{
    if (::TlsFree((DWORD)key))
        return 0;
    return -1;
}

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

int dng_pthread_setspecific(dng_pthread_key_t key, const void *value)
{
    if (::TlsSetValue((DWORD)key, const_cast<void *>(value)))
        return 0;
    return -1;
}

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

void *dng_pthread_getspecific(dng_pthread_key_t key)
{
    return ::TlsGetValue((DWORD)key);
}

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

#if !qDNGUseConditionVariable

namespace {
    struct rw_waiter {
        struct rw_waiter *prev;
        struct rw_waiter *next;
        HANDLE semaphore;
        bool is_writer;
    };
}

#endif

struct dng_pthread_rwlock_impl
{


#if qDNGUseConditionVariable
    SRWLOCK rwlock;
    bool fWriteLockExclusive;

    dng_pthread_rwlock_impl ()  { InitializeSRWLock(&rwlock); }
    ~dng_pthread_rwlock_impl () { } // no delete listed


#else
    dng_pthread_mutex_impl mutex;

    rw_waiter *head_waiter;
    rw_waiter *tail_waiter;

    unsigned long readers_active;
    unsigned long writers_waiting;
    bool writer_active;

    dng_pthread_cond_impl read_wait;
    dng_pthread_cond_impl write_wait;

    dng_pthread_rwlock_impl ()
        : mutex ()
        , head_waiter (NULL)
        , tail_waiter (NULL)
        , readers_active (0)
        , writers_waiting (0)
        , writer_active (false)
        , read_wait ()
        , write_wait ()
    {
    }

    ~dng_pthread_rwlock_impl ()
    {
    }

    void WakeHeadWaiter ()
    {
        HANDLE semaphore = head_waiter->semaphore;

        head_waiter = head_waiter->next;
        if (head_waiter == NULL)
            tail_waiter = NULL;

        ::ReleaseSemaphore(semaphore, 1, NULL);
    }
#endif

    // Non copyable
private:

    dng_pthread_rwlock_impl &operator=(const dng_pthread_rwlock_impl &);
    dng_pthread_rwlock_impl (const dng_pthread_rwlock_impl &);

};

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

int dng_pthread_rwlock_init(dng_pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attrs)
{
    dng_pthread_rwlock_impl *newRWLock;

    newRWLock = new (std::nothrow) dng_pthread_rwlock_impl;
    if (newRWLock == NULL)
        return -1; // ENOMEM;

    *rwlock = newRWLock;

    return 0;
}

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

int dng_pthread_rwlock_destroy(dng_pthread_rwlock_t *rwlock)
{
    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

#if !qDNGUseConditionVariable

    {
        ScopedLock lock (real_rwlock.mutex);

        if (real_rwlock.head_waiter != NULL ||
            real_rwlock.readers_active != 0 ||
            real_rwlock.writers_waiting != 0 ||
            real_rwlock.writer_active)
            return -1; // EBUSY
    }
#endif

    delete *rwlock;
    *rwlock = NULL;
    return 0;
}

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

#if !qDNGUseConditionVariable

#define CHECK_RWLOCK_STATE(real_rwlock) \
    DNG_ASSERT (!real_rwlock.writer_active || real_rwlock.readers_active == 0, "dng_pthread_rwlock_t logic error")

#endif

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

int dng_pthread_rwlock_rdlock(dng_pthread_rwlock_t *rwlock)
{
#if qDNGUseConditionVariable
    // Note: Acquire cannot be called resursively from same thread, once acquired or deadlock will occur

    AcquireSRWLockShared(&(*rwlock)->rwlock);
    (*rwlock)->fWriteLockExclusive = false;

    return 0;

#else

    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

    struct rw_waiter this_wait;
    bool doWait = false;;
    int result = 0;
    HANDLE semaphore=NULL;

        {

        ScopedLock lock (real_rwlock.mutex);

        CHECK_RWLOCK_STATE (real_rwlock);

        if (real_rwlock.writers_waiting > 0 || real_rwlock.writer_active)
        {
            semaphore = GetThreadSemaphore();

            this_wait.next = NULL;
            this_wait.semaphore = semaphore;
            this_wait.is_writer = false;

            // Add this waiter to the end of the list.
            this_wait.prev = real_rwlock.tail_waiter;
            if (real_rwlock.tail_waiter != NULL)
                real_rwlock.tail_waiter->next = &this_wait;
            real_rwlock.tail_waiter = &this_wait;

            // If the list was empty, set the head of the list to this waiter.
            if (real_rwlock.head_waiter == NULL)
                real_rwlock.head_waiter = &this_wait;

            doWait = true;
        }
        else
            real_rwlock.readers_active++;
    }

    if (result == 0 && doWait)
        result = (WaitForSingleObject(semaphore, INFINITE) == WAIT_OBJECT_0) ? 0 : -1;

    return result;
#endif
}

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

int dng_pthread_rwlock_tryrdlock(dng_pthread_rwlock_t *rwlock)
{
#if qDNGUseConditionVariable

     if (TryAcquireSRWLockExclusive(&(*rwlock)->rwlock) == 0)
         return 0;

    (*rwlock)->fWriteLockExclusive = false;
    return -1;

#else
    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

    ScopedLock lock (real_rwlock.mutex);

    CHECK_RWLOCK_STATE (real_rwlock);

    if (real_rwlock.writers_waiting == 0 && !real_rwlock.writer_active)
    {
        real_rwlock.readers_active++;
        return 0;
    }

    return -1;
#endif
}

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

int dng_pthread_rwlock_trywrlock(dng_pthread_rwlock_t *rwlock)
{
#if qDNGUseConditionVariable

    if (TryAcquireSRWLockShared(&(*rwlock)->rwlock) == 0)
        return 0;

    (*rwlock)->fWriteLockExclusive = true;
    return -1;

#else
    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

    ScopedLock lock (real_rwlock.mutex);

    CHECK_RWLOCK_STATE (real_rwlock);

    if (real_rwlock.readers_active == 0 &&
        real_rwlock.writers_waiting == 0 &&
        !real_rwlock.writer_active)
        {
        real_rwlock.writer_active = true;
        return 0;
        }

    return -1;
#endif
}

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

int dng_pthread_rwlock_unlock(dng_pthread_rwlock_t *rwlock)
    {
#if qDNGUseConditionVariable

    if ((*rwlock)->fWriteLockExclusive)
        ReleaseSRWLockExclusive(&(*rwlock)->rwlock);
    else
        ReleaseSRWLockShared(&(*rwlock)->rwlock);

    return 0;

#else
    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

    int result = 0;

    ScopedLock lock (real_rwlock.mutex);

    CHECK_RWLOCK_STATE (real_rwlock);

    if (real_rwlock.readers_active > 0)
        --real_rwlock.readers_active;
    else
        real_rwlock.writer_active = false;

    while (real_rwlock.head_waiter != NULL)
    {
        if (real_rwlock.head_waiter->is_writer)
        {
            if (real_rwlock.readers_active == 0)
            {
                real_rwlock.writers_waiting--;
                real_rwlock.writer_active = true;
                real_rwlock.WakeHeadWaiter ();
            }

            break;
        }
        else
        {
            ++real_rwlock.readers_active;
            real_rwlock.WakeHeadWaiter ();
        }
    }

    return result;
#endif
    }

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

int dng_pthread_rwlock_wrlock(dng_pthread_rwlock_t *rwlock)
    {
#if qDNGUseConditionVariable

     AcquireSRWLockExclusive(&(*rwlock)->rwlock);
     (*rwlock)->fWriteLockExclusive = true;

     return 0;

#else
    dng_pthread_rwlock_impl &real_rwlock = **rwlock;

    int result = 0;
    struct rw_waiter this_wait;
    HANDLE semaphore=NULL;
    bool doWait = false;

    {
        ScopedLock lock (real_rwlock.mutex);

        CHECK_RWLOCK_STATE (real_rwlock);

        if (real_rwlock.readers_active ||
            real_rwlock.writers_waiting ||
            real_rwlock.writer_active)
            {
            semaphore = GetThreadSemaphore();

            this_wait.next = NULL;
            this_wait.semaphore = semaphore;
            this_wait.is_writer = true;

            // Add this waiter to the end of the list.
            this_wait.prev = real_rwlock.tail_waiter;
            if (real_rwlock.tail_waiter != NULL)
                real_rwlock.tail_waiter->next = &this_wait;
            real_rwlock.tail_waiter = &this_wait;

            // If the list was empty, set the head of the list to this waiter.
            if (real_rwlock.head_waiter == NULL)
                real_rwlock.head_waiter = &this_wait;

            real_rwlock.writers_waiting++;

            doWait = true;
        }
        else
            real_rwlock.writer_active = true;
    }

    if (result == 0 && doWait)
        result = (WaitForSingleObject(semaphore, INFINITE) == WAIT_OBJECT_0) ? 0 : -1;

    return result;
#endif
    }

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

void dng_pthread_disassociate()
{
    FreeThreadSemaphore();
}

void dng_pthread_terminate()
    {
    finalize_thread_TLS();
    }

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

}   // extern "C"

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

#endif

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

int dng_pthread_now (struct timespec *now)
    {

    if (now == NULL)
        return -1; // EINVAL

    #if qWinOS

    FILETIME ft;
    ::GetSystemTimeAsFileTime(&ft);

    __int64 sys_time = ((__int64)ft.dwHighDateTime << 32) + ft.dwLowDateTime;

    #define SecsFrom1601To1970 11644473600

    sys_time -= SecsFrom1601To1970 * 10000000LL;

    sys_time *= 100;    // Convert from 100ns to 1ns units

    now->tv_sec  = (long)(sys_time / 1000000000);
    now->tv_nsec = (long)(sys_time % 1000000000);

    #else

    struct timeval tv;

    if (gettimeofday (&tv, NULL) != 0)
        return errno;

    now->tv_sec  = tv.tv_sec;
    now->tv_nsec = tv.tv_usec * 1000;

    #endif

    return 0;

    }

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

#endif // qDNGThreadSafe

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