#include <cassert>
#include <pthread.h>

// When a manual-reset event is signaled by SetEvent it sets the event into the
// signaled state and wakes up all threads waiting on this event. In contrast,
// when an auto-reset event is signaled by SetEvent and there are any threads
// waiting, it wakes up one thread and reset the event to the non-signaled
// state. If there are no threads waiting the event remains signaled until a
// single waiting thread waits on it and is released.

// When a manual-reset event is signaled by PulseEvent it wakes up all waiting
// threads and atomically resets the event. In contrast, if an auto-reset event
// is signaled by PulseEvent and there are any threads waiting, it wakes up one
// thread. In either case, the auto-reset event is set to the non-signaled
// state.

class EventBase {
protected:
    EventBase(bool signaled) : signaled(signaled) {
        pthread_cond_init(&cond, NULL);
        pthread_mutex_init(&lock, NULL);
    }

    ~EventBase() {
        pthread_cond_destroy(&cond);
        pthread_mutex_destroy(&lock);
    }

public:
    bool reset() {
        pthread_mutex_lock(&lock);
        bool old = signaled;
        signaled = false;
        pthread_mutex_unlock(&lock);
        return old;
    }

protected:
    pthread_mutex_t lock;
    pthread_cond_t cond;
    bool signaled;
};

class AutoEvent : public EventBase {
public:
    AutoEvent(bool signaled) :
        EventBase(signaled), waiters(0) {}

    void wait() {
        pthread_mutex_lock(&lock);
        if (signaled) {
            signaled = false;
        } else {
            waiters++;
            pthread_cond_wait(&cond, &lock);
            waiters--;
        }
        pthread_mutex_unlock(&lock);
    }

    void signal() {
        pthread_mutex_lock(&lock);
        if (waiters == 0) {
            signaled = true;
        } else {
            pthread_cond_signal(&cond);
        }
        pthread_mutex_unlock(&lock);
    }

private:
    unsigned int waiters;
};

class ManualEvent : public EventBase {
public:
    ManualEvent(bool signaled) :
        EventBase(signaled) {}

    void wait() {
        pthread_mutex_lock(&lock);
        if (!signaled)
            pthread_cond_wait(&cond, &lock);
        pthread_mutex_unlock(&lock);
    }

    void signal() {
        pthread_mutex_lock(&lock);
        signaled = true;
        pthread_mutex_unlock(&lock);
        pthread_cond_broadcast(&cond);
    }
};

// Polymorphic wrapper.
class Event {
public:
    virtual ~Event() {}
    virtual void wait() = 0;
    virtual void signal() = 0;
    virtual bool reset() = 0;
};

template <typename Impl>
class EventType : public Event, public Impl {
public:
    EventType(bool signaled = false) : Impl(signaled) {}

    virtual void wait() { Impl::wait(); }
    virtual void signal() { Impl::signal(); }
    virtual bool reset() { return Impl::reset(); }

private:
    EventType(const EventType<Impl>&);
    void operator=(EventType<Impl>);
};

void test_wait_with_manual_event() {

    ManualEvent ev(false);

    ev.signal();
    ev.wait();

    // Make sure that the event is still signaled.
    assert(ev.reset() == true);
}

void test_wait_with_auto_event() {

    AutoEvent ev(false);

    ev.signal();
    ev.wait();

    // Make sure that the wait call consumed the
    // signal, i.e. the event isn't signaled anymore.
    assert(ev.reset() == false);
}

void test_wait_with_manual_event_wrapper() {

    EventType<ManualEvent> manual_event;

    Event* ev = &manual_event;

    ev->signal();
    ev->wait();

    // Make sure that the event is still signaled.
    assert(ev->reset() == true);
}

void test_wait_with_auto_event_wrapper() {

    EventType<AutoEvent> auto_event;

    Event* ev = &auto_event;

    ev->signal();
    ev->wait();

    // Make sure that the wait call consumed the
    // signal, i.e. the event isn't signaled anymore.
    assert(ev->reset() == false);
}

int main() {

    // These are simple tests to exerce basic functionalities.

    test_wait_with_manual_event();
    test_wait_with_auto_event();

    test_wait_with_manual_event_wrapper();
    test_wait_with_auto_event_wrapper();

    return 0;
}