/* Evolution calendar - Live query client object
 *
 * Copyright (C) 2001 Ximian, Inc.
 *
 * Author: Federico Mena-Quintero <federico@ximian.com>
 *
 * 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 of the License, 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtksignal.h>
#include <bonobo/bonobo-exception.h>
#include "cal-query.h"
#include "query-listener.h"



/* Private part of the CalQuery structure */
struct _CalQueryPrivate {
	/* Our query listener implementation */
	QueryListener *ql;

	/* Handle to the query in the server */
	GNOME_Evolution_Calendar_Query corba_query;
};



static void cal_query_class_init (CalQueryClass *class);
static void cal_query_init (CalQuery *query);
static void cal_query_destroy (GtkObject *object);

/* Signal IDs */
enum {
	OBJ_UPDATED,
	OBJ_REMOVED,
	QUERY_DONE,
	EVAL_ERROR,
	LAST_SIGNAL
};

static void marshal_obj_updated (GtkObject *object,
				 GtkSignalFunc func, gpointer func_data,
				 GtkArg *args);
static void marshal_query_done (GtkObject *object,
				GtkSignalFunc func, gpointer func_data,
				GtkArg *args);

static guint query_signals[LAST_SIGNAL];

static GtkObjectClass *parent_class;



/**
 * cal_query_get_type:
 * 
 * Registers the #CalQuery class if necessary, and returns the type ID assigned
 * to it.
 * 
 * Return value: The type ID of the #CalQuery class.
 **/
GtkType
cal_query_get_type (void)
{
	static GtkType cal_query_type = 0;

	if (!cal_query_type) {
		static const GtkTypeInfo cal_query_info = {
			"CalQuery",
			sizeof (CalQuery),
			sizeof (CalQueryClass),
			(GtkClassInitFunc) cal_query_class_init,
			(GtkObjectInitFunc) cal_query_init,
			NULL, /* reserved_1 */
			NULL, /* reserved_2 */
			(GtkClassInitFunc) NULL
		};

		cal_query_type = gtk_type_unique (GTK_TYPE_OBJECT, &cal_query_info);
	}

	return cal_query_type;
}

/* Class initialization function for the calendar query */
static void
cal_query_class_init (CalQueryClass *class)
{
	GtkObjectClass *object_class;

	object_class = (GtkObjectClass *) class;

	parent_class = gtk_type_class (GTK_TYPE_OBJECT);

	query_signals[OBJ_UPDATED] =
		gtk_signal_new ("obj_updated",
				GTK_RUN_FIRST,
				object_class->type,
				GTK_SIGNAL_OFFSET (CalQueryClass, obj_updated),
				marshal_obj_updated,
				GTK_TYPE_NONE, 4,
				GTK_TYPE_STRING,
				GTK_TYPE_BOOL,
				GTK_TYPE_INT,
				GTK_TYPE_INT);
	query_signals[OBJ_REMOVED] =
		gtk_signal_new ("obj_removed",
				GTK_RUN_FIRST,
				object_class->type,
				GTK_SIGNAL_OFFSET (CalQueryClass, obj_removed),
				gtk_marshal_NONE__STRING,
				GTK_TYPE_NONE, 1,
				GTK_TYPE_STRING);
	query_signals[QUERY_DONE] =
		gtk_signal_new ("query_done",
				GTK_RUN_FIRST,
				object_class->type,
				GTK_SIGNAL_OFFSET (CalQueryClass, query_done),
				marshal_query_done,
				GTK_TYPE_NONE, 2,
				GTK_TYPE_ENUM,
				GTK_TYPE_STRING);
	query_signals[EVAL_ERROR] =
		gtk_signal_new ("eval_error",
				GTK_RUN_FIRST,
				object_class->type,
				GTK_SIGNAL_OFFSET (CalQueryClass, eval_error),
				gtk_marshal_NONE__STRING,
				GTK_TYPE_NONE, 1,
				GTK_TYPE_STRING);

	gtk_object_class_add_signals (object_class, query_signals, LAST_SIGNAL);

	class->obj_updated = NULL;
	class->obj_removed = NULL;
	class->query_done = NULL;
	class->eval_error = NULL;

	object_class->destroy = cal_query_destroy;
}

/* Object initialization function for the calendar query */
static void
cal_query_init (CalQuery *query)
{
	CalQueryPrivate *priv;

	priv = g_new0 (CalQueryPrivate, 1);
	query->priv = priv;

	priv->ql = NULL;
	priv->corba_query = CORBA_OBJECT_NIL;
}

/* Destroy handler for the calendar query */
static void
cal_query_destroy (GtkObject *object)
{
	CalQuery *query;
	CalQueryPrivate *priv;

	g_return_if_fail (object != NULL);
	g_return_if_fail (IS_CAL_QUERY (object));

	query = CAL_QUERY (object);
	priv = query->priv;

	/* The server unrefs the query listener, so we just NULL it out here */
	query_listener_stop_notification (priv->ql);
	priv->ql = NULL;

	if (priv->corba_query != CORBA_OBJECT_NIL) {
		CORBA_Environment ev;

		CORBA_exception_init (&ev);
		bonobo_object_release_unref (priv->corba_query, &ev);

		if (BONOBO_EX (&ev))
			g_message ("cal_query_destroy(): Could not release/unref the query");

		CORBA_exception_free (&ev);
		priv->corba_query = CORBA_OBJECT_NIL;
	}

	g_free (priv);
	query->priv = NULL;

	if (GTK_OBJECT_CLASS (parent_class)->destroy)
		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}



/* Marshalers */

typedef void (* ObjUpdatedFunc) (CalQuery *query, const char *uid,
				 gboolean query_in_progress, int n_scanned, int total,
				 gpointer data);

static void
marshal_obj_updated (GtkObject *object, GtkSignalFunc func, gpointer func_data, GtkArg *args)
{
	ObjUpdatedFunc f;

	f = (ObjUpdatedFunc) func;

	(* f) (CAL_QUERY (object), GTK_VALUE_STRING (args[0]),
	       GTK_VALUE_BOOL (args[1]), GTK_VALUE_INT (args[2]), GTK_VALUE_INT (args[3]),
	       func_data);
}

typedef void (* QueryDoneFunc) (CalQuery *query, CalQueryDoneStatus status, const char *error_str,
				gpointer data);

static void
marshal_query_done (GtkObject *object, GtkSignalFunc func, gpointer func_data, GtkArg *args)
{
	QueryDoneFunc f;

	f = (QueryDoneFunc) func;

	(* f) (CAL_QUERY (object), GTK_VALUE_ENUM (args[0]), GTK_VALUE_STRING (args[1]),
	       func_data);
}



/* Callback used when an object is updated in the query */
static void
obj_updated_cb (QueryListener *ql,
		const GNOME_Evolution_Calendar_CalObjUID uid,
		CORBA_boolean query_in_progress,
		CORBA_long n_scanned,
		CORBA_long total,
		gpointer data)
{
	CalQuery *query;

	query = CAL_QUERY (data);

	gtk_signal_emit (GTK_OBJECT (query), query_signals[OBJ_UPDATED],
			 uid, query_in_progress, (int) n_scanned, (int) total);
}

/* Callback used when an object is removed from the query */
static void
obj_removed_cb (QueryListener *ql,
		const GNOME_Evolution_Calendar_CalObjUID uid,
		gpointer data)
{
	CalQuery *query;

	query = CAL_QUERY (data);

	gtk_signal_emit (GTK_OBJECT (query), query_signals[OBJ_REMOVED],
			 uid);
}

/* Callback used when the query terminates */
static void
query_done_cb (QueryListener *ql,
	       GNOME_Evolution_Calendar_QueryListener_QueryDoneStatus corba_status,
	       const CORBA_char *error_str,
	       gpointer data)
{
	CalQuery *query;
	CalQueryDoneStatus status;

	query = CAL_QUERY (data);

	switch (corba_status) {
	case GNOME_Evolution_Calendar_QueryListener_SUCCESS:
		status = CAL_QUERY_DONE_SUCCESS;
		break;

	case GNOME_Evolution_Calendar_QueryListener_PARSE_ERROR:
		status = CAL_QUERY_DONE_PARSE_ERROR;
		break;

	default:
		g_assert_not_reached ();
		return;
	}

	gtk_signal_emit (GTK_OBJECT (query), query_signals[QUERY_DONE],
			 status, error_str);
}

/* Callback used when an error occurs when evaluating the query */
static void
eval_error_cb (QueryListener *ql,
	       const CORBA_char *error_str,
	       gpointer data)
{
	CalQuery *query;

	query = CAL_QUERY (data);

	gtk_signal_emit (GTK_OBJECT (query), query_signals[EVAL_ERROR],
			 error_str);
}

/**
 * cal_query_construct:
 * @query: A calendar query.
 * @cal: Handle to an open calendar.
 * @sexp: S-expression that defines the query.
 * 
 * Constructs a query object by issuing the query creation request to the
 * calendar server.
 * 
 * Return value: The same value as @query on success, or NULL if the request
 * failed.
 **/
CalQuery *
cal_query_construct (CalQuery *query,
		     GNOME_Evolution_Calendar_Cal cal,
		     const char *sexp)
{
	CalQueryPrivate *priv;
	GNOME_Evolution_Calendar_QueryListener corba_ql;
	CORBA_Environment ev;

	g_return_val_if_fail (query != NULL, NULL);
	g_return_val_if_fail (IS_CAL_QUERY (query), NULL);
	g_return_val_if_fail (sexp != NULL, NULL);

	priv = query->priv;

	priv->ql = query_listener_new (obj_updated_cb,
				       obj_removed_cb,
				       query_done_cb,
				       eval_error_cb,
				       query);
	if (!priv->ql) {
		g_message ("cal_query_construct(): Could not create the query listener");
		return NULL;
	}

	corba_ql = BONOBO_OBJREF (priv->ql);
				 
	CORBA_exception_init (&ev);
	priv->corba_query = GNOME_Evolution_Calendar_Cal_getQuery (cal, sexp, corba_ql, &ev);

	if (BONOBO_USER_EX (&ev, ex_GNOME_Evolution_Calendar_Cal_CouldNotCreate)) {		
		g_message ("cal_query_construct(): The server could not create the query");
		goto error;
	} else if (BONOBO_EX (&ev)) {
		g_message ("cal_query_construct(): Could not issue the getQuery() request");
		goto error;
	}

	CORBA_exception_free (&ev);

	return query;

 error:

	CORBA_exception_free (&ev);

	bonobo_object_unref (BONOBO_OBJECT (priv->ql));
	priv->ql = NULL;
	priv->corba_query = CORBA_OBJECT_NIL;
	return NULL;
}

/**
 * cal_query_new:
 * @cal: Handle to an open calendar.
 * @sexp: S-expression that defines the query.
 * 
 * Creates a new query object by issuing the query creation request to the
 * calendar server.
 * 
 * Return value: A newly-created query object, or NULL if the request failed.
 **/
CalQuery *
cal_query_new (GNOME_Evolution_Calendar_Cal cal,
	       const char *sexp)
{
	CalQuery *query;

	query = gtk_type_new (CAL_QUERY_TYPE);

	if (!cal_query_construct (query, cal, sexp)) {
		gtk_object_unref (GTK_OBJECT (query));
		return NULL;
	}

	return query;
}
