#include "pch.h"
#include "System/Threading/Semaphore.h"
#include "System/Threading/SemaphoreFullException.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>
#include <condition_variable>

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

namespace DotNetDupe {
    namespace System {
        namespace Threading {

            struct Semaphore::Impl {
                std::mutex mutex;
                std::condition_variable cv;
            };

            Semaphore::Semaphore(int initialCount, int maximumCount)
                : _count(initialCount), _maxCount(maximumCount), _name(""), _hHandle(nullptr), _pImpl(new Impl()) {}

            static bool s_semDummyCreatedNew = false;
            Semaphore::Semaphore(const String& sName, int initialCount, int maximumCount, bool openAlways)
                : Semaphore(initialCount, maximumCount, sName, openAlways, s_semDummyCreatedNew) {}

            Semaphore::Semaphore(int initialCount, int maximumCount, const String& sName, bool openAlways)
                : Semaphore(initialCount, maximumCount, sName, openAlways, s_semDummyCreatedNew) {}

#if defined(_WIN32)
            static HANDLE OpenOrCreateWin32Semaphore(const std::wstring& wsName, int initialCount, int maximumCount, bool openAlways, bool& bCreatedNew) {
                HANDLE hHandle = ::CreateSemaphoreW(NULL, initialCount, maximumCount, wsName.c_str());
                if (hHandle != NULL) {
                    bCreatedNew = (::GetLastError() != ERROR_ALREADY_EXISTS);
                    return hHandle;
                }
                if (::GetLastError() == ERROR_ACCESS_DENIED) {
                    throw UnauthorizedAccessException("Access denied creating Semaphore synchronization object.");
                }
                bCreatedNew = false;
                if (!openAlways) {
                    throw WaitHandleCannotBeOpenedException("Semaphore creation returned null handle and openAlways is false.");
                }
                hHandle = ::OpenSemaphoreW(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, FALSE, wsName.c_str());
                if (hHandle == NULL) {
                    if (::GetLastError() == ERROR_ACCESS_DENIED) {
                        throw UnauthorizedAccessException("Access denied opening existing Semaphore synchronization object.");
                    }
                    throw WaitHandleCannotBeOpenedException("Failed to open existing semaphore with SYNCHRONIZE access.");
                }
                return hHandle;
            }
#endif

            Semaphore::Semaphore(int initialCount, int maximumCount, const String& sName, bool openAlways, bool& bCreatedNew)
                : _count(initialCount), _maxCount(maximumCount), _name(sName), _hHandle(nullptr), _pImpl(new Impl()) {
#if defined(_WIN32)
                if (!_name.IsEmpty()) {
                    std::wstring wsName = Utils::StringConvert::Utf8ToWChar(_name.GetRawString());
                    _hHandle = OpenOrCreateWin32Semaphore(wsName, initialCount, maximumCount, openAlways, bCreatedNew);
                } else {
                    bCreatedNew = true;
                }
#else
                bCreatedNew = true;
#endif
            }

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

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

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

            bool Semaphore::WaitOne() {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    DWORD dwWaitResult = ::WaitForSingleObject((HANDLE)_hHandle, INFINITE);
                    return (dwWaitResult == WAIT_OBJECT_0);
                }
#endif
                if (!_pImpl) return false;
                std::unique_lock<std::mutex> lock(_pImpl->mutex);
                _pImpl->cv.wait(lock, [this]() { return _count > 0; });
                --_count;
                return true;
            }

            static bool WaitForSemaphoreCv(Semaphore::Impl* pImpl, int& count, int msTimeout) {
                std::unique_lock<std::mutex> lock(pImpl->mutex);
                bool bRes = pImpl->cv.wait_for(lock, std::chrono::milliseconds(msTimeout), [&count]() { return count > 0; });
                if (!bRes) throw TimeoutException("The wait operation timed out.");
                --count;
                return true;
            }

            static int ReleaseSemaphoreCv(Semaphore::Impl* pImpl, int& count, int maxCount, int releaseCount) {
                std::lock_guard<std::mutex> lock(pImpl->mutex);
                if (count + releaseCount > maxCount) {
                    throw SemaphoreFullException("Semaphore count exceeded maximum count.");
                }
                int prev = count;
                count += releaseCount;
                for (int i = 0; i < releaseCount; ++i) pImpl->cv.notify_one();
                return prev;
            }

            bool Semaphore::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);
                }
#endif
                if (!_pImpl) return false;
                return WaitForSemaphoreCv(_pImpl, _count, millisecondsTimeout);
            }

            int Semaphore::Release(int releaseCount) {
#if defined(_WIN32)
                if (_hHandle != nullptr) {
                    LONG previousCount = 0;
                    if (!::ReleaseSemaphore((HANDLE)_hHandle, releaseCount, &previousCount)) {
                        throw SemaphoreFullException("Semaphore count exceeded maximum count.");
                    }
                    return (int)previousCount;
                }
#endif
                if (!_pImpl) return 0;
                return ReleaseSemaphoreCv(_pImpl, _count, _maxCount, releaseCount);
            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)