/*******************************************************************************
* Copyright (c) 2026, Jan Koester jan.koester@gmx.net
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
*      notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
*      notice, this list of conditions and the following disclaimer in the
*      documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
*      names of its contributors may be used to endorse or promote products
*      derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#pragma once

#include <shared_mutex>

#if defined(__GLIBC__)
#include <pthread.h>
#else
#include <mutex>
#include <condition_variable>
#endif

namespace netplus {

#if defined(__GLIBC__)
// std::shared_mutex gives no portable way to request writer-preferring
// fairness, and glibc's default pthread_rwlock policy
// (PTHREAD_RWLOCK_PREFER_READER_NP) lets a steady stream of short-lived
// readers starve out a waiting writer indefinitely — reproduced live on a
// production mediadb node, where a connection's own congestion-wait retry
// loop (quic.cpp's sendStreamData(), calling pumpIncomingLocked() up to
// ~2000x per stalled chunk) kept re-acquiring a registry's shared lock for
// the duration of a large transfer, starving accept()'s one-shot exclusive
// lock for registering a brand-new connection. Same API surface as
// std::shared_mutex so it drops into existing std::shared_lock/
// std::unique_lock call sites unchanged.
class WriterPreferringSharedMutex {
public:
    WriterPreferringSharedMutex() {
        pthread_rwlockattr_t attr;
        pthread_rwlockattr_init(&attr);
        pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
        pthread_rwlock_init(&_lock, &attr);
        pthread_rwlockattr_destroy(&attr);
    }
    ~WriterPreferringSharedMutex() { pthread_rwlock_destroy(&_lock); }

    WriterPreferringSharedMutex(const WriterPreferringSharedMutex&) = delete;
    WriterPreferringSharedMutex& operator=(const WriterPreferringSharedMutex&) = delete;

    void lock() { pthread_rwlock_wrlock(&_lock); }
    void unlock() { pthread_rwlock_unlock(&_lock); }
    bool try_lock() { return pthread_rwlock_trywrlock(&_lock) == 0; }

    void lock_shared() { pthread_rwlock_rdlock(&_lock); }
    void unlock_shared() { pthread_rwlock_unlock(&_lock); }
    bool try_lock_shared() { return pthread_rwlock_tryrdlock(&_lock) == 0; }

private:
    pthread_rwlock_t _lock;
};
#else
// pthread_rwlockattr_setkind_np is a glibc extension, unavailable on
// musl/BSD/Windows builds (musl's pthread_rwlock has no writer-preferring
// mode at all). Same fairness fix as the glibc branch above, built instead
// from std::mutex/std::condition_variable — the classic writer-preferring
// readers-writers algorithm, portable to any platform with a C++11
// standard library. A waiting writer blocks new readers from acquiring
// (lock_shared()'s wait predicate checks _waitingWriters), so a sustained
// stream of readers can't starve it out; only readers already active when
// the writer arrives are allowed to finish.
class WriterPreferringSharedMutex {
public:
    WriterPreferringSharedMutex() = default;
    WriterPreferringSharedMutex(const WriterPreferringSharedMutex&) = delete;
    WriterPreferringSharedMutex& operator=(const WriterPreferringSharedMutex&) = delete;

    void lock() {
        std::unique_lock<std::mutex> lk(_mtx);
        ++_waitingWriters;
        _cv.wait(lk, [this] { return !_writerActive && _activeReaders == 0; });
        --_waitingWriters;
        _writerActive = true;
    }
    void unlock() {
        {
            std::lock_guard<std::mutex> lk(_mtx);
            _writerActive = false;
        }
        _cv.notify_all();
    }
    bool try_lock() {
        std::lock_guard<std::mutex> lk(_mtx);
        if (_writerActive || _activeReaders > 0) return false;
        _writerActive = true;
        return true;
    }

    void lock_shared() {
        std::unique_lock<std::mutex> lk(_mtx);
        _cv.wait(lk, [this] { return !_writerActive && _waitingWriters == 0; });
        ++_activeReaders;
    }
    void unlock_shared() {
        bool notify;
        {
            std::lock_guard<std::mutex> lk(_mtx);
            --_activeReaders;
            notify = (_activeReaders == 0);
        }
        if (notify) _cv.notify_all();
    }
    bool try_lock_shared() {
        std::lock_guard<std::mutex> lk(_mtx);
        if (_writerActive || _waitingWriters > 0) return false;
        ++_activeReaders;
        return true;
    }

private:
    std::mutex _mtx;
    std::condition_variable _cv;
    int _activeReaders = 0;
    int _waitingWriters = 0;
    bool _writerActive = false;
};
#endif

} // namespace netplus
