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

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

#include <stdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>

#include "kz-downloader.h"

/* compatibility for older than glib-2.19.10 */
#ifndef G_FILE_CREATE_REPLACE_DESTINATION
#define G_FILE_CREATE_REPLACE_DESTINATION 0
#endif


enum {
	START_SIGNAL,
	COMPLETED_SIGNAL,
	ERROR_SIGNAL,
	IO_IN_SIGNAL,
	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_URI,
	PROP_FILENAME
};

#define BUFFER_SIZE 4096

typedef struct _KzDownloaderPrivate	KzDownloaderPrivate;
struct _KzDownloaderPrivate
{
	GFile *g_file;
	GCancellable *cancellable;
	GFileInputStream *input_stream;
	GString *contents;
	goffset contents_size;
	gchar buffer[BUFFER_SIZE];
	gchar  *uri;
	gchar  *file_name;

	GFile *local_file;
	GFileOutputStream *output_stream;
	gboolean to_file;
};
#define KZ_DOWNLOADER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_DOWNLOADER, KzDownloaderPrivate))

static void     dispose      (GObject *object);
static void     set_property (GObject *object,
                              guint prop_id,
                              const GValue *value,
                              GParamSpec *pspec);
static void     get_property (GObject *object,
                              guint prop_id,
                              GValue *value,
                              GParamSpec *pspec);

static void kz_downloader_real_cancel      (KzDownloader *downloader);
static gint kz_downloader_real_get_percent (KzDownloader *downloader);

static gint kz_downloader_signals[LAST_SIGNAL] = {0};

G_DEFINE_TYPE(KzDownloader, kz_downloader, G_TYPE_OBJECT)

static void
kz_downloader_class_init (KzDownloaderClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS(klass);

	object_class->dispose      = dispose;
	object_class->set_property = set_property;
	object_class->get_property = get_property;

	klass->start     = NULL;
	klass->completed = NULL;
	klass->error     = NULL;
	klass->io_in     = NULL;

	klass->kz_downloader_cancel      = kz_downloader_real_cancel;
	klass->kz_downloader_get_percent = kz_downloader_real_get_percent;

	g_object_class_install_property(
		object_class,
		 PROP_URI,
		 g_param_spec_string(
			 "uri",
			 _("URI"),
			 _("The URI of Target"),
			 NULL,
			 G_PARAM_READWRITE |
			 G_PARAM_CONSTRUCT));
	g_object_class_install_property(
		object_class,
		 PROP_FILENAME,
		 g_param_spec_string(
			 "file-name",
			 _("Filename"),
			 _("The Local Filename"),
			 NULL,
			 G_PARAM_READWRITE));

	kz_downloader_signals[START_SIGNAL]
		= g_signal_new ("start",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzDownloaderClass, start),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
	kz_downloader_signals[COMPLETED_SIGNAL]
		= g_signal_new ("completed",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzDownloaderClass, completed),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
	kz_downloader_signals[ERROR_SIGNAL]
		= g_signal_new ("error",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzDownloaderClass, error),
				NULL, NULL,
				g_cclosure_marshal_VOID__STRING,
				G_TYPE_NONE, 1, G_TYPE_STRING);
	kz_downloader_signals[IO_IN_SIGNAL]
		= g_signal_new ("io_in",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_FIRST,
				G_STRUCT_OFFSET (KzDownloaderClass, io_in),
				NULL, NULL,
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE, 0);
	g_type_class_add_private(object_class, sizeof(KzDownloaderPrivate));
}

static void
kz_downloader_init (KzDownloader *downloader)
{
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(downloader);

	priv->uri = NULL;
	priv->file_name = NULL;
	priv->g_file = NULL;
	priv->cancellable = g_cancellable_new();
	priv->input_stream = NULL;

	priv->contents = g_string_new(NULL);
	priv->contents_size = 0;

	priv->to_file = FALSE;
}

static void
dispose (GObject *object)
{
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(object);

	g_free(priv->uri);
	g_free(priv->file_name);

	if (priv->g_file)
	{
		g_object_unref(priv->g_file);
		priv->g_file = NULL;
	}
	if (priv->cancellable)
	{
		g_object_unref(priv->cancellable);
		priv->cancellable = NULL;
	}
	if (priv->input_stream)
	{
		g_object_unref(priv->input_stream);
		priv->input_stream = NULL;
        }
	if (priv->contents)
	{
		g_string_free(priv->contents, TRUE);
		priv->contents = NULL;
	}
	if (priv->local_file)
	{
		g_object_unref(priv->local_file);
		priv->local_file = NULL;
        }
	if (priv->output_stream)
	{
		g_object_unref(priv->output_stream);
		priv->output_stream = NULL;
        }

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


static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(object);

	switch (prop_id)
	{
	case PROP_URI:
		priv->uri = g_value_dup_string(value);
		break;
	case PROP_FILENAME:
                g_free(priv->file_name);
		priv->file_name = g_value_dup_string(value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(object);

	switch (prop_id)
	{
	case PROP_URI:
		g_value_set_string(value, priv->uri);
		break;
	case PROP_FILENAME:
		g_value_set_string(value, priv->file_name);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzDownloader *
kz_downloader_new (const gchar *uri)
{
	return g_object_new(KZ_TYPE_DOWNLOADER,
		 	    "uri", uri,
			    NULL);
}


KzDownloader *
kz_downloader_new_with_filename (const gchar *uri, const gchar *file_name)
{
	return g_object_new(KZ_TYPE_DOWNLOADER,
			    "uri",      uri,
			    "file-name", file_name,
			    NULL);
}

static void
cb_input_stream_read (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	GError *error = NULL;
	gssize bytes_read;
	KzDownloaderPrivate *priv;

	bytes_read = g_input_stream_read_finish(G_INPUT_STREAM(source_object),
						result, &error);
	if (bytes_read == -1)
	{
		g_signal_emit(user_data,
			      kz_downloader_signals[ERROR_SIGNAL],
			      0, error->message);
		g_error_free(error);
		return;
	}
	else if (bytes_read == 0)
	{
		g_signal_emit(user_data,
			      kz_downloader_signals[COMPLETED_SIGNAL],
			      0);
		return;
	}

	priv = KZ_DOWNLOADER_GET_PRIVATE(user_data);

	if (priv->to_file && priv->local_file && priv->output_stream)
	{
		gsize bytes_written;
		g_output_stream_write_all(G_OUTPUT_STREAM(priv->output_stream),
					  priv->buffer, bytes_read,
					  &bytes_written,
					  NULL, NULL);
	}
	else
	{
		g_string_append_len(priv->contents, priv->buffer, bytes_read);
	}

	g_signal_emit(user_data,
		      kz_downloader_signals[IO_IN_SIGNAL],
		      0);	
	g_input_stream_read_async(G_INPUT_STREAM(priv->input_stream),
				  priv->buffer,
				  sizeof(priv->buffer),
				  G_PRIORITY_DEFAULT,
				  priv->cancellable,
				  cb_input_stream_read,
				  user_data);
}

static void
cb_read (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	GError *error = NULL;
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(user_data);

	priv->input_stream = g_file_read_finish(G_FILE(source_object), result, &error);
	if (!priv->input_stream)
	{
		g_signal_emit(user_data,
			      kz_downloader_signals[ERROR_SIGNAL],
			      0, error->message);
		g_error_free(error);
		return;
	}

	if (priv->to_file && priv->file_name)
	{
		priv->local_file = g_file_new_for_path(priv->file_name);
		priv->output_stream = g_file_replace(priv->local_file,
						    NULL,
						    FALSE,
						    G_FILE_CREATE_PRIVATE|G_FILE_CREATE_REPLACE_DESTINATION,
						    NULL, NULL);
	}
	g_input_stream_read_async(G_INPUT_STREAM(priv->input_stream),
				  priv->buffer,
				  sizeof(priv->buffer),
				  G_PRIORITY_DEFAULT,
				  priv->cancellable,
				  cb_input_stream_read,
				  user_data);

}

static void
cb_query_info (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	GError *error = NULL;
	GFileInfo *info;
	KzDownloaderPrivate *priv = KZ_DOWNLOADER_GET_PRIVATE(user_data);

	info = g_file_query_info_finish(G_FILE(source_object), result, &error);
	if (!info)
	{
		g_signal_emit(user_data,
			      kz_downloader_signals[ERROR_SIGNAL],
			      0, error->message);
		g_error_free(error);
		return;
	}

	priv->contents_size = g_file_info_get_size(info);
	g_object_unref(info);

	g_file_read_async(priv->g_file,
			  G_PRIORITY_DEFAULT,
			  priv->cancellable,
			  cb_read,
			  user_data);
}

static	void
kz_downloader_real_cancel (KzDownloader *downloader)
{
	KzDownloaderPrivate *priv;
	g_return_if_fail(KZ_IS_DOWNLOADER(downloader));

	priv = KZ_DOWNLOADER_GET_PRIVATE(downloader);

	g_cancellable_cancel(priv->cancellable);
}


static	gint
kz_downloader_real_get_percent (KzDownloader *downloader)
{
	glong total, current;
	gint percent;
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), -1);

	total   = kz_downloader_get_total_progress(downloader);
	if (total <= 0)
		return -1;
	current = kz_downloader_get_current_progress(downloader); 
	percent = current * 100 / total;

	return percent;
}

const gchar *
kz_downloader_get_uri (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), NULL);
	
	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->uri;
}

const gchar *
kz_downloader_get_filename (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), NULL);
	
	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->file_name;
}

const gchar *
kz_downloader_get_buffer (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), NULL);

	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->contents->str;
}

goffset
kz_downloader_get_size (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), 0);
	
	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->contents_size;
}

gint
kz_downloader_get_percent (KzDownloader *downloader)
{
	return KZ_DOWNLOADER_GET_CLASS(downloader)->kz_downloader_get_percent(downloader);
}

goffset
kz_downloader_get_current_progress (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), -1);
	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->contents->len;
}

goffset
kz_downloader_get_total_progress (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), -1);
	return KZ_DOWNLOADER_GET_PRIVATE(downloader)->contents_size;
}

static GFile *
create_g_file (const gchar *uri)
{
	GFile *g_file;
        gchar *uri_scheme;

	uri_scheme = g_uri_parse_scheme(uri);
	if (uri_scheme)
	{
		g_file = g_file_new_for_uri(uri);
		g_free(uri_scheme);
	}
	else
	{
		g_file = g_file_new_for_path(uri);
	}

	return g_file;
}

static void
start (KzDownloader *downloader)
{
	KzDownloaderPrivate *priv;
	
	priv = KZ_DOWNLOADER_GET_PRIVATE(downloader);

        g_signal_emit(downloader,
                      kz_downloader_signals[START_SIGNAL],
                      0);
	priv->g_file = create_g_file(priv->uri);

        g_file_query_info_async(priv->g_file,
				G_FILE_ATTRIBUTE_STANDARD_SIZE,
				G_FILE_QUERY_INFO_NONE,
			 	G_PRIORITY_DEFAULT,
				priv->cancellable,
				cb_query_info,
				downloader);
}

gboolean
kz_downloader_to_file (KzDownloader *downloader)
{
	KzDownloaderPrivate *priv;
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), FALSE);
	
	priv = KZ_DOWNLOADER_GET_PRIVATE(downloader);

	/* create file_name from uri if not exist */
	if (!priv->file_name)
		return FALSE;

	priv->to_file = TRUE;

        start(downloader);

	return TRUE;
}

gboolean
kz_downloader_to_buffer (KzDownloader *downloader)
{
	g_return_val_if_fail(KZ_IS_DOWNLOADER(downloader), FALSE);
	
        start(downloader);

	return TRUE;
}


void
kz_downloader_cancel (KzDownloader *downloader)
{
	KZ_DOWNLOADER_GET_CLASS(downloader)->kz_downloader_cancel(downloader);
}
