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

/*
 *  Copyright (C) 2004 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 <string.h>
#include <glib/gi18n.h>

#include "kz-xmlrpc.h"
#include "kz-http.h"
#include "kz-xml.h"

enum {
	XMLRPC_COMPLETED_SIGNAL,
	XMLRPC_LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_XMLRPC_URI
};

#define KZ_XML_RPC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_XML_RPC, KzXMLRPCPrivate))

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 dispose         (GObject *object);

typedef struct _KzXMLRPCPrivate KzXMLRPCPrivate;
struct _KzXMLRPCPrivate 
{
	gchar *xmlrpc_uri;

	GList *results;
};


static gint kz_xml_rpc_signals[XMLRPC_LAST_SIGNAL] = {0};

G_DEFINE_TYPE(KzXMLRPC, kz_xml_rpc, G_TYPE_OBJECT)

static void
kz_xml_rpc_class_init (KzXMLRPCClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass *) klass;

	object_class->dispose      = dispose;
	object_class->set_property = set_property;
	object_class->get_property = get_property;
	
	klass->xml_rpc_completed   = NULL;

	g_object_class_install_property(
		object_class,
		 PROP_XMLRPC_URI,
		 g_param_spec_string(
			 "xmlrpc-uri",
			 _("URI"),
			 _("The URI of XML-RPC interface"),
			 NULL,
			 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));

	kz_xml_rpc_signals[XMLRPC_COMPLETED_SIGNAL]
		= g_signal_new ("xml_rpc_completed",
				G_TYPE_FROM_CLASS (klass),
				G_SIGNAL_RUN_LAST,
				G_STRUCT_OFFSET (KzXMLRPCClass, xml_rpc_completed),
				NULL, NULL,
				g_cclosure_marshal_VOID__POINTER,
				G_TYPE_NONE, 1,
				G_TYPE_POINTER);
	g_type_class_add_private (object_class, sizeof(KzXMLRPCPrivate));
}


static void
kz_xml_rpc_init (KzXMLRPC *xmlrpc)
{
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (xmlrpc);

	priv->xmlrpc_uri = NULL;
	priv->results    = NULL;
}


void
dispose (GObject *object)
{
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (object);

	if (priv->xmlrpc_uri)
		g_free(priv->xmlrpc_uri);
	if (priv->results)
	{
		GList *node, *list;
		list = priv->results;
		for (node = list; node; node = g_list_next(node))
		{
			if (!node->data) continue;
	
			g_free(node->data);
		}
		g_list_free(list);
		priv->results = NULL;
	}

	priv->xmlrpc_uri = NULL;
	
	if (G_OBJECT_CLASS (kz_xml_rpc_parent_class)->dispose)
		G_OBJECT_CLASS (kz_xml_rpc_parent_class)->dispose(object);
}


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

	switch (prop_id)
	{
	case PROP_XMLRPC_URI:
		priv->xmlrpc_uri = 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)
{
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (object);

	switch (prop_id)
	{
	case PROP_XMLRPC_URI:
		g_value_set_string(value, priv->xmlrpc_uri);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}


KzXMLRPC *
kz_xml_rpc_new (const gchar *uri)
{
	KzXMLRPC *xmlrpc = NULL;

	xmlrpc = g_object_new(KZ_TYPE_XML_RPC, 
			      "xmlrpc-uri", uri,
			      NULL);
	
	return xmlrpc;
}


static KzXMLNode *
kz_xml_node_get_first_child_element (KzXMLNode *parent)
{
	KzXMLNode *node;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (kz_xml_node_is_element(node)) break;
	}
	
	return node;
}

static gboolean
parse_response (KzXMLRPC *xmlrpc, KzXMLNode *root)
{
	KzXMLNode *node;
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (xmlrpc);

	if (!kz_xml_node_name_is(root, "methodResponse"))
		return FALSE;


	node = kz_xml_node_get_first_child_element(root);
	if (!node || !kz_xml_node_name_is(node, "params"))
		return FALSE;

	for (node = kz_xml_node_first_child(node);
	     node;
	     node = kz_xml_node_next(node))
	{
		KzXMLNode *value_node, *child_node;

		if (!kz_xml_node_is_element(node)) continue;
		if (!kz_xml_node_name_is(node, "param")) continue;

		value_node = kz_xml_node_get_first_child_element(node);
		if (!value_node || !kz_xml_node_name_is(value_node, "value")) continue;

		child_node = kz_xml_node_get_first_child_element(value_node);
		if (!child_node) continue;

		if (kz_xml_node_name_is(child_node, "struct"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "array"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "string"))
		{
			gchar *text = kz_xml_node_to_str(child_node);
			priv->results = g_list_append(priv->results, text);
		}
		else if (kz_xml_node_name_is(child_node, "int") ||
			 kz_xml_node_name_is(child_node, "i4"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "boolean"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "double"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "base64"))
		{
		}
		else if (kz_xml_node_name_is(child_node, "dataTime.iso8601"))
		{
		}
	}

	return TRUE;
}


static gboolean
idle_load_complete(gpointer data)
{
	KzHTTP *http = data;

	g_object_unref(G_OBJECT(http));

	return FALSE;
}


static void
cb_http_load_complete(KzIO *io, GError *error, KzXMLRPC *xmlrpc)
{
	KzXML *xml = NULL;
	KzXMLNode *node;
	const gchar *buf;
	guint size;
	gboolean ret;

	g_return_if_fail(KZ_IS_XML_RPC(xmlrpc));
	g_return_if_fail(KZ_IS_IO(io));

	g_signal_handlers_disconnect_by_func
		(io,
		 G_CALLBACK(cb_http_load_complete),
		 xmlrpc);

	if (error) goto ERROR;

	buf  = kz_io_get_buffer(io);
	size = kz_io_get_loaded_size(io);

	if (!buf || size == 0) goto ERROR;

	xml = kz_xml_new();
	if (!kz_xml_load_xml(xml, buf, size))
		goto ERROR;

	/* parse response xml */ 
	node = kz_xml_get_root_element (xml);
	if (!node) goto ERROR;

	ret = parse_response(xmlrpc, node);
ERROR:
	if (xml) g_object_unref(xml);

	g_signal_emit(xmlrpc,
		      kz_xml_rpc_signals[XMLRPC_COMPLETED_SIGNAL],
		      0, NULL);

	g_idle_add(idle_load_complete, io);
}


static void
create_params_node (KzXMLNode   *params_node,
		    const gchar *param,
		    va_list	 var_args)
{
  	const gchar *name;
  
  	name = param;
  
  	while (name)
    	{
		KzXMLNode *param_node, *value_node,
			  *string_node, *text_node;
		param_node = kz_xml_element_node_new("param");
		kz_xml_node_append_child(params_node, param_node);
		value_node = kz_xml_element_node_new("value");
		kz_xml_node_append_child(param_node, value_node);
		string_node = kz_xml_element_node_new("string");
		kz_xml_node_append_child(value_node, string_node);

		text_node = kz_xml_text_node_new(name);
		kz_xml_node_append_child(string_node, text_node);

		name = va_arg (var_args, gchar*);
    	}
}


void
kz_xml_rpc_call (KzXMLRPC *xmlrpc, 
		 const gchar *method_name,
		 const gchar *param,
		 ...)
{ 
	KzHTTP *http;
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (xmlrpc);
	KzXML *xml;
	KzXMLNode *root, *method_node, *method_name_node, *params_node, *text_node;
  	va_list var_args;
	gchar *str;

	xml = kz_xml_new();
	root = xml->root;
	
	method_node = kz_xml_element_node_new("methodCall");
	kz_xml_node_append_child(root, method_node);

	method_name_node = kz_xml_element_node_new("methodName");
	kz_xml_node_append_child(method_node, method_name_node);

	text_node = kz_xml_text_node_new(method_name);
	kz_xml_node_append_child(method_name_node, text_node);

	params_node = kz_xml_element_node_new("params");
	kz_xml_node_append_child(method_node, params_node);
	
	/*
	 * <params>
	 *   <param>
	 *     <value><string>title</string></value>
	 *   </param>
	 *   <param>
	 *     <value><string>Kazehakse</string></value>
	 *   </param>
	 *   <param>
	 *     <value><string>link</string></value>
	 *   </param>
	 *   <param>
	 *     <value><string>http://kazehakase.sourceforge.jp/</string></value>
	 *   </param>
	 *   ....
	 * </params>
	 */
	va_start(var_args, param);
	create_params_node(params_node, param, var_args);
	va_end(var_args);

	str = kz_xml_node_to_xml(root);

	http = kz_http_post_new(priv->xmlrpc_uri, str);
	g_free(str);
	g_object_unref(xml);

	g_signal_connect(http, "io_completed",
			 G_CALLBACK(cb_http_load_complete),
			 xmlrpc);
	
	kz_io_load_to_buffer(KZ_IO(http));
}

const GList *
kz_xml_rpc_get_results (KzXMLRPC *xmlrpc)
{
	KzXMLRPCPrivate *priv = KZ_XML_RPC_GET_PRIVATE (xmlrpc);
	return priv->results;
}

