// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#ifndef  BTHREAD_RWLOCK_H
#define  BTHREAD_RWLOCK_H

#include "bthread/types.h"
#include "bthread/bthread.h"
#include "butil/scoped_lock.h"

namespace bthread {

// The C++ Wrapper of bthread_rwlock

// NOTE: Not aligned to cacheline as the container of RWLock is practically aligned.

class RWLock {
public:
    typedef bthread_rwlock_t* native_handler_type;

    RWLock() {
        int rc = bthread_rwlock_init(&_rwlock, NULL);
        if (rc) {
            throw std::system_error(std::error_code(rc, std::system_category()),
                                    "RWLock constructor failed");
        }
    }

    ~RWLock() {
        CHECK_EQ(0, bthread_rwlock_destroy(&_rwlock));
    }

    DISALLOW_COPY_AND_ASSIGN(RWLock);

    native_handler_type native_handler() { return &_rwlock; }

    void rdlock() {
        int rc = bthread_rwlock_rdlock(&_rwlock);
        if (rc) {
            throw std::system_error(std::error_code(rc, std::system_category()),
                                    "RWLock rdlock failed");
        }
    }

    bool try_rdlock() {
        return 0 == bthread_rwlock_tryrdlock(&_rwlock);
    }

    bool timed_rdlock(const struct timespec* abstime) {
        return 0 == bthread_rwlock_timedrdlock(&_rwlock, abstime);
    }

    void wrlock() {
        int rc = bthread_rwlock_wrlock(&_rwlock);
        if (rc) {
            throw std::system_error(std::error_code(rc, std::system_category()),
                                    "RWLock wrlock failed");
        }
    }

    bool try_wrlock() {
        return 0 == bthread_rwlock_trywrlock(&_rwlock);
    }

    bool timed_wrlock(const struct timespec* abstime) {
        return 0 == bthread_rwlock_timedwrlock(&_rwlock, abstime);
    }

    void unlock() { bthread_rwlock_unlock(&_rwlock); }

private:
    bthread_rwlock_t _rwlock{};
};

// Read lock guard of rwlock.
class RWLockRdGuard {
public:
    explicit RWLockRdGuard(bthread_rwlock_t& rwlock)
        : _rwlock(&rwlock) {
#if !defined(NDEBUG)
        const int rc = bthread_rwlock_rdlock(_rwlock);
        if (rc) {
            LOG(FATAL) << "Fail to rdlock bthread_rwlock_t=" << _rwlock << ", " << berror(rc);
            _rwlock = NULL;
        }
#else
        bthread_rwlock_rdlock(_rwlock);
#endif // NDEBUG
    }

    explicit RWLockRdGuard(RWLock& rwlock)
        : RWLockRdGuard(*rwlock.native_handler()) {}

    ~RWLockRdGuard() {
#ifndef NDEBUG
        if (NULL != _rwlock) {
            bthread_rwlock_unlock(_rwlock);
        }
#else
        bthread_rwlock_unlock(_rwlock);
#endif // NDEBUG
    }

    DISALLOW_COPY_AND_ASSIGN(RWLockRdGuard);

private:
    bthread_rwlock_t* _rwlock;
};

// Write lock guard of rwlock.
class RWLockWrGuard {
public:
    explicit RWLockWrGuard(bthread_rwlock_t& rwlock)
        : _rwlock(&rwlock) {
#if !defined(NDEBUG)
        const int rc = bthread_rwlock_wrlock(_rwlock);
        if (rc) {
            LOG(FATAL) << "Fail to wrlock bthread_rwlock_t=" << _rwlock << ", " << berror(rc);
            _rwlock = NULL;
        }
#else
        bthread_rwlock_wrlock(_rwlock);
#endif // NDEBUG
    }

    explicit RWLockWrGuard(RWLock& rwlock)
        : RWLockWrGuard(*rwlock.native_handler()) {}

    ~RWLockWrGuard() {
#ifndef NDEBUG
        if (NULL != _rwlock) {
            bthread_rwlock_unlock(_rwlock);
        }
#else
        bthread_rwlock_unlock(_rwlock);
#endif // NDEBUG
    }

    DISALLOW_COPY_AND_ASSIGN(RWLockWrGuard);

private:
    bthread_rwlock_t* _rwlock;
};

} // namespace bthread

namespace std {

template <>
class lock_guard<bthread_rwlock_t> {
public:
    lock_guard(bthread_rwlock_t& rwlock, bool read)
        :_rwlock(&rwlock), _read(read) {
#if !defined(NDEBUG)
        int rc;
        if (_read) {
            rc = bthread_rwlock_rdlock(_rwlock);
        } else {
            rc = bthread_rwlock_wrlock(_rwlock);
        }
        if (rc) {
            LOG(FATAL) << "Fail to lock bthread_rwlock_t=" << _rwlock << ", " << berror(rc);
            _rwlock = NULL;
        }
#else
        if (_read) {
            bthread_rwlock_rdlock(_rwlock);
        } else {
            bthread_rwlock_wrlock(_rwlock);
        }
#endif // NDEBUG
    }

    ~lock_guard() {
#ifndef NDEBUG
        if (NULL != _rwlock) {
            bthread_rwlock_unlock(_rwlock);
        }
#else
        bthread_rwlock_unlock(_rwlock);
#endif // NDEBUG
    }

    DISALLOW_COPY_AND_ASSIGN(lock_guard);

private:
    bthread_rwlock_t* _rwlock;
    bool _read;
};

template <>
class lock_guard<bthread::RWLock> {
public:
    lock_guard(bthread::RWLock& rwlock, bool read)
        :_rwlock_guard(*rwlock.native_handler(), read) {}

    DISALLOW_COPY_AND_ASSIGN(lock_guard);

private:
    std::lock_guard<bthread_rwlock_t> _rwlock_guard;
};

} // namespace std

#endif  //BTHREAD_RWLOCK_H
