#ifndef INCLUDED_BOBCAT_SHAREDCONDITION_
#define INCLUDED_BOBCAT_SHAREDCONDITION_

#include <ios>
#include <condition_variable>
#include <chrono>

#include <bobcat/sharedmutex>
#include <bobcat/sharedmemory>
#include <bobcat/exception>

namespace FBB
{

class SharedCondition
{
    SharedMemory *d_shmem;              // Note: not allocated
    std::streamsize d_offset;

    struct Condition: private SharedMutex
    {
        Condition();

        pthread_cond_t d_cond;

        using SharedMutex::lock;
        using SharedMutex::unlock;
        using SharedMutex::mutexPtr;
    };

    struct Data
    {
        std::streamsize offset;
        Condition *condition;
    };

    public:
        SharedCondition();
        ~SharedCondition();

        void lock();

        void notify() noexcept;
        void notifyAll() noexcept;

        std::streamsize offset() const;

        void unlock();

        void wait();

        template <typename Predicate>
        void wait(Predicate pred);                          // 2.f

        template <typename Rep, typename Period>            // 1.f
        std::cv_status wait_for(
                std::chrono::duration<Rep, Period> const &relTime
        );

                                                            // 2.f
        template <typename Rep, typename Period, typename Predicate>
        bool wait_for(
                std::chrono::duration<Rep, Period> const &relTime,
                Predicate pred
        );

        template <typename Clock, typename Duration>        // 1.f
        std::cv_status wait_until(
            std::chrono::time_point<Clock, Duration> const &absTime
        );

                                                            // 2.f
        template <typename Clock, typename Duration, typename Predicate>
        bool wait_until(
            std::chrono::time_point<Clock, Duration> const &absTime,
            Predicate pred
        );


        static SharedCondition attach(SharedMemory &shmem,
                        std::ios::off_type offset = 0,
                        std::ios::seekdir way = std::ios::beg);

        static SharedCondition create(SharedMemory &shmem);

        static constexpr size_t size();

    private:
        SharedCondition(SharedMemory &shmem, std::streamsize offset);
        std::cv_status waiter(Condition *cond, int64_t count);

        Data prepare();
};

inline std::streamsize SharedCondition::offset() const
{
    return d_offset;
}
template <typename Predicate>
void SharedCondition::wait(Predicate pred)
{
    Data data = prepare();
    while (not pred())
        pthread_cond_wait(&(data.condition->d_cond),
                          data.condition->mutexPtr());
    d_shmem->seek(data.offset);
}
template <typename Rep, typename Period>
inline std::cv_status SharedCondition::wait_for(
            std::chrono::duration<Rep, Period> const &relTime)
{
    return wait_until(std::chrono::system_clock::now() + relTime);
}
template <typename Rep, typename Period, typename Predicate>
inline bool SharedCondition::wait_for(
            std::chrono::duration<Rep, Period> const &relTime,
            Predicate pred)
{
    return wait_until(std::chrono::system_clock::now() + relTime, pred);
}
template <typename Clock, typename Duration>
std::cv_status SharedCondition::wait_until(
    std::chrono::time_point<Clock, Duration> const &absTime)
{
    Data data = prepare();

    auto ret = waiter(data.condition, absTime.time_since_epoch().count());
    d_shmem->seek(data.offset);
    return ret;
}
template <typename Clock, typename Duration, typename Predicate>
bool SharedCondition::wait_until(
    std::chrono::time_point<Clock, Duration> const &absTime, Predicate pred)
{
    Data data = prepare();

    bool ret = true;

    while (not pred())
    {
        if (waiter(data.condition, absTime.time_since_epoch().count())
            == std::cv_status::timeout
        )
        {
            ret = pred();
            break;
        }
    }

    d_shmem->seek(data.offset);
    return ret;
}
constexpr size_t SharedCondition::size()
{
    return sizeof(Condition);
}

} // FBB
#endif
