#include "tile/base/thread/rw_mutex.h"

#include "tile/base/make_unique.h"
#include "tile/base/thread/cond_var.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"

namespace tile {

class RWMutex::Impl {
public:
    Impl(PriorityPolicy policy) : policy_(policy) {}

    void WriteLock()
    {
        ScopedLock lock(m_);
        if (IsWriteMode() || IsReadMode()) { waiting_writers_++; }
        cv_.Wait(lock, [&] {
            if (IsWriteMode() || IsReadMode()) { return false; }

            if (policy_ == PriorityPolicy::ReadPreferring) { return waiting_readers_ == 0; }

            return true;
        });
        --waiting_writers_;
        SetWriteMode();
    }

    bool WriteTryLock()
    {
        ScopedLock lock(m_);
        if (IsWriteMode() || waiting_writers_ > 0) { return false; }
        if (policy_ == PriorityPolicy::ReadPreferring && waiting_readers_ > 0) { return false; }

        SetWriteMode();

        return true;
    }

    void WriteUnlock()
    {
        ScopedLock lock(m_);
        state_ = kNoLocked;
        cv_.NotifyAll();
    }

    void ReadLock()
    {
        ScopedLock lock(m_);
        AddReader();
        cv_.Wait(lock, [&] { return CanSwitchToReadMode(); });

        if (!IsReadMode()) {
            SetReadMode();
            cv_.NotifyAll();
        }
    }

    bool ReadTryLock()
    {
        ScopedLock lock(m_);
        if (!IsReadMode()) { return false; }

        if (policy_ == PriorityPolicy::WritePreferring && waiting_writers_ > 0) { return false; }

        return true;
    }

    void ReadUnlock() {}

private:
    bool CanSwitchToWriteMode() const
    {
        if (IsReadMode()) { return false; }
        return false;
    }

    bool CanSwitchToReadMode() const
    {
        if (IsWriteMode()) { return false; }

        if (policy_ == PriorityPolicy::WritePreferring && waiting_writers_ > 0) { return false; }

        return true;
    }

    bool IsWriteMode() const { return state_ == kLockedByWriter; }

    bool IsReadMode() const { return state_ == kLockedByReader; }

    void SetWriteMode() { state_ = kLockedByWriter; }

    void SetReadMode() { state_ = kLockedByReader; }

    void AddReader() { waiting_readers_++; }

    enum State {
        kNoLocked,
        kLockedByWriter,// 1 writer
        kLockedByReader,// waiting_readers_ readers
    };

private:
    Mutex m_;
    CondVar cv_;

    const PriorityPolicy policy_;
    State state_         = kNoLocked;
    int waiting_readers_ = 0;
    int waiting_writers_ = 0;

    std::unique_ptr<detail::mutex::RWMutexReadRefWrapper> read_mutex_;
    std::unique_ptr<detail::mutex::RWMutexWriteRefWrapper> write_mutex_;
};

RWMutex::RWMutex(PriorityPolicy policy) : impl_(make_unique<Impl>(policy)) {}

void
RWMutex::WriteLock()
{
    impl_->WriteLock();
}

bool
RWMutex::WriteTryLock()
{
    return impl_->WriteTryLock();
}

void
RWMutex::WriteUnlock()
{
    impl_->WriteUnlock();
}

void
RWMutex::ReadLock()
{
    impl_->ReadUnlock();
}

bool
RWMutex::ReadTryLock()
{
    return impl_->ReadTryLock();
}

void
RWMutex::ReadUnlock()
{
    impl_->ReadUnlock();
}

}// namespace tile
