/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018 Danny Robson <danny@nerdcruft.net>
 */

#include "event.hpp"
#include "../cast.hpp"
#include "../posix/except.hpp"

#include <cerrno>
#include <linux/futex.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <limits>

using cruft::thread::event;


///////////////////////////////////////////////////////////////////////////////
static long
sys_futex (void *addr1, int op, int val1, struct timespec *timeout, void *addr2, int val3)
{
    return syscall (SYS_futex, addr1, op | FUTEX_PRIVATE_FLAG, val1, timeout, addr2, val3);
}

///////////////////////////////////////////////////////////////////////////////
event::event ():
    value (0)
{ ; }


///////////////////////////////////////////////////////////////////////////////
void
event::wait (void)
{
    for (auto observed = value.load (); observed == value.load (); ) {
        auto res = sys_futex (&value, FUTEX_WAIT, observed, nullptr, nullptr, 0);

        if (res < 0) {
            switch (errno) {
            case EAGAIN: return;
            case EINTR:  continue;
            }

            posix::error::throw_code ();
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
int
event::notify_all (void)
{
    return notify (std::numeric_limits<int>::max ());
}


//-----------------------------------------------------------------------------
int
event::notify (int count)
{
    ++value;
    auto res = sys_futex (&value, FUTEX_WAKE, count, nullptr, nullptr, 0);
    if (res < 0)
        posix::error::throw_code ();
    return cruft::cast::narrow<int> (res);
}