/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information

Abstract:
Part of Core Audio Public Utility Classes
*/

//==================================================================================================
//  Includes
//==================================================================================================

//  Self Include
#include "CAMutex.h"

#if TARGET_OS_MAC
    #include <errno.h>
#endif

//  PublicUtility Includes
#include "CADebugMacros.h"
#include "CAException.h"
#include "CAHostTimeBase.h"

//==================================================================================================
//  Logging
//==================================================================================================

#if CoreAudio_Debug
//  #define Log_Ownership       1
//  #define Log_Errors          1
//  #define Log_LongLatencies   1
//  #define LongLatencyThreshholdNS 1000000ULL  // nanoseconds
#endif

//==================================================================================================
//  CAMutex
//==================================================================================================

CAMutex::CAMutex(const char* inName)
:
    mName(inName),
    mOwner(0)
{
#if TARGET_OS_MAC
    OSStatus theError = pthread_mutex_init(&mMutex, NULL);
    ThrowIf(theError != 0, CAException(theError), "CAMutex::CAMutex: Could not init the mutex");

    #if Log_Ownership
        DebugPrintf("%p %.4f: CAMutex::CAMutex: creating %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);
    #endif
#elif TARGET_OS_WIN32
    mMutex = CreateMutex(NULL, false, NULL);
    ThrowIfNULL(mMutex, CAException(GetLastError()), "CAMutex::CAMutex: could not create the mutex.");

    #if Log_Ownership
        DebugPrintf("%lu %.4f: CAMutex::CAMutex: creating %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);
    #endif
#endif
}

CAMutex::~CAMutex()
{
#if TARGET_OS_MAC
    #if Log_Ownership
        DebugPrintf("%p %.4f: CAMutex::~CAMutex: destroying %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);
    #endif
    pthread_mutex_destroy(&mMutex);
#elif TARGET_OS_WIN32
    #if Log_Ownership
        DebugPrintf("%lu %.4f: CAMutex::~CAMutex: destroying %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), mName, mOwner);
    #endif
    if(mMutex != NULL)
    {
        CloseHandle(mMutex);
    }
#endif
}

bool    CAMutex::Lock()
{
    bool theAnswer = false;

#if TARGET_OS_MAC
    pthread_t theCurrentThread = pthread_self();
    if(!pthread_equal(theCurrentThread, mOwner))
    {
        #if Log_Ownership
            DebugPrintf("%p %.4f: CAMutex::Lock: thread %p is locking %s, owner: %p\n", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);
        #endif

        #if Log_LongLatencies
            UInt64 lockTryTime = CAHostTimeBase::GetCurrentTimeInNanos();
        #endif

        OSStatus theError = pthread_mutex_lock(&mMutex);
        ThrowIf(theError != 0, CAException(theError), "CAMutex::Lock: Could not lock the mutex");
        mOwner = theCurrentThread;
        theAnswer = true;

        #if Log_LongLatencies
            UInt64 lockAcquireTime = CAHostTimeBase::GetCurrentTimeInNanos();
            if (lockAcquireTime - lockTryTime >= LongLatencyThresholdNS)
                DebugPrintf("Thread %p took %.6fs to acquire the lock %s\n", theCurrentThread, (lockAcquireTime - lockTryTime) * 1.0e-9 /* nanos to seconds */, mName);
        #endif

        #if Log_Ownership
            DebugPrintf("%p %.4f: CAMutex::Lock: thread %p has locked %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
        #endif
    }
#elif TARGET_OS_WIN32
    if(mOwner != GetCurrentThreadId())
    {
        #if Log_Ownership
            DebugPrintf("%lu %.4f: CAMutex::Lock: thread %lu is locking %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
        #endif

        OSStatus theError = WaitForSingleObject(mMutex, INFINITE);
        ThrowIfError(theError, CAException(theError), "CAMutex::Lock: could not lock the mutex");
        mOwner = GetCurrentThreadId();
        theAnswer = true;

        #if Log_Ownership
            DebugPrintf("%lu %.4f: CAMutex::Lock: thread %lu has locked %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
        #endif
    }
#endif

    return theAnswer;
}

void    CAMutex::Unlock()
{
#if TARGET_OS_MAC
    if(pthread_equal(pthread_self(), mOwner))
    {
        #if Log_Ownership
            DebugPrintf("%p %.4f: CAMutex::Unlock: thread %p is unlocking %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
        #endif

        mOwner = 0;
        OSStatus theError = pthread_mutex_unlock(&mMutex);
        ThrowIf(theError != 0, CAException(theError), "CAMutex::Unlock: Could not unlock the mutex");

        #if Log_Ownership
            DebugPrintf("%p %.4f: CAMutex::Unlock: thread %p has unlocked %s, owner: %p\n", pthread_self(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), pthread_self(), mName, mOwner);
        #endif
    }
    else
    {
        DebugMessage("CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own");
    }
#elif TARGET_OS_WIN32
    if(mOwner == GetCurrentThreadId())
    {
        #if Log_Ownership
            DebugPrintf("%lu %.4f: CAMutex::Unlock: thread %lu is unlocking %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
        #endif

        mOwner = 0;
        bool wasReleased = ReleaseMutex(mMutex);
        ThrowIf(!wasReleased, CAException(GetLastError()), "CAMutex::Unlock: Could not unlock the mutex");

        #if Log_Ownership
            DebugPrintf("%lu %.4f: CAMutex::Unlock: thread %lu has unlocked %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
        #endif
    }
    else
    {
        DebugMessage("CAMutex::Unlock: A thread is attempting to unlock a Mutex it doesn't own");
    }
#endif
}

bool    CAMutex::Try(bool& outWasLocked)
{
    bool theAnswer = false;
    outWasLocked = false;

#if TARGET_OS_MAC
    pthread_t theCurrentThread = pthread_self();
    if(!pthread_equal(theCurrentThread, mOwner))
    {
        //  this means the current thread doesn't already own the lock
        #if Log_Ownership
            DebugPrintf("%p %.4f: CAMutex::Try: thread %p is try-locking %s, owner: %p\n", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);
        #endif

        //  go ahead and call trylock to see if we can lock it.
        int theError = pthread_mutex_trylock(&mMutex);
        if(theError == 0)
        {
            //  return value of 0 means we successfully locked the lock
            mOwner = theCurrentThread;
            theAnswer = true;
            outWasLocked = true;

            #if Log_Ownership
                DebugPrintf("%p %.4f: CAMutex::Try: thread %p has locked %s, owner: %p\n", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);
            #endif
        }
        else if(theError == EBUSY)
        {
            //  return value of EBUSY means that the lock was already locked by another thread
            theAnswer = false;
            outWasLocked = false;

            #if Log_Ownership
                DebugPrintf("%p %.4f: CAMutex::Try: thread %p failed to lock %s, owner: %p\n", theCurrentThread, ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), theCurrentThread, mName, mOwner);
            #endif
        }
        else
        {
            //  any other return value means something really bad happenned
            ThrowIfError(theError, CAException(theError), "CAMutex::Try: call to pthread_mutex_trylock failed");
        }
    }
    else
    {
        //  this means the current thread already owns the lock
        theAnswer = true;
        outWasLocked = false;
    }
#elif TARGET_OS_WIN32
    if(mOwner != GetCurrentThreadId())
    {
        //  this means the current thread doesn't own the lock
        #if Log_Ownership
            DebugPrintf("%lu %.4f: CAMutex::Try: thread %lu is try-locking %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
        #endif

        //  try to acquire the mutex
        OSStatus theError = WaitForSingleObject(mMutex, 0);
        if(theError == WAIT_OBJECT_0)
        {
            //  this means we successfully locked the lock
            mOwner = GetCurrentThreadId();
            theAnswer = true;
            outWasLocked = true;

            #if Log_Ownership
                DebugPrintf("%lu %.4f: CAMutex::Try: thread %lu has locked %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
            #endif
        }
        else if(theError == WAIT_TIMEOUT)
        {
            //  this means that the lock was already locked by another thread
            theAnswer = false;
            outWasLocked = false;

            #if Log_Ownership
                DebugPrintf("%lu %.4f: CAMutex::Try: thread %lu failed to lock %s, owner: %lu\n", GetCurrentThreadId(), ((Float64)(CAHostTimeBase::GetCurrentTimeInNanos()) / 1000000.0), GetCurrentThreadId(), mName, mOwner);
            #endif
        }
        else
        {
            //  any other return value means something really bad happenned
            ThrowIfError(theError, CAException(GetLastError()), "CAMutex::Try: call to lock the mutex failed");
        }
    }
    else
    {
        //  this means the current thread already owns the lock
        theAnswer = true;
        outWasLocked = false;
    }
#endif

    return theAnswer;
}

bool    CAMutex::IsFree() const
{
    return mOwner == 0;
}

bool    CAMutex::IsOwnedByCurrentThread() const
{
    bool theAnswer = true;

#if TARGET_OS_MAC
    theAnswer = pthread_equal(pthread_self(), mOwner);
#elif TARGET_OS_WIN32
    theAnswer = (mOwner == GetCurrentThreadId());
#endif

    return theAnswer;
}


CAMutex::Unlocker::Unlocker(CAMutex& inMutex)
:   mMutex(inMutex),
    mNeedsLock(false)
{
    Assert(mMutex.IsOwnedByCurrentThread(), "Major problem: Unlocker attempted to unlock a mutex not owned by the current thread!");

    mMutex.Unlock();
    mNeedsLock = true;
}

CAMutex::Unlocker::~Unlocker()
{
    if(mNeedsLock)
    {
        mMutex.Lock();
    }
}
