// 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.

// bthread - An M:N threading library to make applications more concurrent.

// Date: 2015/12/14 21:26:26

#ifndef  BTHREAD_CONDITION_VARIABLE_H
#define  BTHREAD_CONDITION_VARIABLE_H

#include "butil/time.h"
#include "bthread/mutex.h"

__BEGIN_DECLS
extern int bthread_cond_init(bthread_cond_t* __restrict cond,
                             const bthread_condattr_t* __restrict cond_attr);
extern int bthread_cond_destroy(bthread_cond_t* cond);
extern int bthread_cond_signal(bthread_cond_t* cond);
extern int bthread_cond_broadcast(bthread_cond_t* cond);
extern int bthread_cond_wait(bthread_cond_t* __restrict cond,
                             bthread_mutex_t* __restrict mutex);
extern int bthread_cond_timedwait(
    bthread_cond_t* __restrict cond,
    bthread_mutex_t* __restrict mutex,
    const struct timespec* __restrict abstime);
__END_DECLS

namespace bthread {

class ConditionVariable {
    DISALLOW_COPY_AND_ASSIGN(ConditionVariable);
public:
    typedef bthread_cond_t*         native_handler_type;
    
    ConditionVariable() {
        CHECK_EQ(0, bthread_cond_init(&_cond, NULL));
    }
    ~ConditionVariable() {
        CHECK_EQ(0, bthread_cond_destroy(&_cond));
    }

    native_handler_type native_handler() { return &_cond; }

    void wait(std::unique_lock<bthread::Mutex>& lock) {
        bthread_cond_wait(&_cond, lock.mutex()->native_handler());
    }

    void wait(std::unique_lock<bthread_mutex_t>& lock) {
        bthread_cond_wait(&_cond, lock.mutex());
    }

    template<typename Predicate>
    void wait(std::unique_lock<bthread::Mutex>& lock, Predicate p) {
        while (!p()) {
            bthread_cond_wait(&_cond, lock.mutex()->native_handler());
        }
    }

    template<typename Predicate>
    void wait(std::unique_lock<bthread_mutex_t>& lock, Predicate p) {
        while (!p()) {
            bthread_cond_wait(&_cond, lock.mutex());
        }
    }

    // Unlike std::condition_variable, we return ETIMEDOUT when time expires
    // rather than std::timeout
    int wait_for(std::unique_lock<bthread::Mutex>& lock,
                 long timeout_us) {
        return wait_until(lock, butil::microseconds_from_now(timeout_us));
    }

    int wait_for(std::unique_lock<bthread_mutex_t>& lock,
                 long timeout_us) {
        return wait_until(lock, butil::microseconds_from_now(timeout_us));
    }

    int wait_until(std::unique_lock<bthread::Mutex>& lock,
                   timespec duetime) {
        const int rc = bthread_cond_timedwait(
                &_cond, lock.mutex()->native_handler(), &duetime);
        return rc == ETIMEDOUT ? ETIMEDOUT : 0;
    }

    int wait_until(std::unique_lock<bthread_mutex_t>& lock,
                   timespec duetime) {
        const int rc = bthread_cond_timedwait(
                &_cond, lock.mutex(), &duetime);
        return rc == ETIMEDOUT ? ETIMEDOUT : 0;
    }

    void notify_one() {
        bthread_cond_signal(&_cond);
    }

    void notify_all() {
        bthread_cond_broadcast(&_cond);
    }

private:
    bthread_cond_t                  _cond;
};

}  // namespace bthread

#endif  //BTHREAD_CONDITION_VARIABLE_H
