/***
  This file is part of avahi.

  avahi is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2.1 of the
  License, or (at your option) any later version.

  avahi is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
  Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with avahi; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/poll.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>

#include "llist.h"
#include "avahi-malloc.h"
#include "timeval.h"
#include "simple-watch.h"
#include "thread-watch.h"

struct AvahiThreadedPoll {
    AvahiSimplePoll *simple_poll;
    pthread_t thread_id;
    pthread_mutex_t mutex;
    int thread_running;
    int retval;
};

static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) {
    pthread_mutex_t *mutex = userdata;
    int r;

    /* Before entering poll() we unlock the mutex, so that
     * avahi_simple_poll_quit() can succeed from another thread. */

    pthread_mutex_unlock(mutex);
    r = poll(ufds, nfds, timeout);
    pthread_mutex_lock(mutex);

    return r;
}

static void* thread(void *userdata){
    AvahiThreadedPoll *p = userdata;
    sigset_t mask;

    /* Make sure that signals are delivered to the main thread */
    sigfillset(&mask);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);

    pthread_mutex_lock(&p->mutex);
    p->retval = avahi_simple_poll_loop(p->simple_poll);
    pthread_mutex_unlock(&p->mutex);

    return NULL;
}

AvahiThreadedPoll *avahi_threaded_poll_new(void) {
    AvahiThreadedPoll *p;

    if (!(p = avahi_new(AvahiThreadedPoll, 1)))
        goto fail; /* OOM */

    if (!(p->simple_poll = avahi_simple_poll_new()))
        goto fail;

    pthread_mutex_init(&p->mutex, NULL);

    avahi_simple_poll_set_func(p->simple_poll, poll_func, &p->mutex);

    p->thread_running = 0;

    return p;

fail:
    if (p) {
        if (p->simple_poll) {
            avahi_simple_poll_free(p->simple_poll);
            pthread_mutex_destroy(&p->mutex);
        }

        avahi_free(p);
    }

    return NULL;
}

void avahi_threaded_poll_free(AvahiThreadedPoll *p) {
    assert(p);

    /* Make sure that this function is not called from the helper thread */
    assert(!p->thread_running || !pthread_equal(pthread_self(), p->thread_id));

    if (p->thread_running)
        avahi_threaded_poll_stop(p);

    if (p->simple_poll)
        avahi_simple_poll_free(p->simple_poll);

    pthread_mutex_destroy(&p->mutex);
    avahi_free(p);
}

const AvahiPoll* avahi_threaded_poll_get(AvahiThreadedPoll *p) {
    assert(p);

    return avahi_simple_poll_get(p->simple_poll);
}

int avahi_threaded_poll_start(AvahiThreadedPoll *p) {
    assert(p);

    assert(!p->thread_running);

    if (pthread_create(&p->thread_id, NULL, thread, p) < 0)
        return -1;

    p->thread_running = 1;

    return 0;
}

int avahi_threaded_poll_stop(AvahiThreadedPoll *p) {
    assert(p);

    if (!p->thread_running)
        return -1;

    /* Make sure that this function is not called from the helper thread */
    assert(!pthread_equal(pthread_self(), p->thread_id));

    pthread_mutex_lock(&p->mutex);
    avahi_simple_poll_quit(p->simple_poll);
    pthread_mutex_unlock(&p->mutex);

    pthread_join(p->thread_id, NULL);
    p->thread_running = 0;

    return p->retval;
}

void avahi_threaded_poll_quit(AvahiThreadedPoll *p) {
    assert(p);

    /* Make sure that this function is called from the helper thread */
    assert(pthread_equal(pthread_self(), p->thread_id));

    avahi_simple_poll_quit(p->simple_poll);
}

void avahi_threaded_poll_lock(AvahiThreadedPoll *p) {
    assert(p);

    /* Make sure that this function is not called from the helper thread */
    assert(!p->thread_running || !pthread_equal(pthread_self(), p->thread_id));

    pthread_mutex_lock(&p->mutex);
}

void avahi_threaded_poll_unlock(AvahiThreadedPoll *p) {
    assert(p);

    /* Make sure that this function is not called from the helper thread */
    assert(!p->thread_running || !pthread_equal(pthread_self(), p->thread_id));

    pthread_mutex_unlock(&p->mutex);
}