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

/*
 * guppi-gnumeric-vector.c: Handlers for sequence change notification.
 *
 * Copyright (C) 2000-2001 Jody Goldberg (jgoldberg@home.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
 */

#include <config.h>
#include "guppi-gnumeric-vector.h"
#include "guppi-gnumeric-graph.h"
#include "GNOME_Gnumeric_Graph.h"

#include <bonobo.h>
#include <gtk/gtkobject.h>
#include <guppi-seq-scalar.h>
#include <guppi-seq-scalar-core.h>
#include <guppi-seq-string.h>
#include <guppi-seq-string-core.h>
#include <guppi-useful.h>

struct _GupGnmVector {
	GtkObject obj;

	GNOME_Gnumeric_VectorType type;
	GuppiSeqString *string_data;
	GuppiSeqScalar *scalar_data;
	long id;

	gboolean initialized : 1;
	gboolean activated   : 1;
	gboolean has_header  : 1;

	GSList *name_indicies;

	CORBA_Object    corba_obj;	/* local CORBA object */
	union {
		POA_GNOME_Gnumeric_Scalar_Vector	scalar;
		POA_GNOME_Gnumeric_String_Vector	string;
		PortableServer_POA			any;
	} servant;

	/* The remote server monitoring this series */
	union {
		GNOME_Gnumeric_Scalar_Vector	scalar;
		GNOME_Gnumeric_String_Vector	string;
		CORBA_Object			any;
	} subscriber;
};

typedef struct {
	GtkObjectClass parent_class;
} GupGnmVectorClass;

#define GUP_GNM_VECTOR_CLASS(k)		(GTK_CHECK_CLASS_CAST((k), GUP_GNM_VECTOR_TYPE, GupGnmVectorClass))
#define IS_GUP_GNM_VECTOR_CLASS(k)	(GTK_CHECK_CLASS_TYPE ((k), GUP_GNM_VECTOR_TYPE))
#define SERVANT_TO_GUP_GNM_VECTOR(ptr)	(GupGnmVector *)(((void *)ptr) - GTK_STRUCT_OFFSET(GupGnmVector, servant))

static GtkObjectClass *gup_gnm_vector_parent_class = NULL;
static POA_GNOME_Gnumeric_VectorSelection__vepv	vector_selection_vepv;
static POA_GNOME_Gnumeric_Scalar_Vector__vepv	scalar_vector_vepv;
static POA_GNOME_Gnumeric_String_Vector__vepv	string_vector_vepv;

typedef struct {
	GupGnmGraph *graph;
	int indx;
} NameEntry;

void
gup_gnm_vector_mark_as_name (GupGnmVector *vector,
			     GupGnmGraph *graph, unsigned indx)
{
	NameEntry *entry;
	g_return_if_fail (IS_GUP_GNM_VECTOR (vector));

	entry = guppi_new (NameEntry, 1);
	entry->graph = graph;
	entry->indx = indx;
	vector->name_indicies = g_slist_prepend (vector->name_indicies, entry);
}

void
gup_gnm_vector_clear_names (GupGnmVector *vector,
			    GupGnmGraph *graph, unsigned indx)
{
	GSList *ptr, *accum = NULL;

	g_return_if_fail (IS_GUP_GNM_VECTOR (vector));

	for (ptr = vector->name_indicies; ptr != NULL ; ptr = ptr->next) {
		NameEntry *entry = ptr->data;
		if ((graph == NULL || graph == entry->graph) &&
		    (indx < 0 || indx == entry->indx)) {
			guppi_free (entry);
		} else
			accum = g_slist_prepend (accum, entry);
	}

	g_slist_free (vector->name_indicies);
	vector->name_indicies = accum;
}

static void
gup_gnm_vector_update_names (GupGnmVector *vector, char const *name)
{
	GSList *ptr = vector->name_indicies;
	for (; ptr != NULL ; ptr = ptr->next) {
		NameEntry const *entry = ptr->data;
		gup_gnm_graph_store_series_name (entry->graph,
			entry->indx,name);
	}
}

static void
impl_vector_selection_selected (PortableServer_Servant servant,
				const GNOME_Gnumeric_SeqPair * ranges,
				CORBA_Environment * ev)
{
	GupGnmVector *vector = SERVANT_TO_GUP_GNM_VECTOR (servant);

	g_warning ("Guppi : VectorSelection::selected (%p) placeholder\n", vector);
}

static void
impl_scalar_vector_changed (PortableServer_Servant servant,
			    const CORBA_short start,
			    const GNOME_Gnumeric_Scalar_Seq *vals,
			    CORBA_Environment *ev)
{
	GupGnmVector *vector = SERVANT_TO_GUP_GNM_VECTOR (servant);

	ggd(1, printf ("Guppi : scalar changed %p\n", vector));

	if (vector->scalar_data == NULL) {
		vector->scalar_data = GUPPI_SEQ_SCALAR (guppi_seq_scalar_core_new ());
		guppi_seq_scalar_append_many (vector->scalar_data,
			vals->_buffer, sizeof(double), vals->_length);
	} else
		guppi_seq_scalar_set_many (vector->scalar_data, 0,
			vals->_buffer, sizeof(double), vals->_length);

	ggd(5, unsigned i; for (i = 0; i < vals->_length; i++)
	  printf ("scalar[%d] = %f\n", i, vals->_buffer[i]););
#if 0
	/* TODO : What to do if someone uses a number as a name ?
	 * do we convert it to a string ? or just use the index as we do now.
	 */
	gup_gnm_vector_update_names (GupGnmVector *vector, char const *name)
#endif
}

static void
impl_string_vector_changed (PortableServer_Servant servant,
			    const CORBA_short start,
			    const GNOME_Gnumeric_String_Seq *vals,
			    CORBA_Environment *ev)
{
	int i;
	GupGnmVector *vector = SERVANT_TO_GUP_GNM_VECTOR (servant);

	ggd(1, printf ("Guppi : string changed %p\n", vector));

	/* TODO : Do we need a set_many */
	i = vals->_length;
	if (vector->string_data == NULL) {
		vector->string_data = GUPPI_SEQ_STRING (guppi_seq_string_core_new ());
		while (i-- > 0 )
			guppi_seq_string_append (vector->string_data,
				vals->_buffer[i]);
	} else
		while (i-- > 0 )
			guppi_seq_string_set (vector->string_data,
				i, vals->_buffer[i]);
	gup_gnm_vector_update_names (vector, vals->_buffer[0]);
}

static void
gup_gnm_vector_corba_destroy (GupGnmVector *vector)
{
	CORBA_Environment ev;

	CORBA_exception_init (&ev);
	if (vector->subscriber.any != CORBA_OBJECT_NIL) {
		CORBA_Object_release(vector->subscriber.any, &ev);
		if (ev._major != CORBA_NO_EXCEPTION) {
			g_warning ("'%s' : while releasing a vector",
				   bonobo_exception_get_text (&ev));
		}
	}
	if (vector->activated) {
		PortableServer_ObjectId *oid;
		PortableServer_POA poa = bonobo_poa ();

		oid = PortableServer_POA_servant_to_id (poa,
			&vector->servant.any, &ev);
		PortableServer_POA_deactivate_object (poa, oid, &ev);
		vector->activated = FALSE;
		CORBA_free (oid);

		g_return_if_fail (ev._major == CORBA_NO_EXCEPTION);
	}
	if (vector->initialized) {
		switch (vector->type) {
		case GNOME_Gnumeric_VECTOR_TYPE_SCALAR :
		case GNOME_Gnumeric_VECTOR_TYPE_DATE :
			POA_GNOME_Gnumeric_Scalar_Vector__fini (
				&vector->servant.scalar, &ev);
			break;
		case GNOME_Gnumeric_VECTOR_TYPE_STRING :
			POA_GNOME_Gnumeric_String_Vector__fini (
				&vector->servant.string, &ev);
			break;
		default :
			g_warning ("Should not be reached");
		};

		g_return_if_fail (ev._major == CORBA_NO_EXCEPTION);
	}
	CORBA_exception_free (&ev);

}
static void
gup_gnm_vector_destroy (GtkObject *obj)
{
	GupGnmVector *vector = GUP_GNM_VECTOR (obj);

	ggd(1, printf ("guppi : graph-vector destroy %p\n", obj));

	if (vector->string_data != NULL) {
		guppi_unref (GTK_OBJECT (vector->string_data));
		vector->string_data = NULL;
	}
	if (vector->scalar_data != NULL) {
		guppi_unref (GTK_OBJECT (vector->scalar_data));
		vector->scalar_data = NULL;
	}

	gup_gnm_vector_clear_names (vector, NULL, -1);
	gup_gnm_vector_corba_destroy (vector);

	if (gup_gnm_vector_parent_class->destroy)
		gup_gnm_vector_parent_class->destroy (obj);
}

static void
corba_implementation_classes_init (void)
{
	static POA_GNOME_Gnumeric_VectorSelection__epv	selection_epv;
	static POA_GNOME_Gnumeric_Scalar_Vector__epv	scalar_epv;
	static POA_GNOME_Gnumeric_String_Vector__epv	string_epv;

	selection_epv.selected = &impl_vector_selection_selected;
	vector_selection_vepv.GNOME_Gnumeric_VectorSelection_epv =
		&selection_epv;

	scalar_epv.changed = &impl_scalar_vector_changed;
	scalar_vector_vepv.GNOME_Gnumeric_Scalar_Vector_epv =
		&scalar_epv;
	scalar_vector_vepv.GNOME_Gnumeric_VectorSelection_epv =
		&selection_epv;

	string_epv.changed = & impl_string_vector_changed;
	string_vector_vepv.GNOME_Gnumeric_String_Vector_epv =
		&string_epv;
	string_vector_vepv.GNOME_Gnumeric_VectorSelection_epv =
		&selection_epv;
}

static void
gup_gnm_vector_class_init (GtkObjectClass *object_class)
{
	gup_gnm_vector_parent_class = gtk_type_class (gtk_object_get_type ());

	object_class->destroy = &gup_gnm_vector_destroy;

	corba_implementation_classes_init ();
}

static void
gup_gnm_vector_init (GtkObject *object)
{
	GupGnmVector *vector = GUP_GNM_VECTOR (object);

	vector->corba_obj = CORBA_OBJECT_NIL;
	vector->subscriber.any = CORBA_OBJECT_NIL;
	vector->activated = FALSE;
	vector->initialized = FALSE;
	vector->has_header = FALSE;
	vector->name_indicies = NULL;
	vector->string_data = NULL;
	vector->scalar_data = NULL;
}

GtkType
gup_gnm_vector_get_type (void)
{
	static GtkType type = 0;

	if (!type) {
		GtkTypeInfo info = {
			"GupGnmVector",
			sizeof (GupGnmVector),
			sizeof (GupGnmVectorClass),
			(GtkClassInitFunc) gup_gnm_vector_class_init,
			(GtkObjectInitFunc) gup_gnm_vector_init,
			NULL, /* reserved 1 */
			NULL, /* reserved 2 */
			(GtkClassInitFunc) NULL
		};

		type = gtk_type_unique (gtk_object_get_type (), &info);
	}

	return type;
}

static GupGnmVector *
gup_gnm_vector_corba_init (GupGnmVector *vector)
{
	CORBA_Environment ev;

	CORBA_exception_init (&ev);

	switch (vector->type) {
	case GNOME_Gnumeric_VECTOR_TYPE_SCALAR :
	case GNOME_Gnumeric_VECTOR_TYPE_DATE :
		vector->servant.scalar.vepv = &scalar_vector_vepv;
		POA_GNOME_Gnumeric_Scalar_Vector__init (
			&vector->servant.scalar, &ev);
		break;

	case GNOME_Gnumeric_VECTOR_TYPE_STRING :
		vector->servant.string.vepv = &string_vector_vepv;
		POA_GNOME_Gnumeric_String_Vector__init (
			&vector->servant.string, &ev);
		break;

	default :
		g_assert_not_reached ();
	};

	if (ev._major == CORBA_NO_EXCEPTION) {
		PortableServer_ObjectId *oid;
		PortableServer_POA poa = bonobo_poa ();
		
		vector->initialized = TRUE;

		oid = PortableServer_POA_activate_object (poa,
			&vector->servant.any, &ev);
		vector->activated = (ev._major == CORBA_NO_EXCEPTION);

		vector->corba_obj = PortableServer_POA_servant_to_reference (poa,
			&vector->servant.any, &ev);
		CORBA_free (oid);
	} else {
		g_warning ("'%s' : while creating a vector",
			   bonobo_exception_get_text (&ev));
		gtk_object_unref (GTK_OBJECT (vector));
		vector = NULL;
	}
	CORBA_exception_free (&ev);

	return vector;
}

GupGnmVector *
gup_gnm_vector_new (GNOME_Gnumeric_VectorSelection subscriber,
		    GNOME_Gnumeric_VectorType type,
		    GNOME_Gnumeric_VectorID id)
{
	GupGnmVector *vector = NULL;
	CORBA_Environment ev;
	CORBA_Object dup;

	CORBA_exception_init (&ev);
	dup = CORBA_Object_duplicate (subscriber, &ev);
	if (ev._major == CORBA_NO_EXCEPTION) {
		vector = guppi_type_new (gup_gnm_vector_get_type ());
		vector->subscriber.any = dup;
		vector->id = id;
		vector->type = type;

		ggd(2, printf ("Guppi : new vector (type = %d, id = %d) = %p\n", type, id, vector));

		vector = gup_gnm_vector_corba_init (vector);
	} else {
		g_warning ("'%s' : while initializing vector %p",
			   bonobo_exception_get_text (&ev), vector);
	}
	CORBA_exception_free (&ev);

	return vector;
}

/****************************************************************************/

GuppiSeqScalar *
gup_gnm_vector_get_scalar (GupGnmVector *vector)
{
	g_return_val_if_fail (IS_GUP_GNM_VECTOR (vector), NULL);

	if (vector->scalar_data == NULL) {
		vector->scalar_data = GUPPI_SEQ_SCALAR (guppi_seq_scalar_core_new ());
		if (vector->type != GNOME_Gnumeric_VECTOR_TYPE_STRING) {
			GNOME_Gnumeric_Scalar_Seq *values;
			CORBA_Environment  	   ev;

			CORBA_exception_init (&ev);
			GNOME_Gnumeric_Scalar_Vector_value (vector->subscriber.scalar,
							    &values, &ev);

			if (ev._major == CORBA_NO_EXCEPTION) {
				ggd(1, printf ("Guppi : scalar load %p\n", vector));
				ggd(5, unsigned i; for (i = 0; i < values->_length; i++)
				    printf ("scalar[%d] = %f\n", i, values->_buffer[i]););
				guppi_seq_scalar_append_many (GUPPI_SEQ_SCALAR (vector->scalar_data),
							      values->_buffer, sizeof(double), values->_length);

				CORBA_free (values);
			} else {
				guppi_unref0 (vector->scalar_data);
				g_warning ("'%s' : while initializing vector with scalars %p",
					   bonobo_exception_get_text (&ev), vector);
			}
			CORBA_exception_free (&ev);
		} else {
			/* load the vector if necessary */
			int i, n = guppi_seq_size (GUPPI_SEQ (gup_gnm_vector_get_string (vector)));
			for (i = 0; i < n ; )
				guppi_seq_scalar_append (GUPPI_SEQ_SCALAR (vector->scalar_data), ++i);
		}
	}
	return vector->scalar_data;
}

/* One less that the Julian day number of 19000101.  */
static int excel_date_origin = 0;

/*
 * The serial number of 19000228.  Excel allocates a serial number for
 * the non-existing date 19000229.
 */
static const int excel_date_serial_19000228 = 59;
static void
excel_date_init (void)
{
  /* Day 1 means 1st of January of 1900 */
  GDate date;
  g_date_set_dmy (&date, 1, 1, 1900);
  excel_date_origin = g_date_julian (&date) - 1;
}

static gboolean
guppi_date_set_excel (GDate *dst, int serial)
{
  if (excel_date_origin == 0)
    excel_date_init ();

  if (serial > excel_date_serial_19000228) {
    if (serial == excel_date_serial_19000228 + 1)
      g_warning ("Request for date 19000229.");
    serial--;
  }
  serial += excel_date_origin;
  if (! g_date_valid_julian (serial))
    return FALSE;
  g_date_set_julian (dst, serial);
  return TRUE;
}

GuppiSeqString *
gup_gnm_vector_get_string (GupGnmVector *vector)
{
	g_return_val_if_fail (IS_GUP_GNM_VECTOR (vector), NULL);

	if (vector->string_data == NULL) {
		vector->string_data = GUPPI_SEQ_STRING (guppi_seq_string_core_new ());
		if (vector->type == GNOME_Gnumeric_VECTOR_TYPE_STRING) {
			GNOME_Gnumeric_String_Seq *values;
			CORBA_Environment  	   ev;
			int i;

			CORBA_exception_init (&ev);
			GNOME_Gnumeric_String_Vector_value (vector->subscriber.string,
							    &values, &ev);

			if (ev._major == CORBA_NO_EXCEPTION) {
				ggd(1, printf ("Guppi : string load %p\n", vector));
				ggd(5, unsigned i; for (i = 0; i < values->_length; i++)
				    printf ("string[%d] = %s\n", i, values->_buffer[i]););

				for (i = 0; i < values->_length; i++)
					guppi_seq_string_append (vector->string_data,
								 values->_buffer [i]);

				CORBA_free (values);
			} else {
				guppi_unref0 (vector->string_data);
				g_warning ("'%s' : while initializing vector with strings %p",
					   bonobo_exception_get_text (&ev), vector);
			}
			CORBA_exception_free (&ev);
		} else {
			/* load the vector if necessary */
			GuppiSeqScalar *scalars = gup_gnm_vector_get_scalar (vector);
			int i, n = guppi_seq_size (GUPPI_SEQ (scalars));
			gchar buf[32];
			char *label;
			double val;
			GDate date;

			for (i = 0; i < n ; i++) {
				val = guppi_seq_scalar_get (scalars, i);
				if (gup_gnm_vector_is_date (vector) &&
				    guppi_date_set_excel (&date, (int)floor (val + DBL_EPSILON))) {
					g_date_strftime (buf, sizeof(buf), "%d %b %y", &date);
					guppi_seq_string_append (vector->string_data, buf);
				} else {
					label = guppi_strdup_printf ("%g", val);
					guppi_seq_string_append_nc (vector->string_data, label);
				}
			}
		}
	}
	return vector->string_data;
}

CORBA_Object
gup_gnm_vector_servant_get (GupGnmVector *vector)
{
	g_return_val_if_fail (IS_GUP_GNM_VECTOR (vector), CORBA_OBJECT_NIL);

	return vector->corba_obj;
}

int
gup_gnm_vector_id_get (GupGnmVector const *vector)
{
	g_return_val_if_fail (IS_GUP_GNM_VECTOR (vector), -1);

	return vector->id;
}
gboolean
gup_gnm_vector_is_date (GupGnmVector const *vec)
{
	g_return_val_if_fail (IS_GUP_GNM_VECTOR (vec), FALSE);

	return vec->type == GNOME_Gnumeric_VECTOR_TYPE_DATE;
}


syntax highlighted by Code2HTML, v. 0.9.1