/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 */
#include <glib.h>
#include <sys/socket.h>
#include "giomessage.h"
#include "giodgram.h"
#include "errno.h"

static gboolean 
g_io_dgram_prepare (gpointer source_data, 
		    GTimeVal *current_time,
		    gint     *timeout,
		    gpointer user_data);

static gboolean 
g_io_dgram_check   (gpointer source_data,
		    GTimeVal *current_time,
		    gpointer user_data);

static gboolean
g_io_dgram_dispatch (gpointer source_data, 
		     GTimeVal *current_time,
		     gpointer user_data);

static void 
g_io_dgram_destroy (gpointer source_data);

static GIOError 
g_io_dgram_recv (GIOMessage *message, 
		 gchar      *buf, 
		 guint       count,
		 guint      *bytes_read,
		 GIOAddress  address);

static GIOError 
g_io_dgram_send(GIOMessage *message, 
		gchar      *buf, 
		guint       count,
		guint      *bytes_written,
		GIOAddress  address);

static void 
g_io_dgram_close (GIOMessage *message);

static guint 
g_io_dgram_add_watch (GIOMessage    *message,
		      gint           priority,
		      GIOCondition   condition,
		      GIOMFunc       func,
		      gpointer       user_data,
		      GDestroyNotify notify);

static void 
g_io_dgram_free (GIOMessage *message);

GSourceFuncs dgram_watch_funcs = {
  g_io_dgram_prepare,
  g_io_dgram_check,
  g_io_dgram_dispatch,
  g_io_dgram_destroy
};

GIOMFuncs dgram_message_funcs = {
  g_io_dgram_recv,
  g_io_dgram_send,
  g_io_dgram_close,
  g_io_dgram_add_watch,
  g_io_dgram_free,
};

static gboolean 
g_io_dgram_prepare (gpointer source_data, 
		    GTimeVal *current_time,
		    gint     *timeout,
		    gpointer user_data)
{
  *timeout = -1;
  return FALSE;
}

static gboolean 
g_io_dgram_check   (gpointer source_data,
		    GTimeVal *current_time,
		    gpointer user_data)
{
  GIODgramWatch *data = source_data;

  return (data->pollfd.revents & data->condition);
}

static gboolean
g_io_dgram_dispatch (gpointer source_data, 
		     GTimeVal *current_time,
		     gpointer user_data)

{
  GIODgramWatch *data = source_data;

  return (*data->callback)(data->message,
			   data->pollfd.revents & data->condition,
			   user_data);
}

static void 
g_io_dgram_destroy (gpointer source_data)
{
  GIODgramWatch *data = source_data;

  g_main_remove_poll (&data->pollfd);
  g_io_message_unref (data->message);
  g_free (data);
}

static GIOError 
g_io_dgram_recv (GIOMessage *message, 
		 gchar      *buf, 
		 guint       count,
		 guint      *bytes_read,
		 GIOAddress  address)
{
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;
  gint result;
  GIODgramAddress *saddr;

  saddr = (GIODgramAddress *)address;

  result = recvfrom (dgram_message->fd, buf, count, 0, &(saddr->addr),
  		     &(saddr->len));

  if (result < 0)
    {
      *bytes_read = 0;
      switch (errno)
	{
	case EINVAL:
	  return G_IO_ERROR_INVAL;
	case EAGAIN:
	  return G_IO_ERROR_AGAIN;
	default:
	  return G_IO_ERROR_UNKNOWN;
	}
    }
  else
    {
      *bytes_read = result;
      return G_IO_ERROR_NONE;
    }
}
		       
static GIOError 
g_io_dgram_send(GIOMessage *message, 
		gchar      *buf, 
		guint       count,
		guint      *bytes_written,
		GIOAddress  address)
{
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;
  gint result;
  GIODgramAddress *saddr;

  saddr = (GIODgramAddress *)address;

  result = sendto (dgram_message->fd, buf, count, 0, &(saddr->addr), 
  		   saddr->len);

  if (result < 0)
    {
      *bytes_written = 0;
      switch (errno)
	{
	case EINVAL:
	  return G_IO_ERROR_INVAL;
	case EAGAIN:
	  return G_IO_ERROR_AGAIN;
	default:
	  return G_IO_ERROR_UNKNOWN;
	}
    }
  else
    {
      *bytes_written = result;
      return G_IO_ERROR_NONE;
    }
}

static void 
g_io_dgram_close (GIOMessage *message)
{
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;

  close (dgram_message->fd);
}

static void 
g_io_dgram_free (GIOMessage *message)
{
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;

  g_free (dgram_message);
}

static guint 
g_io_dgram_add_watch (GIOMessage    *message,
		      gint           priority,
		      GIOCondition   condition,
		      GIOMFunc       func,
		      gpointer       user_data,
		      GDestroyNotify notify)
{
  GIODgramWatch *watch = g_new (GIODgramWatch, 1);
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;
  
  watch->message = message;
  g_io_message_ref (message);

  watch->callback = func;
  watch->condition = condition;

  watch->pollfd.fd = dgram_message->fd;
  watch->pollfd.events = condition;

  g_main_add_poll (&watch->pollfd, priority);

  return g_source_add (priority, TRUE, &dgram_watch_funcs, watch, user_data, notify);
}

GIOMessage *
g_io_message_dgram_new (gint fd)
{
  GIODgramMessage *dgram_message = g_new (GIODgramMessage, 1);
  GIOMessage *message = (GIOMessage *)dgram_message;

  g_io_message_init (message);
  message->funcs = &dgram_message_funcs;

  dgram_message->fd = fd;
  return message;
}

gint
g_io_message_dgram_get_fd (GIOMessage *message)
{
  GIODgramMessage *dgram_message = (GIODgramMessage *)message;
  return dgram_message->fd;
}
