/*
 * Copyright (C) 2004 Richard Atterer (richard@atterer.net) All rights reserved.
 * Copyright (C) 2008 Santtu Lakkala (inz@inz.fi) All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software for any purpose with or
 * without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of a copyright holder shall not be used in
 * advertising or otherwise to promote the sale, use or other dealings in this Software
 * without prior written authorization of the copyright holder.
 *
 */

#include <glib.h>
#include <stdio.h>
#include <string.h>
#include "glibirc.h"

/*#define D(_args) fprintf _args;*/
#define D(_args)

/* Number of highest allowed fd */
#define IRCCLIENT_FDMAX 127

/* GIOCondition event masks */
#define IRCCLIENT_READ  (G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP)
#define IRCCLIENT_WRITE (G_IO_OUT | G_IO_ERR | G_IO_HUP)

typedef struct IrcClientSource_ {
  GSource source;		/* First: The type we're deriving from */
  irc_session_t *session;

  /* Previously seen FDs, for comparing with libcurl's current fd_sets */
  GPollFD lastPollFd[IRCCLIENT_FDMAX + 1];
  int lastPollFdMax;		/* Index of highest non-empty entry in lastPollFd */

  int callPerform;		/* Non-zero => curl_multi_perform() gets called */

  /* For data returned by irc_add_select_descriptors */
  fd_set fdRead;
  fd_set fdWrite;
} IrcClientSource;

static gboolean _irc_client_source_prepare(GSource *source, gint *timeout);
static gboolean _irc_client_source_check(GSource *source);
static gboolean _irc_client_source_dispatch(GSource *source,
					    GSourceFunc callback,
					    gpointer user_data);
static void _irc_client_source_finalize(GSource *source);

static GSourceFuncs ircFuncs = {
  _irc_client_source_prepare,
  _irc_client_source_check,
  _irc_client_source_dispatch,
  _irc_client_source_finalize,
  0, 0
};

GSource *irc_client_source_new(irc_session_t * session,
			       GMainContext *context)
{
  IrcClientSource *ircSrc;
  int fd;
  /* Create source object for curl file descriptors, and hook it into the
   * given main context. */
  ircSrc =
    (IrcClientSource *)g_source_new(&ircFuncs, sizeof(IrcClientSource));
  g_source_attach((GSource *)ircSrc, context);

  /* Init rest of our data */
  memset(&ircSrc->lastPollFd, 0, sizeof(ircSrc->lastPollFd));
  for (fd = 1; fd <= IRCCLIENT_FDMAX; ++fd)
    ircSrc->lastPollFd[fd].fd = fd;
  ircSrc->lastPollFdMax = 0;
  ircSrc->callPerform = 0;

  ircSrc->session = session;

  return (GSource *)ircSrc;
}

static gboolean registerUnregisterFds(IrcClientSource *ircSrc)
{
  gboolean active_fds = FALSE;
  int fd, fdMax, fdMaxx;

  FD_ZERO(&ircSrc->fdRead);
  FD_ZERO(&ircSrc->fdWrite);
  fdMax = -1;

  /* What fds does libirclient want us to poll? */
  irc_add_select_descriptors(ircSrc->session,
			     &ircSrc->fdRead, &ircSrc->fdWrite, &fdMax);

  g_assert(fdMax >= -1 && fdMax <= IRCCLIENT_FDMAX);

  fdMaxx = fdMax;
  if (fdMaxx < ircSrc->lastPollFdMax)
    fdMaxx = ircSrc->lastPollFdMax;

  /* Has the list of required events for any of the fds changed? */
  for (fd = 0; fd <= fdMaxx; ++fd) {
    gushort events = 0;
    if (FD_ISSET(fd, &ircSrc->fdRead))
      events |= IRCCLIENT_READ;
    if (FD_ISSET(fd, &ircSrc->fdWrite))
      events |= IRCCLIENT_WRITE;

    if (events)
      active_fds = TRUE;

    /* List of events unchanged => no (de)registering */
    if (events == ircSrc->lastPollFd[fd].events)
      continue;

    /* fd is already a lastPollFd, but event type has changed => do nothing.
     * Due to the implementation of g_main_context_query(), the new event
     * flags will be picked up automatically. */
    if (events != 0 && ircSrc->lastPollFd[fd].events != 0) {
      ircSrc->lastPollFd[fd].events = events;
      continue;
    }
    ircSrc->lastPollFd[fd].events = events;

    /* Otherwise, (de)register as appropriate */
    if (events == 0) {
      g_source_remove_poll(&ircSrc->source, &ircSrc->lastPollFd[fd]);
      ircSrc->lastPollFd[fd].revents = 0;
    } else {
      g_source_add_poll(&ircSrc->source, &ircSrc->lastPollFd[fd]);
    }
  }

  ircSrc->lastPollFdMax = fdMax;

  return active_fds;
}

gboolean _irc_client_source_prepare(GSource *source, gint *timeout)
{
  IrcClientSource *ircSrc = (IrcClientSource *)source;

  if (ircSrc->session == NULL)
    return FALSE;

  registerUnregisterFds(ircSrc);

  *timeout = -1;

  return FALSE;
}

gboolean _irc_client_source_check(GSource *source)
{
  IrcClientSource *ircSrc = (IrcClientSource *)source;
  int fd, somethingHappened = 0;

  if (ircSrc->session == NULL)
    return FALSE;

  g_assert(source == &ircSrc->source);
  FD_ZERO(&ircSrc->fdRead);
  FD_ZERO(&ircSrc->fdWrite);
  for (fd = 0; fd <= ircSrc->lastPollFdMax; ++fd) {
    gushort revents = ircSrc->lastPollFd[fd].revents;
    if (revents == 0)
      continue;
    somethingHappened = 1;
    /* D((stderr, "[fd%d] ", fd)); */
    if (revents & IRCCLIENT_READ)
      FD_SET((unsigned)fd, &ircSrc->fdRead);
    if (revents & IRCCLIENT_WRITE)
      FD_SET((unsigned)fd, &ircSrc->fdWrite);
  }
  return !!somethingHappened;
}

/*______________________________________________________________________*/

gboolean _irc_client_source_dispatch(GSource *source, GSourceFunc callback,
		  gpointer user_data)
{
  IrcClientSource *ircSrc = (IrcClientSource *)source;

  g_assert(ircSrc->session != NULL);

  irc_process_select_descriptors(ircSrc->session,
				 &ircSrc->fdRead, &ircSrc->fdWrite);

  if (callback != 0)
    (*callback) (user_data);

  return TRUE;			/* "Do not destroy me" */
}

/*______________________________________________________________________*/

void _irc_client_source_finalize(GSource *source)
{
  registerUnregisterFds((IrcClientSource *)source);
}
