#include "pch.h"
#include "System/Threading/Mutex.h"
#include "System/Threading/WaitHandleCannotBeOpenedException.h"
#include "System/TimeoutException.h"
#include "System/UnauthorizedAccessException.h"
#include "System/Char.h"
#include "System/Utils/StringConvert.h"
#include "System/SmartPointer.h"
#include <chrono>
#include <mutex>

#if defined(_WIN32)
#include <windows.h>
#endif

namespace DotNetDupe {
    namespace System {
        namespace Threading {

            struct Mutex::Impl {
                std::timed_mutex mutex;
            };

            Mutex::Mutex() : _name(""), _hHandle(nullptr), _pImpl(new Impl()) {}

            Mutex::Mutex(bool bInitiallyOwned) : _name(""), _hHandle(nullptr), _pImpl(new Impl()) {
                if (bInitiallyOwned) {
                    _pImpl->mutex.lock();
                }
            }

            static bool s_mutexDummyCreatedNew = false;
            Mutex::Mutex(const String& sName, bool bInitiallyOwned, bool openAlways)
                : Mutex(bInitiallyOwned, sName, openAlways, s_mutexDummyCreatedNew) {}

            Mutex::Mutex(bool bInitiallyOwned, const String& sName, bool openAlways)
                : Mutex(bInitiallyOwned, sName, openAlways, s_mutexDummyCreatedNew) {}

#if defined(_WIN32)
            static HANDLE OpenOrCreateWin32Mutex(const std::wstring& wsName, bool bInitiallyOwned, bool openAlways, bool& bCreatedNew) {
                HANDLE hHandle = ::CreateMutexW(NULL, bInitiallyOwned ? TRUE : FALSE, wsName.c_str());
                if (hHandle != NULL) {
                    bCreatedNew = (::GetLastError() != ERROR_ALREADY_EXISTS);
                    return hHandle;
                }
                if (::GetLastError() == ERROR_ACCESS_DENIED) {
                    throw UnauthorizedAccessException("Access denied creating Mutex synchronization object.");
                }
                bCreatedNew = false;
                if (!openAlways) {
                    throw WaitHandleCannotBeOpenedException("Mutex creation returned null handle and openAlways is false.");
                }
                hHandle = ::OpenMutexW(SYNCHRONIZE, FALSE, wsName.c_str());
                if (hHandle == NULL) {
                    if (::GetLastError() == ERROR_ACCESS_DENIED) {
                        throw UnauthorizedAccessException("Access denied opening existing Mutex synchronization object.");
                    }
                    throw WaitHandleCannotBeOpenedException("Failed to open existing mutex with SYNCHRONIZE access.");
                }
                return hHandle;
            }
#endif

            Mutex::Mutex(bool bInitiallyOwned, const String& sName, bool openAlways, bool& bCreatedNew)
                : _name(sName), _hHandle(nullptr), _pImpl(new Impl()) {
#if defined(_WIN32)
                if (!_name.IsEmpty()) {
                    std::wstring wsName = Utils::StringConvert::Utf8ToWChar(_name.GetRawString());
                    _hHandle = OpenOrCreateWin32Mutex(wsName, bInitiallyOwned, openAlways, bCreatedNew);
                } else {
                    bCreatedNew = true;
                    if (bInitiallyOwned) {
                        _pImpl->mutex.lock();
                    }
                }
#else
                bCreatedNew = true;
                if (bInitiallyOwned) {
                    _pImpl->mutex.lock();
                }
#endif
            }

            Mutex::~Mutex() {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    ::CloseHandle((HANDLE)_hHandle);
                    _hHandle = nullptr;
                }
#endif
                if (_pImpl != nullptr) {
                    delete _pImpl;
                    _pImpl = nullptr;
                }
            }

            SmartPointer<Mutex> Mutex::OpenExisting(const String& sName) {
                SmartPointer<Mutex> pResult = nullptr;
                if (TryOpenExisting(sName, pResult)) {
                    return pResult;
                }
                throw WaitHandleCannotBeOpenedException("No mutex handle of the given name exists.");
            }

            bool Mutex::TryOpenExisting(const String& sName, SmartPointer<Mutex>& pResult) {
                pResult = SmartPointer<Mutex>();
                if (sName.IsEmpty()) return false;
#if defined(_WIN32)
                std::wstring wsName = Utils::StringConvert::Utf8ToWChar(sName.GetRawString());
                HANDLE h = ::OpenMutexW(SYNCHRONIZE, FALSE, wsName.c_str());
                if (h == NULL) return false;
                SmartPointer<Mutex> spM = SmartPointer<Mutex>::NewShared();
                spM->_name = sName;
                spM->_hHandle = h;
                pResult = std::move(spM);
                return true;
#else
                return false;
#endif
            }

            bool Mutex::WaitOne() {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    DWORD dwWaitResult = ::WaitForSingleObject((HANDLE)_hHandle, INFINITE);
                    return (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED);
                }
#endif
                if (_pImpl) _pImpl->mutex.lock();
                return true;
            }

            bool Mutex::WaitOne(int millisecondsTimeout) {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    DWORD dwWaitResult = ::WaitForSingleObject((HANDLE)_hHandle, (DWORD)millisecondsTimeout);
                    if (dwWaitResult == WAIT_TIMEOUT) {
                        throw TimeoutException("The wait operation timed out.");
                    }
                    return (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_ABANDONED);
                }
#endif
                if (!_pImpl || !_pImpl->mutex.try_lock_for(std::chrono::milliseconds(millisecondsTimeout))) {
                    throw TimeoutException("The wait operation timed out.");
                }
                return true;
            }

            int Mutex::Release(int releaseCount) {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    ::ReleaseMutex((HANDLE)_hHandle);
                    return 0;
                }
#endif
                if (_pImpl) _pImpl->mutex.unlock();
                return 0;
            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)