/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2004 Hiroyuki Ikezoe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 *  These codes are picked from GLib-2.4.0 (glib/gmain.c).
 *  Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *  Copyright 1998 Owen Taylor
 */

#include "glib-utils.h"

#if (GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 4)

#include <fcntl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
typedef struct _GChildWatchSource GChildWatchSource;

struct _GChildWatchSource
{
  GSource     source;
  GPid        pid;
  gint        child_status;
#ifdef G_OS_WIN32
  GPollFD     poll;
#else /* G_OS_WIN32 */
  gint        count;
  gboolean    child_exited;
#endif /* G_OS_WIN32 */
};

static gboolean g_child_watch_prepare  (GSource     *source,
				        gint        *timeout);
static gboolean g_child_watch_check    (GSource     *source);
static gboolean g_child_watch_dispatch (GSource     *source,
					GSourceFunc  callback,
					gpointer     user_data);
#ifndef G_OS_WIN32
/* Child status monitoring code */
enum {
  CHILD_WATCH_UNINITIALIZED,
  CHILD_WATCH_INITIALIZED_SINGLE,
  CHILD_WATCH_INITIALIZED_THREADED
};
static gint child_watch_init_state = CHILD_WATCH_UNINITIALIZED;
static gint child_watch_count = 0;
static gint child_watch_wake_up_pipe[2] = {0, 0};
#endif /* !G_OS_WIN32 */

GSourceFuncs g_child_watch_funcs =
{
  g_child_watch_prepare,
  g_child_watch_check,
  g_child_watch_dispatch,
  NULL
};

G_LOCK_DEFINE_STATIC (main_context_list);
static GSList *main_context_list = NULL;

/* The following implementation of poll() comes from the GNU C Library.
 * Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc.
 */

#include <string.h> /* for bzero on BSD systems */

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */

#ifdef G_OS_BEOS
#undef NO_FD_SET
#endif /* G_OS_BEOS */

#ifndef NO_FD_SET
#  define SELECT_MASK fd_set
#else /* !NO_FD_SET */
#  ifndef _AIX
typedef long fd_mask;
#  endif /* _AIX */
#  ifdef _IBMR2
#    define SELECT_MASK void
#  else /* !_IBMR2 */
#    define SELECT_MASK int
#  endif /* !_IBMR2 */
#endif /* !NO_FD_SET */

static gint 
g_poll (GPollFD *fds,
	guint    nfds,
	gint     timeout)
{
  struct timeval tv;
  SELECT_MASK rset, wset, xset;
  GPollFD *f;
  int ready;
  int maxfd = 0;

  FD_ZERO (&rset);
  FD_ZERO (&wset);
  FD_ZERO (&xset);

  for (f = fds; f < &fds[nfds]; ++f)
    if (f->fd >= 0)
      {
	if (f->events & G_IO_IN)
	  FD_SET (f->fd, &rset);
	if (f->events & G_IO_OUT)
	  FD_SET (f->fd, &wset);
	if (f->events & G_IO_PRI)
	  FD_SET (f->fd, &xset);
	if (f->fd > maxfd && (f->events & (G_IO_IN|G_IO_OUT|G_IO_PRI)))
	  maxfd = f->fd;
      }

  tv.tv_sec = timeout / 1000;
  tv.tv_usec = (timeout % 1000) * 1000;

  ready = select (maxfd + 1, &rset, &wset, &xset,
		  timeout == -1 ? NULL : &tv);
  if (ready > 0)
    for (f = fds; f < &fds[nfds]; ++f)
      {
	f->revents = 0;
	if (f->fd >= 0)
	  {
	    if (FD_ISSET (f->fd, &rset))
	      f->revents |= G_IO_IN;
	    if (FD_ISSET (f->fd, &wset))
	      f->revents |= G_IO_OUT;
	    if (FD_ISSET (f->fd, &xset))
	      f->revents |= G_IO_PRI;
	  }
      }

  return ready;
}

/* Child watch functions */

#ifdef G_OS_WIN32

static gboolean
g_child_watch_prepare (GSource *source,
		       gint    *timeout)
{
  *timeout = -1;
  return FALSE;
}


static gboolean 
g_child_watch_check (GSource  *source)
{
  GChildWatchSource *child_watch_source;
  gboolean child_exited;

  child_watch_source = (GChildWatchSource *) source;

  child_exited = child_watch_source->poll.revents & G_IO_IN;

  if (child_exited)
    {
      DWORD child_status;

      /*
       * Note: We do _not_ check for the special value of STILL_ACTIVE
       * since we know that the process has exited and doing so runs into
       * problems if the child process "happens to return STILL_ACTIVE(259)"
       * as Microsoft's Platform SDK puts it.
       */
      if (!GetExitCodeProcess (child_watch_source->pid, &child_status))
        {
	  gchar *emsg = g_win32_error_message (GetLastError ());
	  g_warning (G_STRLOC ": GetExitCodeProcess() failed: %s", emsg);
	  g_free (emsg);

	  child_watch_source->child_status = -1;
	}
      else
	child_watch_source->child_status = child_status;
    }

  return child_exited;
}

#else /* G_OS_WIN32 */

static gboolean
check_for_child_exited (GSource *source)
{
  GChildWatchSource *child_watch_source;
  gint count;

  /* protect against another SIGCHLD in the middle of this call */
  count = child_watch_count;

  child_watch_source = (GChildWatchSource *) source;

  if (child_watch_source->child_exited)
    return TRUE;

  if (child_watch_source->count < count)
    {
      gint child_status;

      if (waitpid (child_watch_source->pid, &child_status, WNOHANG) > 0)
	{
	  child_watch_source->child_status = child_status;
	  child_watch_source->child_exited = TRUE;
	}
      child_watch_source->count = count;
    }

  return child_watch_source->child_exited;
}

static gboolean
g_child_watch_prepare (GSource *source,
		       gint    *timeout)
{
  GChildWatchSource *child_watch_source;
  *timeout = -1;

  child_watch_source = (GChildWatchSource *) source;

  return check_for_child_exited (source);
}


static gboolean 
g_child_watch_check (GSource  *source)
{
  GChildWatchSource *child_watch_source;

  child_watch_source = (GChildWatchSource *) source;

  return check_for_child_exited (source);
}

#endif /* G_OS_WIN32 */

static gboolean
g_child_watch_dispatch (GSource    *source, 
			GSourceFunc callback,
			gpointer    user_data)
{
  GChildWatchSource *child_watch_source;
  GChildWatchFunc child_watch_callback = (GChildWatchFunc) callback;

  child_watch_source = (GChildWatchSource *) source;

  if (!callback)
    {
      g_warning ("Child watch source dispatched without callback\n"
		 "You must call g_source_set_callback().");
      return FALSE;
    }

  (child_watch_callback) (child_watch_source->pid, child_watch_source->child_status, user_data);

  /* We never keep a child watch source around as the child is gone */
  return FALSE;
}

#ifndef G_OS_WIN32

static void
g_child_watch_signal_handler (int signum)
{
  child_watch_count ++;

  if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED)
    {
      write (child_watch_wake_up_pipe[1], "B", 1);
    }
  else
    {
      /* We count on the signal interrupting the poll in the same thread.
       */
    }
}
 
static void
g_child_watch_source_init_single (void)
{
  g_assert (! g_thread_supported());
  g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED);

  child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE;

  signal (SIGCHLD, g_child_watch_signal_handler);
}

static gpointer
child_watch_helper_thread (gpointer data)
{
  GPollFD fds;
  GPollFunc poll_func;

#ifdef HAVE_POLL
      poll_func = (GPollFunc)poll;
#else
      poll_func = g_poll;
#endif

  fds.fd = child_watch_wake_up_pipe[0];
  fds.events = G_IO_IN;

  while (1)
    {
      gchar b[20];
      GSList *list;

      read (child_watch_wake_up_pipe[0], b, 20);

      /* We were woken up.  Wake up all other contexts in all other threads */
      G_LOCK (main_context_list);
      for (list = main_context_list; list; list = list->next)
	{
	  GMainContext *context;

	  context = list->data;
#warning FIXME!!
#if 0	  
	  if (g_atomic_int_get (&context->ref_count) > 0)
	    /* Due to racing conditions we can find ref_count == 0, in
	     * that case, however, the context is still not destroyed
	     * and no poll can be active, otherwise the ref_count
	     * wouldn't be 0 */
	    g_main_context_wakeup (context);
#endif
	}
      G_UNLOCK (main_context_list);
    }
  return NULL;
}

static void
g_child_watch_source_init_multi_threaded (void)
{
  GError *error = NULL;

  g_assert (g_thread_supported());

  if (pipe (child_watch_wake_up_pipe) < 0)
    g_error ("Cannot create wake up pipe: %s\n", g_strerror (errno));
  fcntl (child_watch_wake_up_pipe[1], F_SETFL, O_NONBLOCK | fcntl (child_watch_wake_up_pipe[1], F_GETFL));

  /* We create a helper thread that polls on the wakeup pipe indefinitely */
  /* FIXME: Think this through for races */
  if (g_thread_create (child_watch_helper_thread, NULL, FALSE, &error) == NULL)
    g_error ("Cannot create a thread to monitor child exit status: %s\n", error->message);
  child_watch_init_state = CHILD_WATCH_INITIALIZED_THREADED;
  signal (SIGCHLD, g_child_watch_signal_handler);
}

static void
g_child_watch_source_init_promote_single_to_threaded (void)
{
  g_child_watch_source_init_multi_threaded ();
}

static void
g_child_watch_source_init (void)
{
  if (g_thread_supported())
    {
      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
	g_child_watch_source_init_multi_threaded ();
      else if (child_watch_init_state == CHILD_WATCH_INITIALIZED_SINGLE)
	g_child_watch_source_init_promote_single_to_threaded ();
    }
  else
    {
      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
	g_child_watch_source_init_single ();
    }
}

#endif /* !G_OS_WIN32 */

/**
 * g_child_watch_source_new:
 * @pid: process id of a child process to watch
 * 
 * Creates a new child_watch source.
 *
 * The source will not initially be associated with any #GMainContext
 * and must be added to one with g_source_attach() before it will be
 * executed.
 * 
 * Note that on platforms where #GPid must be explicitely closed
 * (see g_spawn_close_pid()) @pid must not be closed while the
 * source is still active. Typically, you will want to call
 * g_spawn_close_pid() in the callback function for the source.
 * 
 * Return value: the newly-created child watch source
 *
 * Since: 2.4
 **/
GSource *
g_child_watch_source_new (GPid pid)
{
  GSource *source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource));
  GChildWatchSource *child_watch_source = (GChildWatchSource *)source;

#ifdef G_OS_WIN32
  child_watch_source->poll.fd = (int)pid;
  child_watch_source->poll.events = G_IO_IN;

  g_source_add_poll (source, &child_watch_source->poll);
#else /* G_OS_WIN32 */
  g_child_watch_source_init ();
#endif /* G_OS_WIN32 */

  child_watch_source->pid = pid;

  return source;
}

/**
 * g_child_watch_add_full:
 * @priority: the priority of the idle source. Typically this will be in the
 *            range between #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE.
 * @pid:      process id of a child process to watch
 * @function: function to call
 * @data:     data to pass to @function
 * @notify:   function to call when the idle is removed, or %NULL
 * 
 * Sets a function to be called when the child indicated by @pid exits, at a
 * default priority, #G_PRIORITY_DEFAULT.
 * 
 * Note that on platforms where #GPid must be explicitely closed
 * (see g_spawn_close_pid()) @pid must not be closed while the
 * source is still active. Typically, you will want to call
 * g_spawn_close_pid() in the callback function for the source.
 * 
 * Return value: the id of event source.
 *
 * Since: 2.4
 **/
guint
g_child_watch_add_full (gint            priority,
			GPid            pid,
			GChildWatchFunc function,
			gpointer        data,
			GDestroyNotify  notify)
{
  GSource *source;
  guint id;
  
  g_return_val_if_fail (function != NULL, 0);

  source = g_child_watch_source_new (pid);

  if (priority != G_PRIORITY_DEFAULT)
    g_source_set_priority (source, priority);

  g_source_set_callback (source, (GSourceFunc) function, data, notify);
  id = g_source_attach (source, NULL);
  g_source_unref (source);

  return id;
}

/**
 * g_child_watch_add:
 * @pid:      process id of a child process to watch
 * @function: function to call
 * @data:     data to pass to @function
 * 
 * Sets a function to be called when the child indicated by @pid exits, at a
 * default priority, #G_PRIORITY_DEFAULT.
 * 
 * Note that on platforms where #GPid must be explicitely closed
 * (see g_spawn_close_pid()) @pid must not be closed while the
 * source is still active. Typically, you will want to call
 * g_spawn_close_pid() in the callback function for the source.
 *
 * Return value: the id of event source.
 *
 * Since: 2.4
 **/
guint 
g_child_watch_add (GPid            pid,
		   GChildWatchFunc function,
		   gpointer        data)
{
  return g_child_watch_add_full (G_PRIORITY_DEFAULT, pid, function, data, NULL);
}


#endif /* (GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 4) */
