/*
 * SPDX-FileCopyrightText: 1997-1999 AT&T Cambridge Laboratories
 * SPDX-FileCopyrightText: 2004 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#ifndef TANGO_SERVER_READERSWRITERSLOCK_H
#define TANGO_SERVER_READERSWRITERSLOCK_H

#include <tango/common/omnithread_wrapper.h>

namespace Tango
{
class ReadersWritersLock
{
  public:
    omni_mutex mut;
    omni_condition cond;
    int n{0}; // 0 means no-one active, > 0 means n readers, < 0 means writer
              // (-n times).
    int writerId{0};

    ReadersWritersLock() :
        cond(&mut)

    {
    }

    void readerIn()
    {
        mut.lock();

        // In the case of usage with another threading library, omni_thread::self() might
        // return a nullptr pointer!
        int threadId = 0;
        omni_thread *th = omni_thread::self();
        if(th != nullptr)
        {
            threadId = th->id();
        }

        if((n < 0) && (writerId == threadId))
        {
            // this thread already has lock as writer, simply decrement n
            n--;
            mut.unlock();
            return;
        }
        while(n < 0)
        {
            cond.wait();
        }
        n++;
        mut.unlock();
    }

    void readerOut()
    {
        mut.lock();
        if(n < 0)
        {
            // this thread already had lock as writer, simply increment n
            n++;
            mut.unlock();
            return;
        }
        n--;
        if(n == 0)
        {
            cond.signal();
        }
        mut.unlock();
    }

    void writerIn()
    {
        mut.lock();

        // In the case of usage with another threading library, omni_thread::self() might
        // return a nullptr pointer!
        int threadId = 0;
        omni_thread *th = omni_thread::self();
        if(th != nullptr)
        {
            threadId = th->id();
        }

        if((n < 0) && (writerId == threadId))
        {
            // this thread already has lock as writer, simply decrement n
            n--;
            mut.unlock();
            return;
        }
        while(n != 0)
        {
            cond.wait();
        }

        n--;

        // Now the writer lock was taken.
        // Make sure we get a correct thread ID
        // With the class ensure_self it should return always a thread ID.
        // Create the ensure_self object only for the thread which takes the writer lock!
        if(th == nullptr)
        {
            auto_self = new omni_thread::ensure_self();
        }
        writerId = omni_thread::self()->id();

        mut.unlock();
    }

    void writerOut()
    {
        mut.lock();
        n++;
        if(n == 0)
        {
            // delete the dummy thread when it was created.
            if(auto_self != nullptr)
            {
                delete auto_self;
                auto_self = nullptr;
            }

            cond.broadcast(); // might as well wake up all readers
        }
        mut.unlock();
    }

  private:
    // in the case of usage with another threading library, omni_thread::self() might
    // return a nullptr pointer!
    // To avoid this problem we use the class ensure_self to get a dummy thread ID!
    //
    // The class ensure_self should be created on the stack. If created in
    // a thread without an associated omni_thread, it creates a dummy
    // thread which is released when the ensure_self object is deleted.

    omni_thread::ensure_self *auto_self{nullptr};
};
} // namespace Tango
#endif
