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

/*
 *  Copyright (C) 2003 Hiroyuki Ikezoe
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  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.
 */

#include <stdio.h>
#include <string.h>

#include "gobject-utils.h"
#include "intl.h"
#include "kz-io.h"
#include "kz-http.h"
#include "kz-file.h"
#include "egg-marshalers.h"

#define BUFFER_SIZE 1024

enum {
	IO_START_SIGNAL,
	IO_IN_SIGNAL,
	IO_OUT_SIGNAL,
	IO_COMPLETED_SIGNAL,
	IO_ERROR_SIGNAL,
	IO_REDIRECT_SIGNAL,
	IO_LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_URI,
	PROP_MODE,
};

static void kz_io_class_init      (KzIOClass *klass);
static void kz_io_init            (KzIO *io);
static void kz_io_set_property    (GObject *object,
				   guint prop_id,
				   const GValue *value,
				   GParamSpec *pspec);
static void kz_io_get_property    (GObject *object,
				   guint prop_id,
				   GValue *value,
				   GParamSpec *pspec);

static void kz_io_error           (KzIO *io);

static GIOStatus kz_io_real_in    (KzIO *io, GIOChannel *iochannel);
static GIOStatus kz_io_real_out   (KzIO *io, GIOChannel *iochannel);
static void      kz_io_real_start (KzIO *io);
static void      kz_io_real_stop  (KzIO *io);

static void kz_io_set_iochannel         (KzIO *io);
static void kz_io_emit_io_in_signal     (KzIO *io, guint len, const gchar *buf);
static void kz_io_emit_io_out_signal    (KzIO *io, guint len, const gchar *buf);
static void kz_io_emit_error_signal     (KzIO *io);
static void kz_io_emit_completed_signal (KzIO *io);

static gboolean cb_io_in          (GIOChannel *iochannel,
				   GIOCondition condition,
				   gpointer data);
static gboolean cb_io_out         (GIOChannel *iochannel,
				   GIOCondition condition,
				   gpointer data);

GType
kz_io_mode_type_get_type (void)
{
	static GType etype = 0;
	if (etype == 0) {
		static const GEnumValue values[] = {
			{ KZ_IO_READ,  "KZ_IO_READ",  "READ" },
			{ KZ_IO_WRITE, "KZ_IO_WRITE", "WRITE" },
			{ 0, NULL, NULL }
		};
		etype = g_enum_register_static ("KzIOMode", values);
	}
	return etype;
}


static GObjectClass *parent_class = NULL;
static gint kz_io_signals[IO_LAST_SIGNAL] = {0};

KZ_OBJECT_GET_TYPE(kz_io, "KzIO", KzIO,
		   kz_io_class_init, kz_io_init,
		   G_TYPE_OBJECT)

static void
kz_io_class_init (KzIOClass *klass)
{
	GObjectClass *object_class;

	parent_class = g_type_class_peek_parent (klass);
	object_class = (GObjectClass *) klass;

	object_class->dispose = kz_io_dispose;
	object_class->set_property = kz_io_set_property;
	object_class->get_property = kz_io_get_property;
	
	klass->set_io_channel        = kz_io_set_iochannel;
	klass->emit_io_in_signal     = kz_io_emit_io_in_signal;
	klass->emit_io_out_signal    = kz_io_emit_io_out_signal;
	klass->emit_error_signal     = kz_io_emit_error_signal;
	klass->emit_completed_signal = kz_io_emit_completed_signal;

	klass->io_in               = NULL;
	klass->io_out              = NULL;
	klass->io_completed        = NULL;
	klass->io_error            = NULL;
	klass->io_redirect         = NULL;
	
	klass->kz_io_in            = kz_io_real_in;
	klass->kz_io_out           = kz_io_real_out;
	klass->kz_io_start         = kz_io_real_start;
	klass->kz_io_stop          = kz_io_real_stop;

	g_object_class_install_property(
		object_class,
		 PROP_URI,
		 g_param_spec_string(
			 "uri",
			 _("URI"),
			 _("The URI of Fetch file"),
			 NULL,
			 G_PARAM_READWRITE));

	g_object_class_install_property(
		object_class,
		PROP_MODE,
		g_param_spec_enum(
			"mode",
			_("I/O Mode"),
			_("Read or write mode"),
			KZ_TYPE_IO_MODE,
			KZ_IO_READ,
			G_PARAM_READWRITE));

	kz_io_signals[IO_IN_SIGNAL]
		= g_signal_new ("io_in",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzIOClass, io_in),
				NULL, NULL,
				_egg_marshal_VOID__UINT_STRING,
				G_TYPE_NONE, 2,
				G_TYPE_UINT, G_TYPE_STRING);
	kz_io_signals[IO_OUT_SIGNAL]
		= g_signal_new ("io_out",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzIOClass, io_out),
				NULL, NULL,
				_egg_marshal_VOID__UINT_STRING,
				G_TYPE_NONE, 2,
				G_TYPE_UINT, G_TYPE_STRING);
	kz_io_signals[IO_COMPLETED_SIGNAL]
		= g_signal_new ("io_completed",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzIOClass, io_completed),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
	kz_io_signals[IO_ERROR_SIGNAL]
		= g_signal_new ("io_error",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzIOClass, io_error),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
	kz_io_signals[IO_REDIRECT_SIGNAL]
		= g_signal_new ("io_redirect",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzIOClass, io_redirect),
				NULL, NULL,
				g_cclosure_marshal_VOID__STRING,
				G_TYPE_NONE, 1,
				G_TYPE_STRING);
}


static void
kz_io_init (KzIO *io)
{
	io->iochannel       = NULL;	
	io->uri             = NULL;
	
	io->file_size       = 0;
	io->bytes_loaded    = 0;

	io->tempio          = NULL;
	io->tempfile        = NULL;

	io->write_str       = NULL;

	io->cancel          = FALSE;

	io->inmemory        = FALSE;
	io->memory_buffer   = NULL;
}


void
kz_io_dispose (GObject *object)
{
	KzIO *io;

	g_return_if_fail(KZ_IS_IO(object));

	io = KZ_IO(object);

	if (io->iochannel)
		g_io_channel_unref(io->iochannel);
	if (io->tempio)
		g_io_channel_unref(io->tempio);
	if (io->tempfile)
	{
		if (g_file_test(io->tempfile, G_FILE_TEST_EXISTS))
			remove(io->tempfile);
		g_free(io->tempfile);
	}
	if (io->uri)
		g_free(io->uri);
	if (io->write_str)
		g_free(io->write_str);

	io->uri       = NULL;
	io->tempfile  = NULL;
	io->write_str = NULL;

	if (io->memory_buffer)
		g_string_free(io->memory_buffer, TRUE);
	io->memory_buffer = NULL;

	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose(object);
}


static void
kz_io_set_property (GObject *object,
		    guint prop_id,
		    const GValue *value,
		    GParamSpec *pspec)
{
	KzIO *io = KZ_IO(object);

	switch (prop_id)
	{
	case PROP_URI:
		g_free(io->uri);
		io->uri = g_value_dup_string(value);
		break;
	case PROP_MODE:
		io->mode = g_value_get_enum(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
kz_io_get_property (GObject *object,
		    guint prop_id,
		    GValue *value,
		    GParamSpec *pspec)
{
	KzIO *io = KZ_IO(object);

	switch (prop_id)
	{
	case PROP_URI:
		g_value_set_string(value, io->uri);
		break;
	case PROP_MODE:
		g_value_set_enum(value, io->mode);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzIO *
kz_io_new (const gchar *uri, KzIOMode mode)
{
	KzIO *io = NULL;

	/* FIXME! I think this code is something wrong. */
	if (g_str_has_prefix(uri, "http://"))
	{
		/*
		io = KZ_IO(g_object_new(KZ_TYPE_HTTP,
					"uri",      uri,
					NULL));
		 */
		io = KZ_IO(kz_http_new(uri));
	}
	else if (g_str_has_prefix(uri, "ftp://"))
	{
		/* io = KZ_IO(kz_ftp_new(uri)); */
	}
	else if (g_str_has_prefix(uri, "file://"))
	{
		io = KZ_IO(kz_file_new(uri + 7, mode));
	}	    
	else
	{
		io = KZ_IO(kz_file_new(uri, mode));
	}	    

	return io;
}


KzIO *
kz_io_new_in_memory (const gchar *uri, KzIOMode mode)
{
	KzIO *io = NULL;

	io = kz_io_new(uri, mode);
	
	io->inmemory = TRUE;

	return io;
}


static void
kz_io_error (KzIO *io)
{
	g_return_if_fail(KZ_IS_IO(io));

	g_signal_emit(G_OBJECT(io),
		      kz_io_signals[IO_ERROR_SIGNAL],
		      0);
}


static GIOStatus
kz_io_in(KzIO *io, GIOChannel *iochannel)
{
	GIOStatus iostatus;
	gsize bytes_read, bytes_written;
	gchar buffer[BUFFER_SIZE];

	/* Read the data into our buffer */
	iostatus = g_io_channel_read_chars(iochannel, buffer, 
					   sizeof(buffer),
					   &bytes_read,
					   NULL);

	if (iostatus == G_IO_STATUS_NORMAL)
	{	
		if (io->inmemory)
			g_string_append_len(io->memory_buffer, buffer, bytes_read);
		else
		{
			iostatus = g_io_channel_write_chars(io->tempio,
							    buffer,
							    bytes_read,
							    &bytes_written,
							    NULL);
		}
		io->bytes_loaded += bytes_read;
		/* Check for EOF */
		if (bytes_read == 0)
			iostatus = G_IO_STATUS_EOF;
	}
	return iostatus;
}


static GIOStatus
kz_io_out(KzIO *io, GIOChannel *iochannel)
{
	GIOStatus iostatus;
	gssize count;
	gsize bytes_written;

	count = strlen(io->write_str);
	iostatus = g_io_channel_write_chars(iochannel,
					    io->write_str,
					    count,
					    &bytes_written,
					    NULL);
	g_io_channel_flush(iochannel, NULL);
	return iostatus;
}


#include <errno.h>
static gboolean
cb_io_in(GIOChannel *iochannel, GIOCondition condition,
	 gpointer data)
{
	KzIO *io;
	GIOStatus iostatus;
	
	g_return_val_if_fail(KZ_IS_IO(data), FALSE);
	io = KZ_IO(data);
	
	/* If kz_io_stop have been called, loading stops.  */
	if (io->cancel)
	{
		kz_io_error(io);
		return FALSE;
	}

	if (condition & G_IO_ERR)
	{
		g_warning("IO Condition: %d", condition);
		kz_io_error(io);
		return FALSE;
	}

	iostatus = KZ_IO_GET_CLASS(io)->kz_io_in(io, iochannel);

	switch (iostatus)
	{
	 case G_IO_STATUS_EOF:
		{
			if(!io->inmemory)
				g_io_channel_flush(io->tempio, NULL);
		
			g_signal_emit(G_OBJECT(io),
				      kz_io_signals[IO_COMPLETED_SIGNAL],
				      0);
			return FALSE;
		}
	 case G_IO_STATUS_NORMAL:
		{
			return TRUE;
		}
	 case G_IO_STATUS_AGAIN :
		{
			g_signal_emit(G_OBJECT(io),
				      kz_io_signals[IO_REDIRECT_SIGNAL],
				      0, io->uri);
			return FALSE;
		}
	 default:
		{
			kz_io_error(io);
			return FALSE;
		}
	}
}


static gboolean
cb_io_out(GIOChannel *iochannel, GIOCondition condition,
	  gpointer data)
{
	KzIO *io;
	GIOStatus iostatus;
	
	g_return_val_if_fail(KZ_IS_IO(data), FALSE);
	io = KZ_IO(data);
	
	/* If kz_io_stop have been called, writing stops.  */
	if (io->cancel)
	{
		kz_io_error(io);
		return FALSE;
	}

	if ((condition & G_IO_ERR) || (condition & G_IO_HUP))
	{
		g_warning("IO Condition: %d", condition);
		kz_io_error(io);
		return FALSE;
	}
	iostatus = KZ_IO_GET_CLASS(io)->kz_io_out(io, iochannel);

	switch (iostatus)
	{
	 case G_IO_STATUS_EOF:
		{
			g_io_channel_flush(iochannel, NULL);
		
			g_signal_emit(G_OBJECT(io),
				      kz_io_signals[IO_COMPLETED_SIGNAL],
				      0);
			return FALSE;
		}
	 case G_IO_STATUS_NORMAL:
		{
			return TRUE;
		}
	 default:
		{
			kz_io_error(io);
			return FALSE;
		}
	}
}


static void 
kz_io_set_iochannel(KzIO *io)
{
	GIOStatus iostatus = G_IO_STATUS_NORMAL;

	if(io->mode == KZ_IO_READ)
	iostatus = g_io_channel_set_flags(io->iochannel,
					  G_IO_FLAG_NONBLOCK,
					  NULL);
	if (iostatus != G_IO_STATUS_NORMAL)
		kz_io_error(io);

	g_io_channel_set_buffered(io->iochannel, TRUE);

	if(io->mode == KZ_IO_READ)
	{
		g_io_add_watch(io->iochannel,
			       G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP,
			       cb_io_in, io);

		if(io->inmemory)
			io->memory_buffer = g_string_new(0);
		else
		{
			io->tempfile = g_strconcat(g_get_home_dir(),
						   "/.kazehakase/",
						   "tmpkzXXXXXX", NULL);
			io->fd     = g_mkstemp(io->tempfile);
			if (io->fd != -1)
			{
				io->tempio = g_io_channel_unix_new(io->fd);
				iostatus = g_io_channel_set_encoding(io->tempio,
								     NULL,
								     NULL);
				g_io_channel_set_buffered(io->tempio, TRUE);
			}
			else 
				kz_io_error(io);
		}
	}
	else if (io->mode == KZ_IO_WRITE)
/*		g_io_add_watch(io->iochannel,
			       G_IO_OUT | G_IO_PRI | G_IO_ERR | G_IO_HUP,
			       cb_io_out, io);
 */
		cb_io_out(io->iochannel, G_IO_OUT, io);
}


static GIOStatus
kz_io_real_in (KzIO *io, GIOChannel *iochannel)
{
	return kz_io_in(io, iochannel);
}


static GIOStatus
kz_io_real_out (KzIO *io, GIOChannel *iochannel)
{
	return kz_io_out(io, iochannel);
}


static void
kz_io_real_start (KzIO *io)
{
	GIOStatus iostatus;

	g_return_if_fail(KZ_IS_IO(io));

	if (io->mode == KZ_IO_READ)
	{
		if (g_file_test(io->uri, G_FILE_TEST_EXISTS) == FALSE)
		{
			kz_io_error(io);
			return;
		}
		io->iochannel = g_io_channel_new_file(io->uri, "r", NULL);
	}
	else if (io->mode == KZ_IO_WRITE)
		io->iochannel = g_io_channel_new_file(io->uri, "w", NULL);

	iostatus = g_io_channel_set_encoding(io->iochannel,
					     NULL,
					     NULL);
	kz_io_set_iochannel(io);
}


static void
kz_io_real_stop (KzIO *io)
{
	g_return_if_fail(KZ_IS_IO(io));
	io->cancel = TRUE;
	return;
}


static void
kz_io_emit_io_in_signal (KzIO *io, guint len, const gchar *buf)
{
	g_return_if_fail(KZ_IS_IO(io));
	
	g_signal_emit(G_OBJECT(io),
		      kz_io_signals[IO_IN_SIGNAL],
		      0,
		      len, buf);
}


static void
kz_io_emit_io_out_signal (KzIO *io, guint len, const gchar *buf)
{
	g_return_if_fail(KZ_IS_IO(io));
	
	g_signal_emit(G_OBJECT(io),
		      kz_io_signals[IO_OUT_SIGNAL],
		      0,
		      len, buf);
}


static void
kz_io_emit_error_signal (KzIO *io)
{
	g_return_if_fail(KZ_IS_IO(io));

	g_signal_emit(G_OBJECT(io),
		      kz_io_signals[IO_ERROR_SIGNAL],
		      0);
}


static void 
kz_io_emit_completed_signal (KzIO *io)
{
	g_signal_emit(G_OBJECT(io),
		      kz_io_signals[IO_COMPLETED_SIGNAL],
		      0);
}


void
kz_io_set_string (KzIO *io, gchar *str)
{
	g_return_if_fail(KZ_IS_IO(io));
	g_return_if_fail(str);
	g_return_if_fail(io->mode == KZ_IO_WRITE);

	io->write_str = str;
}


static gchar *
kz_io_get_string_from_tempfile (KzIO *io)
{
	GIOChannel *iochannel;
	GIOStatus iostatus;
	gchar *buffer;
	gsize bytes_read;

	g_return_val_if_fail(KZ_IS_IO(io), NULL);
	g_return_val_if_fail(io->tempfile != NULL, NULL);

	iochannel = g_io_channel_new_file(io->tempfile, "r", NULL);
	g_return_val_if_fail(iochannel != NULL, NULL);

	iostatus = g_io_channel_set_encoding(iochannel,
					     NULL,
					     NULL);
	g_return_val_if_fail(iostatus == G_IO_STATUS_NORMAL, NULL);

	buffer = g_new0(gchar, io->bytes_loaded);
	iostatus = g_io_channel_read_to_end(iochannel, &buffer,
					    &bytes_read, NULL);
	g_io_channel_unref(iochannel);
	return buffer;
}


gchar *
kz_io_get_string (KzIO *io)
{
	if(io->inmemory)
	{
		if (io->memory_buffer)
			return g_strdup(io->memory_buffer->str);
	}
	else
		return kz_io_get_string_from_tempfile(io);

	return NULL;
}


guint
kz_io_get_lastmodified (KzIO *io)
{
	g_return_val_if_fail(KZ_IS_IO(io), 0);

	return io->last_modified;
}


gsize
kz_io_get_file_size (KzIO *io)
{
	g_return_val_if_fail(KZ_IS_IO(io), 0);

	return io->file_size;
}


gsize
kz_io_get_loaded_size (KzIO *io)
{
	g_return_val_if_fail(KZ_IS_IO(io), 0);

	return io->bytes_loaded;
}


void
kz_io_start (KzIO *io)
{
	KZ_IO_GET_CLASS(io)->kz_io_start(io);
}


void
kz_io_stop (KzIO *io)
{
	KZ_IO_GET_CLASS(io)->kz_io_stop(io);
}

