/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */
#ifndef BU_READ_WRITE_MUTEX_H
#define BU_READ_WRITE_MUTEX_H

#include "bu/mutex.h"
#include "bu/condition.h"

namespace Bu
{
    /**
     * Mutex designed for situations where overlapped reading is safe, but
     * overlapped writing isn't.  There are many, many good examples of this
     * including most data structures, streams, etc. etc.
     *
     * Use this just like a normal mutex except that you use the
     * lockRead/unlockRead and lockWrite/unlockWrite functions depending on
     * weather the section of code your locking is reading data or changing
     * data.
     *
     * This particular mutex is designed so that while a read operation is
     * happening other read operations can also happen, but no write operations
     * can occur.  While a write is happening, no other write or read operation
     * can continue.  There is an extra feature to ensure writes get a chance
     * to complete, when a lockWrite is issued, all current read operations
     * continue, but future read operations block until the write is complete.
     */
    class ReadWriteMutex
    {
    public:
        ReadWriteMutex();
        virtual ~ReadWriteMutex();

        /**
         * Lock the mutex for reading.  Multiple code sections can hold a read
         * lock at the same time, but write locks will wait for all read locks
         * to be released before continuing.  Read locks will also wait if
         * there is an active write lock.
         *
         * It is very important to not make any changes to your data within
         * a read lock.
         */
        void lockRead();

        /**
         * Release a read lock.
         */
        void unlockRead();

        /**
         * Lock the mutex for writing.  Only one code section can have a write
         * lock at any given time.  No code sections can be in a locked read
         * section while a write lock is held.  When a write lock is requested
         * all following read locks will block until the write operation is
         * started, ensuring writes always get a chance to execute.
         *
         * Within a write locked code section feel free to change your data
         * and read your data.  It is imparative to spend as little time as
         * possible in a write-locked section.
         */
        void lockWrite();

        /**
         * Release a write lock.
         */
        void unlockWrite();

        class ReadLocker
        {
        public:
            ReadLocker( ReadWriteMutex &m ) :
                m( m )
            {
                m.lockRead();
            }

            ~ReadLocker()
            {
                m.unlockRead();
            }

        private:
            ReadWriteMutex &m;
        };

        class WriteLocker
        {
        public:
            WriteLocker( ReadWriteMutex &m ) :
                m( m )
            {
                m.lockWrite();
            }

            ~WriteLocker()
            {
                m.unlockWrite();
            }

        private:
            ReadWriteMutex &m;
        };

    private:
        Bu::Mutex mRead;
        int iCounter;
        Bu::Mutex mWrite;
        Bu::Condition cWrite;
        bool bWantWrite;
    };
};

#endif
