/* Swfdec
 * Copyright (C) 2006 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "swfdec_fb_socket.h"
#include <libsoup/soup.h>
#include <libsoup/soup-address.h>

/*** FB-DOC ***/

/**
 * SECTION:SwfdecFbSocket
 * @title: SwfdecFbSocket
 * @short_description: a socket implementation integrating into the Fb main loop
 * @see_also: #SwfdecSocket, #SwfdecFbPlayer
 *
 * #SwfdecFbSocket is a #SwfdecSocket that is a socket implementation that 
 * uses libsoup and integrates with the Fb main loop. It is used by default
 * by the #SwfecFbPlayer.
 */

/**
 * SwfdecFbSocket:
 *
 * This is the object used to represent a socket. It is completely private.
 */

struct _SwfdecFbSocket
{
  SwfdecSocket		socket;

  SoupSocket *		sock;		/* libsoup socket we're using */
};

struct _SwfdecFbSocketClass {
  SwfdecSocketClass	socket_class;
};

/*** SwfdecFbSocket ***/

G_DEFINE_TYPE (SwfdecFbSocket, swfdec_fb_socket, SWFDEC_TYPE_SOCKET)

static void
swfdec_fb_socket_close (SwfdecStream *stream)
{
  SwfdecFbSocket *fb = SWFDEC_FB_SOCKET (stream);

  soup_socket_disconnect (fb->sock);
}

static void
swfdec_fb_socket_do_disconnect (SoupSocket *sock, SwfdecFbSocket *fb)
{
  swfdec_stream_close (SWFDEC_STREAM (fb));
}

static void
swfdec_fb_socket_do_read (SoupSocket *sock, SwfdecFbSocket *fb)
{
#define SWFDEC_FB_SOCKET_BLOCK_SIZE 1024
  SwfdecBuffer *buffer;
  SoupSocketIOStatus status;
  gsize len;
  GError *error = NULL;

  do {
    buffer = swfdec_buffer_new (SWFDEC_FB_SOCKET_BLOCK_SIZE);
    status = soup_socket_read (sock, buffer->data,
	SWFDEC_FB_SOCKET_BLOCK_SIZE, &len, NULL, &error);
    buffer->length = len;
    switch (status) {
      case SOUP_SOCKET_OK:
	swfdec_stream_push (SWFDEC_STREAM (fb), buffer);
	break;
      case SOUP_SOCKET_WOULD_BLOCK:
      case SOUP_SOCKET_EOF:
	swfdec_buffer_unref (buffer);
	break;
      case SOUP_SOCKET_ERROR:
	swfdec_buffer_unref (buffer);
	swfdec_stream_error (SWFDEC_STREAM (fb), "%s", error->message);
	g_error_free (error);
	break;
      default:
	g_warning ("unhandled status code %u from soup_socket_read()", (guint) status);
	break;
    }
  } while (status == SOUP_SOCKET_OK);
}

static void
swfdec_fb_socket_writable (SoupSocket *sock, SwfdecFbSocket *fb)
{
  swfdec_socket_signal_writable (SWFDEC_SOCKET (fb));
}

static void
swfdec_fb_socket_do_connect (SoupSocket *sock, guint status, gpointer fb)
{
  if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
    swfdec_stream_open (fb);
    // need to read here, since readable signal will only be launched once the
    // socket has been read until it's empty once
    swfdec_fb_socket_do_read (sock, fb);
  } else {
    swfdec_stream_error (fb, "error connecting");
  }
}

static void
swfdec_fb_socket_connect (SwfdecSocket *sock_, SwfdecPlayer *player, 
    const char *hostname, guint port)
{
  SwfdecFbSocket *sock = SWFDEC_FB_SOCKET (sock_);
  SoupAddress *addr;

  addr = soup_address_new (hostname, port);
  sock->sock = soup_socket_new (
      SOUP_SOCKET_FLAG_NONBLOCKING, TRUE,
      SOUP_SOCKET_REMOTE_ADDRESS, addr, NULL);
  g_signal_connect (sock->sock, "disconnected", 
      G_CALLBACK (swfdec_fb_socket_do_disconnect), sock);
  g_signal_connect (sock->sock, "readable", 
      G_CALLBACK (swfdec_fb_socket_do_read), sock);
  g_signal_connect (sock->sock, "writable", 
      G_CALLBACK (swfdec_fb_socket_writable), sock);
  soup_socket_connect_async (sock->sock, NULL, swfdec_fb_socket_do_connect, sock);
}

static gsize
swfdec_fb_socket_send (SwfdecSocket *sock, SwfdecBuffer *buffer)
{
  SwfdecFbSocket *fb = SWFDEC_FB_SOCKET (sock);
  SoupSocketIOStatus status;
  GError *error = NULL;
  gsize len;

  status = soup_socket_write (fb->sock, buffer->data, buffer->length, 
      &len, NULL, &error);
  switch (status) {
    case SOUP_SOCKET_OK:
    case SOUP_SOCKET_WOULD_BLOCK:
    case SOUP_SOCKET_EOF:
      break;
    case SOUP_SOCKET_ERROR:
      swfdec_stream_error (SWFDEC_STREAM (fb), "%s", error->message);
      g_error_free (error);
      return 0;
    default:
      g_warning ("unhandled status code %u from soup_socket_read()", (guint) status);
      break;
  }
  return len;
}

static void
swfdec_fb_socket_dispose (GObject *object)
{
  SwfdecFbSocket *fb = SWFDEC_FB_SOCKET (object);

  if (fb->sock) {
    g_signal_handlers_disconnect_matched (fb->sock, G_SIGNAL_MATCH_DATA,
      0, 0, NULL, NULL, fb);
    g_object_unref (fb->sock);
    fb->sock = NULL;
  }

  G_OBJECT_CLASS (swfdec_fb_socket_parent_class)->dispose (object);
}

static void
swfdec_fb_socket_class_init (SwfdecFbSocketClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  SwfdecStreamClass *stream_class = SWFDEC_STREAM_CLASS (klass);
  SwfdecSocketClass *socket_class = SWFDEC_SOCKET_CLASS (klass);

  object_class->dispose = swfdec_fb_socket_dispose;

  stream_class->close = swfdec_fb_socket_close;

  socket_class->connect = swfdec_fb_socket_connect;
  socket_class->send = swfdec_fb_socket_send;
}

static void
swfdec_fb_socket_init (SwfdecFbSocket *fb)
{
}

