#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <glib.h>
#include "EVAPI.h"

static GMainContext *
get_gcontext (SV *context)
{
  if (!SvOK (context))
    return g_main_context_default ();

  croak ("only the default context is currently supported.");
}

static void
timeout_cb (EV_P_ ev_timer *w, int revents)
{
  ev_break (EV_A, EVBREAK_ONE);
}

typedef struct
{
  struct ev_io io;
  int *got_events;
  GPollFD *pfd;
} slot;

static void
io_cb (EV_P_ ev_io *w, int revents)
{
  slot *s = (slot *)w;
  int oev = s->pfd->revents;

  s->pfd->revents |= s->pfd->events &
    ((revents & EV_READ ? G_IO_IN : 0)
     | (revents & EV_WRITE ? G_IO_OUT : 0));

  if (!oev && s->pfd->revents)
    ++*(s->got_events);

  ev_break (EV_A, EVBREAK_ONE);
}

static gint
event_poll_func (GPollFD *fds, guint nfds, gint timeout)
{
  dSP;
  // yes, I use C99. If your compiler barfs here, fix it, but don't
  // tell me your compiler vendor was too incompetent to implement
  // the C standard within the last eight years.
  ev_timer to;
  slot slots[nfds];
  int got_events = 0;
  int n;

  for (n = 0; n < nfds; ++n)
    {
      GPollFD *pfd = fds + n;
      slot *s = slots + n;

      pfd->revents = 0;

      s->pfd = pfd;
      s->got_events = &got_events;

      ev_io_init (
        &s->io,
        io_cb,
        pfd->fd,
        (pfd->events & G_IO_IN ? EV_READ : 0)
         | (pfd->events & G_IO_OUT ? EV_WRITE : 0)
      );
      ev_io_start (EV_DEFAULT, &s->io);
    }

  if (timeout >= 0)
    {
      ev_timer_init (&to, timeout_cb, timeout * 1e-3, 0.);
      ev_timer_start (EV_DEFAULT, &to);
    }

  ev_run (EV_DEFAULT, 0);

  if (timeout >= 0)
    ev_timer_stop (EV_DEFAULT, &to);

  for (n = 0; n < nfds; ++n)
    ev_io_stop (EV_DEFAULT, &slots[n].io);

  return got_events;
}

MODULE = Glib::EV                PACKAGE = Glib::EV

PROTOTYPES: ENABLE

BOOT:
{
	I_EV_API ("Glib::EV");
}

long
install (SV *context)
	CODE:
{
	GMainContext *ctx = get_gcontext (context);

        RETVAL = (long)g_main_context_get_poll_func (ctx);

        g_main_context_set_poll_func (ctx, event_poll_func);
}
	OUTPUT:
        RETVAL

void
uninstall (SV *context, long prev_poll_func)
	CODE:
{
	GMainContext *ctx = get_gcontext (context);

        g_main_context_set_poll_func (ctx, (GPollFunc) prev_poll_func);
}