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

static gboolean 
gxsnmp_io_dgram_prepare  (GSource         *source, 
		          gint            *timeout);

static gboolean 
gxsnmp_io_dgram_check    (GSource         *source);

static gboolean
gxsnmp_io_dgram_dispatch (GSource         *source, 
		          GSourceFunc      callback,
		          gpointer         user_data);

static void
gxsnmp_io_dgram_destroy  (GSource         *source);

static GIOError 
gxsnmp_io_dgram_recv     (GxsnmpIOMessage *message, 
		       gchar           *buf, 
		       guint            count,
		       guint           *bytes_read,
		       GxsnmpIOAddress  address);

static GIOError 
gxsnmp_io_dgram_send  (GxsnmpIOMessage *message, 
	  	       gchar           *buf, 
		       guint            count,
	 	       guint           *bytes_written,
		       GxsnmpIOAddress  address);

static void 
gxsnmp_io_dgram_close (GxsnmpIOMessage *message);

static GSource *
gxsnmp_io_dgram_add_watch (GxsnmpIOMessage    *message,
		      GIOCondition   condition);

static void 
gxsnmp_io_dgram_free (GxsnmpIOMessage *message);

GSourceFuncs dgram_watch_funcs = {
  gxsnmp_io_dgram_prepare,
  gxsnmp_io_dgram_check,
  gxsnmp_io_dgram_dispatch,
  gxsnmp_io_dgram_destroy
};

GxsnmpIOMFuncs dgram_message_funcs = {
  gxsnmp_io_dgram_recv,
  gxsnmp_io_dgram_send,
  gxsnmp_io_dgram_close,
  gxsnmp_io_dgram_add_watch,
  gxsnmp_io_dgram_free,
};

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

static gboolean 
gxsnmp_io_dgram_check   (GSource *source)
{
  GxsnmpIODgramWatch *watch = (GxsnmpIODgramWatch *)source;

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

static gboolean
gxsnmp_io_dgram_dispatch (GSource *source, GSourceFunc callback,
	       	          gpointer user_data)

{
  GxsnmpIOMFunc func = (GxsnmpIOMFunc)callback;
  GxsnmpIODgramWatch *watch = (GxsnmpIODgramWatch *)source;

  if (!func)
    {
      g_warning ("IO watch dispatched without callback\n"
		 "You must call g_source_connect().");
      return FALSE;
    }
  return (*func)(watch->message,
		 watch->pollfd.revents & watch->condition,
		 user_data);
}

static void 
gxsnmp_io_dgram_destroy (GSource *source)
{
  GxsnmpIODgramWatch *watch = (GxsnmpIODgramWatch *)source;

  gxsnmp_io_message_unref (watch->message);
}

static GIOError 
gxsnmp_io_dgram_recv (GxsnmpIOMessage *message, 
		 gchar      *buf, 
		 guint       count,
		 guint      *bytes_read,
		 GxsnmpIOAddress  address)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;
  gint result;
  GxsnmpIODgramAddress *saddr;

  saddr = (GxsnmpIODgramAddress *)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 
gxsnmp_io_dgram_send(GxsnmpIOMessage *message, 
		gchar      *buf, 
		guint       count,
		guint      *bytes_written,
		GxsnmpIOAddress  address)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;
  gint result;
  GxsnmpIODgramAddress *saddr;

  saddr = (GxsnmpIODgramAddress *)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 
gxsnmp_io_dgram_close (GxsnmpIOMessage *message)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;

  close (dgram_message->fd);
}

static void 
gxsnmp_io_dgram_free (GxsnmpIOMessage *message)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;

  g_free (dgram_message);
}

static GSource *
gxsnmp_io_dgram_add_watch (GxsnmpIOMessage    *message,
		      GIOCondition   condition)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;
  GSource *source;
  GxsnmpIODgramWatch *watch;
 

  source = g_source_new (&dgram_watch_funcs, sizeof (GxsnmpIODgramWatch)); 
  watch = (GxsnmpIODgramWatch *) source;

  watch->message = message;
  gxsnmp_io_message_ref (message);

  watch->condition = condition;

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

  g_source_add_poll (source, &watch->pollfd);

  return source;
}

GxsnmpIOMessage *
gxsnmp_io_message_dgram_new (gint fd)
{
  GxsnmpIODgramMessage *dgram_message = g_new (GxsnmpIODgramMessage, 1);
  GxsnmpIOMessage *message = (GxsnmpIOMessage *)dgram_message;

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

  dgram_message->fd = fd;
  return message;
}

gint
gxsnmp_io_message_dgram_get_fd (GxsnmpIOMessage *message)
{
  GxsnmpIODgramMessage *dgram_message = (GxsnmpIODgramMessage *)message;
  return dgram_message->fd;
}
