// Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#ifndef QREADWRITELOCK_P_H
#define QREADWRITELOCK_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists for the convenience
// of the implementation.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtCore/private/qlocking_p.h>
#include <QtCore/private/qwaitcondition_p.h>
#include <QtCore/qreadwritelock.h>
#include <QtCore/qvarlengtharray.h>

QT_REQUIRE_CONFIG(thread);

QT_BEGIN_NAMESPACE

class QReadWriteLockPrivate
{
public:
    // export the State constants protected in QReadWriteLock
    static constexpr quintptr StateLockedForRead = QReadWriteLock::StateLockedForRead;
    static constexpr quintptr StateLockedForWrite = QReadWriteLock::StateLockedForWrite;
    static constexpr quintptr StateMask = QReadWriteLock::StateMask;
    static constexpr quintptr MultiplyLocked = QReadWriteLock::Counter;
    static constexpr quintptr IsRecursiveLock = MultiplyLocked << 1;
    static constexpr quintptr RecursivelyLockedForWrite =
            StateLockedForWrite | MultiplyLocked | IsRecursiveLock;

    explicit QReadWriteLockPrivate(bool isRecursive = false)
        : recursive(isRecursive) {}

    alignas(QtPrivate::IdealMutexAlignment) std::condition_variable writerCond;
    std::condition_variable readerCond;

    alignas(QtPrivate::IdealMutexAlignment) std::mutex mutex;
    int readerCount = 0;
    int writerCount = 0;
    int waitingReaders = 0;
    int waitingWriters = 0;
    const bool recursive;

    //Called with the mutex locked
    bool lockForWrite(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout);
    bool lockForRead(std::unique_lock<std::mutex> &lock, QDeadlineTimer timeout);
    void unlock();

    //memory management
    int id = 0;
    void release();
    static QReadWriteLockPrivate *allocate();

    // Recursive mutex handling
    Qt::HANDLE currentWriter = {};

    struct Reader {
        Qt::HANDLE handle;
        int recursionLevel;
    };

    QVarLengthArray<Reader, 16> currentReaders;

    // called with the mutex unlocked
    bool recursiveLockForWrite(QDeadlineTimer timeout);
    bool recursiveLockForRead(QDeadlineTimer timeout);
    void recursiveUnlock();

    /*!
     * \internal
     * Describes the state of the QReadWriteLock whose private is \a dd.
     *
     * Returns one of:
     * \list
     *   \li 0 (unlocked)
     *   \li \c IsRecursiveLock (still unlocked)
     *   \li \c StateLockedForRead
     *   \li \c{StateLockedForRead | IsRecursiveLock}
     *   \li \c StateLockedForWrite
     *   \li \c{StateLockedForWrite | IsRecursiveLock}
     *   \li \c{StateLockedForWrite | IsRecursiveLock | MultiplyLocked} = \c RecursivelyLockedForWrite
     * \endlist
     */
    static quintptr describeState(void *dd) noexcept
    {
        quintptr u = quintptr(dd);
        if (quintptr state = u & StateMask) {
            // Not a pointer: a non-recursive, uncontended lock. The upper bits
            // may hold a reader count, which we don't report.
            return state;
        }
        if (!u)
            return 0; // unlocked

        auto d = static_cast<QReadWriteLockPrivate *>(dd);
        const auto lock = qt_scoped_lock(d->mutex);
        u = 0;
        if (d->writerCount)
            u |= StateLockedForWrite;
        else if (d->readerCount)
            u |= StateLockedForRead;
        if (d->recursive) {
            u |= IsRecursiveLock;
            if (d->writerCount > 1) {
                u |= MultiplyLocked;
                Q_ASSERT(u == RecursivelyLockedForWrite);
            }
        } else {
            // non-recursive, quick check of the state
            Q_ASSERT(d->writerCount <= 1);
        }
        return u;
    }

    // used by QWaitCondition::wait
    template <typename Prep, typename DoWait>
    static bool waitConditionWait(QReadWriteLock *readWriteLock, Prep &&prep, DoWait &&doWait);
};
Q_DECLARE_TYPEINFO(QReadWriteLockPrivate::Reader, Q_PRIMITIVE_TYPE);

template <typename Prep, typename DoWait>
inline bool QReadWriteLockPrivate::waitConditionWait(QReadWriteLock *readWriteLock, Prep &&prep, DoWait &&doWait)
{
    if (!readWriteLock)
        return false;
    auto previousState = describeState(readWriteLock->d_ptr.loadAcquire());
    if (previousState == 0)     // unlocked
        return false;
    if (previousState == RecursivelyLockedForWrite) {
        qWarning("QWaitCondition: cannot wait on QReadWriteLocks with recursive lockForWrite()");
        return false;
    }

    prep();
    readWriteLock->unlock();
    bool returnValue = doWait();

    // relock
    if (previousState & StateLockedForWrite)
        readWriteLock->lockForWrite();
    else
        readWriteLock->lockForRead();

    return returnValue;
}

QT_END_NAMESPACE

#endif // QREADWRITELOCK_P_H
