
#include <stdio.h>
#include <sys/poll.h>

#include "mecha.h"

#include "fd_watcher.h"

#include "fd_watcher_entry.h"

static FDWatcher *g_instance = NULL;
static pthread_once_t g_instanceOnce = PTHREAD_ONCE_INIT;

struct s_FDWatcher
{
    hshTvoid_list   *fdList;

    pthread_mutex_t fdListMutex;
    pthread_cond_t fdListCond;

    pthread_t pollerThread;
};

static void *fd_watcher_dispatcher (void *context);
static void fd_watcher_setupInstance (void);

FDWatcher *
fd_watcher_new (void)
{
    FDWatcher *instance = (FDWatcher *) malloc (sizeof (FDWatcher));

    instance->fdList = hshFvoid_int_init (fd_watcher_entry_destroy);

    pthread_mutex_init (&instance->fdListMutex, NULL);
    pthread_cond_init (&instance->fdListCond, NULL);

    pthread_create (&instance->pollerThread, NULL, fd_watcher_dispatcher, instance);

    return (instance);
}

void
fd_watcher_destroy (void *ptr)
{
    FDWatcher *instance = (FDWatcher *) ptr;

    pthread_cancel (instance->pollerThread);
    pthread_join (instance->pollerThread, NULL);

    hshFvoid_destroy (instance->fdList);

    pthread_mutex_destroy (&instance->fdListMutex);
    pthread_cond_destroy (&instance->fdListCond);

    free (instance);
}

static void
fd_watcher_setupInstance()
{
    g_instance = fd_watcher_new();
}

FDWatcher *
fd_watcher_getInstance()
{
    pthread_once (&g_instanceOnce, fd_watcher_setupInstance);

    return (g_instance);
}

static void *
fd_watcher_dispatcher (void *context)
{
    FDWatcher *instance = (FDWatcher *) context;

    while (1)
    {
        struct pollfd *ufds;
        hshTiterator *iter;
        int i = 0;
        int listSize;

        pthread_mutex_lock (&instance->fdListMutex);

        if (hshFvoid_size (instance->fdList) == 0)
        {
            pthread_cond_wait (&instance->fdListCond, &instance->fdListMutex);
        }

        listSize = hshFvoid_size (instance->fdList);

        ufds = malloc (sizeof (struct pollfd) * 
                listSize);

        iter = hshFiterator_init (instance->fdList);

        while (hshFiterator_next (iter) == 1)
        {
            FDWatcherEntry *entry = hshFiterator_current_value (iter);

            ufds[i].fd = fd_watcher_entry_getFD (entry);
            ufds[i].events = fd_watcher_entry_getEvents (entry);

            i++;
        }

        hshFiterator_free (iter);

        pthread_mutex_unlock (&instance->fdListMutex);

        {
            int rfds = poll (ufds, listSize, 300);

            for (i = 0; i < listSize && rfds > 0; i++)
            {
                if (ufds[i].revents != 0)
                {
                    FDWatcherEntry *entry;

                    pthread_mutex_lock (&instance->fdListMutex);

                    entry = hshFvoid_int_remove (instance->fdList, ufds[i].fd);

                    pthread_mutex_unlock (&instance->fdListMutex);

                    if (entry != NULL)
                    {
                        /* 
                         * this will destroy the entry as soon as it is completed
                         */
                        fd_watcher_entry_invoke (entry, ufds[i].revents);
                    }
                }
            }
        }

        free (ufds);
    }

    return (NULL);
}

void
fd_watcher_removeWatchFD (FDWatcher *instance, int fd)
{
    pthread_mutex_lock (&instance->fdListMutex);

    hshFvoid_int_del (instance->fdList, fd);

    pthread_mutex_unlock (&instance->fdListMutex);
}

void
fd_watcher_watchFD (FDWatcher *instance, int fd, FDWatcherDelegate delegate, void *context, short events)
{
    pthread_mutex_lock (&instance->fdListMutex);

    hshFvoid_int_add (instance->fdList, fd, 
            fd_watcher_entry_new (fd, delegate, context, events));

    /* 
     * if the size is 1, that means it was at 0 before we added ourselves,
     * which means we should notify the watcher.
     */
    if (hshFvoid_size (instance->fdList) == 1)
    {
        pthread_cond_signal (&instance->fdListCond);
    }

    pthread_mutex_unlock (&instance->fdListMutex);
}
