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

/*
 * guppi-gnumeric-graph.c: a container class to manage extra data elements and
 *			 views of an xml spec
 *
 * Copyright (C) 2001 Ximian, Inc
 *
 * Developed by 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-view.h"
#include "guppi-gnumeric-manager.h"
#include "guppi-gnumeric-vector.h"
#include "guppi-gnumeric-xml.h"

#include <guppi-memory.h>
#include <guppi-seq-string.h>
#include <guppi-seq-string-core.h>
#include <guppi-seq-scalar.h>
#include <guppi-seq-scalar-core.h>
#include <guppi-root-group-item.h>
#include <guppi-root-group-state.h>
#include <guppi-group-view-layout.h>
#include <guppi-metrics.h>
#include <guppi-enums.h>
#include <guppi-defaults.h>
#include <guppi-rgb.h>

#include <gal/util/e-xml-utils.h>

/* Ideally this would be 0 for an
 * un-anti-aliased un scale hair width
 * line
 */
#define HAIR_WIDTH	.5
#define GAP		3.6	/* 1/20 inch */

GupGnmPlotDescriptor const *
gup_gnm_plot_get_descriptor (xmlNode *plot)
{
	extern GupGnmPlotDescriptor gup_gnm_scatter_plot_type;
	extern GupGnmPlotDescriptor gup_gnm_pie_plot_type;
	extern GupGnmPlotDescriptor gup_gnm_line_plot_type;
	extern GupGnmPlotDescriptor gup_gnm_bar_plot_type;
	static GupGnmPlotDescriptor const * const plot_types[] = {
		&gup_gnm_scatter_plot_type,
		&gup_gnm_pie_plot_type,
		&gup_gnm_line_plot_type,
		&gup_gnm_bar_plot_type,
		NULL
	};
    
	xmlNode *type_node = e_xml_get_child_by_name (plot, "Type");
	xmlChar *type;
	int i;

	g_return_val_if_fail (type_node != NULL, NULL);
	type = xmlGetProp (type_node, "name");
	g_return_val_if_fail (type != NULL, NULL);

	for (i = 0 ; plot_types[i] != NULL; i++)
		if (!strcmp (plot_types[i]->name, type)) {
			xmlFree (type);
			return plot_types[i];
		}
	xmlFree (type);
	return NULL;
}

static void
gup_gnm_graph_clear_names (GupGnmGraph *graph)
{
	GupGnmManager *manager = graph->manager;
	if (manager->vectors != NULL) {
		int i;
		
		i = manager->vectors->len;
		while (i-- > 0) {
			GupGnmVector *vector = g_ptr_array_index (manager->vectors, i);
			if (vector != NULL)
				gup_gnm_vector_clear_names (vector, graph, -1);
		}
	}
}

/**
 * gup_gnm_graph_add_wrapper :
 * @graph :
 * @wrapper :
 *
 * Adds @wrapper to the collection of sequences whose lifecyle is controllled
 * by the @graph.
 */
void
gup_gnm_graph_add_wrapper (GupGnmGraph *graph,  GuppiData *wrapper)
{
	if (graph->wrapper_sequences == NULL)
		graph->wrapper_sequences = g_ptr_array_new ();
	g_ptr_array_add	(graph->wrapper_sequences, wrapper);
}

static void
gup_gnm_graph_clear_wrappers (GupGnmGraph *graph)
{
	if (graph->wrapper_sequences != NULL) {
		int i;
		
		i = graph->wrapper_sequences->len;
		while (i-- > 0) {
			GupGnmVector *vector = g_ptr_array_index (graph->wrapper_sequences, i);
			if (vector != NULL)
				guppi_unref (GTK_OBJECT (vector));
		}
		g_ptr_array_free (graph->wrapper_sequences, TRUE);
		graph->wrapper_sequences = NULL;
	}
}

static char *
gup_gnm_series_calc_name (xmlNode *series, int seriesIndex, GupGnmGraph *graph)
{

	char *name = NULL;
	GupGnmVector *vector = gup_gnm_series_dim_get_vector (series, "labels", graph);

	if (vector != NULL) {
		GuppiSeqString *seqs = gup_gnm_vector_get_string (vector);

		if (seqs != NULL) {
			gup_gnm_vector_mark_as_name (vector, graph, seriesIndex);
			name = guppi_strdup (guppi_seq_string_get (seqs, 0));
		}
	} else {
		xmlChar *tmp = xmlGetProp (series, "name");
		if (tmp != NULL) {
			name = guppi_strdup (tmp);
			xmlFree (tmp);
		}
	}
	if (name == NULL)
		name = guppi_strdup_printf ("Series%d", seriesIndex+1);

	xmlSetProp (series, "name", name);
	return name;
}

typedef struct {
	int seriesID;
	char const *dim_name;
	int vectorID;
	gboolean setting_shared;
} SeriesDimClosure;

static gboolean
cb_series_set_dim (GupGnmGraph *graph,
		   xmlNode *plot, xmlNode *series,
		   gpointer user)
{
	SeriesDimClosure *info = user;
	xmlNode *dim;
	/* FIXME : get an 'ID' rather than using index */
	int ID = gup_gnm_series_get_id (series);
	gboolean is_label;

	/* Use negative seriesIDs as a wildcard */
	if (info->seriesID >= 0 && ID != info->seriesID)
		return FALSE;

	is_label = (0 == strcmp (info->dim_name, "labels"));

	dim = gup_gnm_series_get_dimension (series,
		info->dim_name);
	if (dim != NULL) {
		int id = e_xml_get_integer_prop_by_name_with_default (dim, "ID", -1);

		/* clear out the old label if it exists */
		if (is_label && id >= 0)
			gup_gnm_vector_clear_names (
				gup_gnm_manager_get_vector (graph->manager, id),
				graph, info->seriesID);

		if (info->vectorID < 0) {
			xmlUnlinkNode (dim);
			xmlFreeNode (dim);
		} else
			e_xml_set_integer_prop_by_name (dim, "ID", info->vectorID);
	} else if (info->vectorID >= 0)
		gup_gnm_series_add_dimension (series,
			info->dim_name, info->vectorID);
	/* else it was already empty */

	/* store the new name */
	if (is_label)
		guppi_seq_string_set_nc (
			graph->series.names,
			ID, gup_gnm_series_calc_name (series, ID, graph));

	/* If this is a shared dim change the other members of this plot */
	if (info->seriesID >= 0) {
		int i;
		xmlNode *ptr;
		GupGnmPlotDescriptor const *descriptor =
			gup_gnm_plot_get_descriptor (plot);

		g_return_val_if_fail (descriptor != NULL, TRUE);
		for (i = 0; descriptor->spec [i].display_name != NULL ; i++) {
			if (strcmp (info->dim_name, descriptor->spec [i].dim_name))
				continue;
			if (!descriptor->spec[i].shared)
				break;

			info->seriesID = -1;
			for (ptr = series->parent->xmlChildrenNode ; ptr ; ptr = ptr->next) {
				if (ptr == series ||
				    strcmp (series->name, "Series"))
					continue;
				cb_series_set_dim (graph, plot, ptr, user);
			}
			break;
		}
	}

	return TRUE;
}

/**
 * gup_gnm_graph_set_series_dim :
 *
 * Find the series with the supplied ID and change the specified dimension.
 * Handle special case of shared dimensions, and names.
 *
 * returns TRUE if things succeeded.
 */
gboolean
gup_gnm_graph_set_series_dim (GupGnmGraph *graph,
			      int seriesID, char const *dim_name,
			      int vectorID)
{
	SeriesDimClosure closure;

	g_return_val_if_fail (graph != NULL, FALSE);
	g_return_val_if_fail (dim_name != NULL, FALSE);
	g_return_val_if_fail (seriesID >= 0, FALSE);

	closure.seriesID = seriesID;
	closure.dim_name = dim_name;
	closure.vectorID = vectorID;
	return gup_gnm_graph_foreach_series (graph, cb_series_set_dim, &closure);
}

void
gup_gnm_graph_arrange_data (GupGnmGraph const *graph,
			    xmlNode *plot, xmlNode *data,
			    char const **extras, int extra_count)
{
	GupGnmManager *manager = graph->manager;
	int j, i = 0, cat = -1, count = 0;
	int max = manager->arrangement_len - extra_count;

	if (manager->arrangement_len > 1) {
		cat = manager->data_ids[0];
		i = 1;
	}

	for (;i < max ; i++) {
		xmlNode *series;
		
		series = xmlNewChild (data, data->ns, "Series", NULL);
		e_xml_set_integer_prop_by_name (series, "index", count++);

		if (manager->header_ids[i] >= 0)
			gup_gnm_series_add_dimension (series, "labels",
				manager->header_ids[i]);
		gup_gnm_series_add_dimension (series, "values", manager->data_ids[i]);
		if (cat >= 0)
			gup_gnm_series_add_dimension (series, "categories", cat);
		for (j = 0 ; j < extra_count ; j++)
			gup_gnm_series_add_dimension (series, extras[j],
				manager->data_ids[++i]);
	}
}

static GuppiRootGroupView *
gup_gnm_graph_make_plot (GupGnmGraph *graph)
{
	GupGnmPlotDescriptor const *descriptor = NULL;
	xmlNode *node, *plot;
	guppi_compass_t legend_location;
	gboolean singleton = FALSE;

	/* Models */
	GuppiElementState *root_state;
	GuppiElementState *plot_state, *legend_state;
	GuppiElementState *compassbox_state;
	GSList *plot_states = NULL;

	/* View Objects */
	GuppiGroupView	 *root_view;
	GuppiElementView *plot_view, *legend_view = NULL;
	GuppiElementView *compassbox_view;
	GuppiElementView *frame_view, *x_axis_view, *y_axis_view;
	
	GuppiGroupView *group_view;

	g_return_val_if_fail (graph->spec != NULL,  NULL);

	plot = e_xml_get_child_by_name (graph->spec->xmlRootNode, "Plots");

	g_return_val_if_fail (plot != NULL,  NULL);

	gup_gnm_graph_clear_wrappers (graph);

	plot_states = NULL;
	for (plot = plot->xmlChildrenNode ; plot ; plot = plot->next) {
		if (strcmp (plot->name, "Plot"))
			continue;

		if (NULL != (descriptor = gup_gnm_plot_get_descriptor (plot))) {
			plot_states = g_slist_concat (plot_states,
						      descriptor->plot (graph, plot));
			singleton = descriptor->singleton;
		}
	}

	plot_state = NULL;
	if (plot_states != NULL) {
		GSList *tmp = plot_states;
		plot_states = plot_states->next;
		plot_state = tmp->data;
	} else {
		plot_state = guppi_element_state_new("text",
			"font",	guppi_default_font_large (),
			"text",	"Unimplemented",
			NULL);
		singleton = FALSE;
	}

	/* Legend */
	legend_state = NULL;
	legend_location = GUPPI_COMPASS_INVALID;
	node = e_xml_get_child_by_name (graph->spec->xmlRootNode, "Legend");
	if (node != NULL)
		node = e_xml_get_child_by_name (node, "Position");
	if (node != NULL) {
		xmlChar *tmp = xmlNodeGetContent (node);
		if (tmp != NULL) {
			legend_location = guppi_str2compass (tmp);
			xmlFree (tmp);
		}
	}

	legend_state = guppi_element_state_new("legend",
					       "swatch_width",   6.,
					       "swatch_height",  6.,
					       "edge_thickness", HAIR_WIDTH,
					       "edge_margin",	  guppi_in2pt(1/16.0),
					       NULL);
	legend_view = guppi_element_state_make_view (legend_state);
	guppi_unref (legend_state);
	
	/* guppi_element_state_changed_delayed (plot_state); */
	
	if (singleton) {
		
		/* This is an evil, pie-specific hack */
		if (!strcmp (descriptor->name, "Pie")) {
			
			GuppiData *d = NULL;
			GuppiColorPalette *palette = NULL;
			guppi_element_state_get (plot_state,
						 "label_data", &d,
						 "slice_colors", &palette,
						 NULL);
			guppi_element_state_set (legend_state,
						 "labels", d,
						 "swatch_colors", palette, 
						 NULL);
		}
		
	} else if (graph->series.names != NULL) {
		guppi_element_state_set (legend_state,
					 "labels",	 graph->series.names,
					 "swatch_colors", graph->series.colours,
					 NULL);
	}

	/* Frame (i.e. Grid Lines) */
	if (singleton) {
		frame_view = NULL;
	} else {
		GuppiElementState *frame_state = guppi_element_state_new ("frame",
			"minor_rule_thickness", HAIR_WIDTH,
			"major_rule_thickness", HAIR_WIDTH,
			"minor_rule_color",	RGBA_BLACK,
			"major_rule_color",	RGBA_BLACK,
			"frame_color",		RGBA_BLACK,
			"frame_thickness",	.25,
			NULL);
		frame_view  = guppi_element_view_new (frame_state, NULL);
		guppi_unref (frame_state);
	}

	/* Compass Box */
	compassbox_state = guppi_element_state_new ("compass-box",
						    "position", legend_location,
						    NULL);
	compassbox_view  = guppi_element_view_new (compassbox_state, NULL);
	guppi_unref (compassbox_state);

	/* Axes */
	if (singleton) {
		x_axis_view = y_axis_view = NULL;
	} else {
		GuppiElementState *x_axis_state = guppi_element_state_new ("axis",
			"minor_tick_thickness", .25,
			"major_tick_thickness", .5,
			"position", GUPPI_SOUTH,
			NULL);
		GuppiElementState *y_axis_state = guppi_element_state_new ("axis",
			"minor_tick_thickness", .25,
			"major_tick_thickness", .5,
			"position", GUPPI_WEST,
			NULL);
		x_axis_view = guppi_element_view_new (x_axis_state, NULL);
		y_axis_view = guppi_element_view_new (y_axis_state, NULL);
		guppi_unref (x_axis_state);
		guppi_unref (y_axis_state);
	}

	root_state = guppi_root_group_state_new ();
	root_view = GUPPI_GROUP_VIEW (guppi_element_state_make_view (root_state));
	guppi_unref (root_state);

	plot_view = guppi_element_state_make_view (plot_state);
	if (descriptor && descriptor->process_view)
		descriptor->process_view (plot_view);

	/* This view contains our plots and (if they exist) our axes */
	group_view = guppi_group_view_new ();

	if (frame_view) {
		static guint32 const grey = RGBA_TO_UINT(0xd7,0xd7,0xd7,0xff);
		GuppiElementState *back_state = guppi_element_state_new ("background",
#ifdef EYE_CANDY
			"color", 	0xe0e0ffff,
			"color_final",	0xffffffff,
			"gradient_start", GUPPI_SOUTH,
#else
			"color",	grey,
			"color_final",	grey,
#endif
			NULL);
		GuppiElementView *background_view =
			guppi_element_view_new (back_state, NULL);
		guppi_unref (back_state);

		guppi_group_view_layout_same_place (group_view, background_view, frame_view);
		guppi_group_view_layout_same_place (group_view, frame_view, plot_view);
		guppi_element_view_connect_axis_markers (plot_view, GUPPI_X_AXIS, frame_view, GUPPI_X_AXIS);
		guppi_element_view_connect_axis_markers (plot_view, GUPPI_Y_AXIS, frame_view, GUPPI_Y_AXIS);
	}

	if (y_axis_view) {
		guppi_group_view_layout_flush_top  (group_view, y_axis_view, GAP);
		guppi_group_view_layout_flush_left (group_view, y_axis_view, GAP);
		guppi_group_view_layout_horizontally_aligned (group_view, y_axis_view, plot_view, 0.);
		guppi_group_view_layout_flush_right (group_view, plot_view, GAP);

		guppi_element_view_connect_axis_markers (plot_view, GUPPI_Y_AXIS, y_axis_view, GUPPI_Y_AXIS);

	} else {
		guppi_group_view_layout_fill_horizontally (group_view, plot_view, GAP, GAP);
	}

	if (x_axis_view) {
		guppi_group_view_layout_flush_bottom (group_view, x_axis_view, GAP);
		guppi_group_view_layout_flush_right (group_view, x_axis_view, GAP);
		guppi_group_view_layout_vertically_aligned (group_view, plot_view, x_axis_view, 0.);
		guppi_group_view_layout_flush_top (group_view, plot_view, GAP);

		guppi_element_view_connect_axis_markers (plot_view, GUPPI_X_AXIS, x_axis_view, GUPPI_X_AXIS);
	} else {
		guppi_group_view_layout_fill_vertically (group_view, plot_view, GAP, GAP);
	}

	if (plot_states != NULL) {
		GSList *ptr = plot_states;
		for (; ptr != NULL; ptr = ptr->next) {
			GuppiElementView *p2_view = guppi_element_state_make_view (ptr->data);
			guppi_group_view_layout_same_place (group_view, plot_view, p2_view);
			guppi_element_view_connect_axis_markers (plot_view, GUPPI_X_AXIS, p2_view, GUPPI_X_AXIS);
			guppi_element_view_connect_axis_markers (plot_view, GUPPI_Y_AXIS, p2_view, GUPPI_Y_AXIS);
			guppi_unref (ptr->data);
		}
		g_slist_free (plot_states);
	}

	guppi_group_view_add ((GuppiGroupView *) compassbox_view, legend_view);
	guppi_group_view_add ((GuppiGroupView *) compassbox_view, (GuppiElementView *) group_view);

	guppi_group_view_layout_fill (root_view, compassbox_view, GAP, GAP, GAP, GAP);

	guppi_element_view_set_preferred_view_all (plot_view);

	guppi_root_group_view_set_size (GUPPI_ROOT_GROUP_VIEW (root_view),
		6*72, 6*72);
#if 0
	guppi_root_group_item_set_resize_semantics (GUPPI_ROOT_GROUP_ITEM (view->item),
						    ROOT_GROUP_RESIZE_FILL_SPACE);
#endif


	guppi_unref (plot_state);

	return GUPPI_ROOT_GROUP_VIEW (root_view);
}
/**
 * gup_gnm_graph_get_view :
 * @graph :
 *
 * Get a possibly cached view.
 */
GuppiRootGroupView *
gup_gnm_graph_get_view (GupGnmGraph *graph)
{
	if (graph->plot == NULL)
		graph->plot = gup_gnm_graph_make_plot (graph);
	return graph->plot;
}


/**
 * gup_gnm_graph_regenerate_plots :
 * @graph :
 */
void
gup_gnm_graph_regenerate_plots (GupGnmGraph *graph)
{
	GList *view;

	if (graph->plot != NULL)
		guppi_unref (GTK_OBJECT (graph->plot));
	graph->plot = gup_gnm_graph_make_plot (graph);

	for (view = graph->views ; view != NULL ; view = view->next)
		gup_gnm_view_regenerate (view->data);
}

guint32
gup_gnm_graph_get_series_color (GupGnmGraph const *graph, xmlNode *series)
{
	int i = e_xml_get_integer_prop_by_name_with_default (series,
		"index", -1);

	g_return_val_if_fail (i >= 0, 0);

	return guppi_color_palette_get (graph->series.colours, i);
}

GuppiMarker
gup_gnm_graph_get_series_marker (GupGnmGraph const *graph, xmlNode *series)
{
	int i = e_xml_get_integer_prop_by_name_with_default (series,
		"index", -1);

	g_return_val_if_fail (i >= 0, 0);

	return g_array_index (graph->series.markers, GuppiMarker, i);
}

void
gup_gnm_graph_construct (GupGnmGraph *graph, GupGnmManager *manager)
{
	graph->manager = manager;
	graph->plot = NULL;
	graph->views = NULL;
	graph->spec = NULL;
	graph->wrapper_sequences = NULL;
	graph->series.names = guppi_seq_string_core_new ();
	graph->series.colours = guppi_color_palette_new ();
	graph->series.markers = g_array_new (FALSE, TRUE, sizeof (GuppiMarker));
}

void
gup_gnm_graph_release (GupGnmGraph *graph)
{
	if (graph->manager != NULL) {
		if (graph->series.names != NULL)
			guppi_unref0 (graph->series.names);
		if (graph->series.colours != NULL)
			guppi_unref0 (graph->series.colours);
		if (graph->series.markers != NULL)
			g_array_free (graph->series.markers, TRUE);
		if (graph->spec != NULL) {
			xmlFreeDoc (graph->spec);
			graph->spec = NULL;
		}
	}
}

typedef struct {
	char const *name;
	int seriesID;
} SeriesNameClosure;

static gboolean
cb_store_series_name (GupGnmGraph *graph,
		      xmlNode *plot, xmlNode *series,
		      gpointer user)
{
	SeriesNameClosure const *info = user;
	int ID = gup_gnm_series_get_id (series);
	if (ID != info->seriesID)
		return FALSE;

	xmlSetProp (series, "name", info->name);
	guppi_seq_string_set (graph->series.names,
		info->seriesID, info->name);
	return TRUE;
}

/**
 * gup_gnm_graph_store_series_name :
 * @graph :
 * @seriesID :
 * @name :
 *
 * Store a copy of the supplied string in the series_name list,
 * and update the xml.
 */
void
gup_gnm_graph_store_series_name (GupGnmGraph *graph,
				 int seriesID, char const *name)
{
	SeriesNameClosure closure;

	g_return_if_fail (graph->series.names != NULL);

	closure.name = name;
	closure.seriesID = seriesID;
	gup_gnm_graph_foreach_series (graph, cb_store_series_name, &closure);
}

static int
ensure_index (GPtrArray *elements, xmlNode *element)
{
	int tmp = e_xml_get_integer_prop_by_name_with_default (element,
							       "index", -1);
	if (tmp < 0) {
		tmp = elements->len;
		e_xml_set_integer_prop_by_name (element, "index", tmp);
	}
	if (tmp >= elements->len)
		g_ptr_array_set_size (elements, tmp+1);
	g_ptr_array_index (elements, tmp) = element;

	return tmp;
}

static GuppiMarker
stock_marker_alien (int indx)
{
	static GuppiMarker const markers[] = {
		GUPPI_MARKER_DIAMOND,
		GUPPI_MARKER_SQUARE,
		GUPPI_MARKER_TRIANGLE,
		GUPPI_MARKER_X,
		GUPPI_MARKER_AST,
		GUPPI_MARKER_CIRCLE,
		GUPPI_MARKER_CROSS,
		GUPPI_MARKER_HALF_BAR,
		GUPPI_MARKER_BAR,
	};
	static int const marker_count = sizeof(markers)/sizeof(GuppiMarker);
	return markers [indx % marker_count];
}

void
gup_gnm_graph_markup_spec (GupGnmGraph *graph)
{
	GupGnmPlotDescriptor const *descriptor;
	xmlNode *plot, *series, *layout;
	GPtrArray *all_plots, *all_series, *all_names;
	GuppiSeq *seq;
	int i;

	g_return_if_fail (graph->spec != NULL);

	all_plots = g_ptr_array_new ();
	all_series = g_ptr_array_new ();
	all_names = g_ptr_array_new ();

	/* 0) Clear the names */
	seq = GUPPI_SEQ (graph->series.names);
	if (guppi_seq_nonempty (seq)) {
		guppi_seq_delete_range (seq,
			guppi_seq_min_index (seq),
			guppi_seq_max_index (seq));
		gup_gnm_graph_clear_names (graph);
	}
	/* 0.1) reset the colours to stock */

	guppi_color_palette_set_alien_stock (graph->series.colours);
#if 0
	guppi_color_palette_set_stock (graph->series.colours);
#endif

	/* 0.2) clear out the markers too */
	g_array_set_size (graph->series.markers, 0);

	plot = e_xml_get_child_by_name (graph->spec->xmlRootNode, "Plots");

	g_return_if_fail (plot != NULL);

	for (plot = plot->xmlChildrenNode ; plot ; plot = plot->next) {
		if (strcmp (plot->name, "Plot"))
			continue;
		/* 1) Make sure all the plots have index numbers. */
		ensure_index (all_plots, plot);

		/* 2) clean out old descriptors */
		layout = e_xml_get_child_by_name (plot, "DataLayout");
		if (layout != NULL) {
			xmlUnlinkNode (layout);
			xmlFreeNode (layout);
		}

		/* 3) Add shiny new descriptors */
		if (NULL != (descriptor = gup_gnm_plot_get_descriptor (plot))) {
			int i;
			xmlNode *dim, *layout = xmlNewChild (plot, plot->ns,
				"DataLayout", NULL);

			dim = xmlNewChild (layout, layout->ns, "Dimension", 
					   _("Name"));
			xmlSetProp (dim, "dim_name", "labels");
			for (i = 0; descriptor->spec [i].display_name != NULL ; i++) {
				dim = xmlNewChild (layout, layout->ns, "Dimension", 
					_(descriptor->spec[i].display_name));
				xmlSetProp (dim, "dim_name", 
					descriptor->spec[i].dim_name);
				if (!descriptor->spec[i].optional)
					e_xml_set_bool_prop_by_name (dim, "required", TRUE);
				if (descriptor->spec[i].shared)
					e_xml_set_bool_prop_by_name (dim, "shared", TRUE);
			}
		}

		series = e_xml_get_child_by_name (plot, "Data");
		if (series == NULL)
			continue;
		for (series = series->xmlChildrenNode ; series ; series = series->next) {
			int indx;
			char *name = NULL;
			xmlNode *format;

			if (strcmp (series->name, "Series"))
				continue;

			/* 4) Make sure all the series have index numbers. */
			indx = ensure_index (all_series, series);

			/* 5) handle series names */
			name = gup_gnm_series_calc_name (series, indx, graph);

			if (indx >= all_names->len) {
				g_ptr_array_set_size (all_names, indx+1);
				g_array_set_size (graph->series.markers, indx+1);
			}
			g_ptr_array_index (all_names, indx) = name;

			/* 6) Handle series formating */
			format = e_xml_get_child_by_name (series, "Format");

			guppi_color_palette_set (graph->series.colours, indx,
				gup_gnm_attr_get_color (format, "AreaColor",
					guppi_color_palette_get (graph->series.colours, indx)));

			g_array_index (graph->series.markers, GuppiMarker, indx) =
				gup_gnm_attr_get_marker (format, "MarkerShape",
					stock_marker_alien (indx));
		}
	}

	for (i = 0; i < all_names->len ; i++)
		guppi_seq_string_append_nc (graph->series.names,
			g_ptr_array_index (all_names, i));

	g_ptr_array_free (all_names, TRUE);
	g_ptr_array_free (all_series, TRUE);
	g_ptr_array_free (all_plots, TRUE);

	gup_gnm_graph_regenerate_plots (graph);
}

/**
 * gup_gnm_graph_generate_series :
 *
 * FIXME think about how to handle an existing set of plots.
 * For now just use the first
 */
void
gup_gnm_graph_generate_series (GupGnmGraph *graph)
{
	GupGnmManager *manager = graph->manager;
	GupGnmPlotDescriptor const *descriptor;
	xmlNode *plot, *data;

	/* if no type has been selected yet, just store the data,
	 * we'll generate later.
	 */
	if (graph->spec == NULL)
		return;

	plot = e_xml_get_child_by_name (graph->spec->xmlRootNode, "Plots");

	g_return_if_fail (plot != NULL);

	plot = e_xml_get_child_by_name (plot, "Plot");
	data = e_xml_get_child_by_name (plot, "Data");
	if (data != NULL) {
		xmlUnlinkNode (data);
		xmlFreeNode (data);
	}

	gup_gnm_graph_clear_names (graph);

	data = xmlNewChild (plot, plot->ns, "Data", NULL);
	descriptor = gup_gnm_plot_get_descriptor (plot);

	/* There needs to be data and a known descriptor type */
	if (manager->arrangement_len >= 1 && descriptor != NULL) {
		g_return_if_fail (manager->data_ids != NULL);
		g_return_if_fail (manager->header_ids != NULL);
		g_return_if_fail (descriptor->arrange_data != NULL);

		descriptor->arrange_data (graph, plot, data);
	}

	gup_gnm_graph_markup_spec (graph);
}

void
gup_gnm_graph_set_spec (GupGnmGraph *graph, xmlDoc *doc, gboolean copy)
{
	if (graph->spec != NULL) {
		g_return_if_fail (doc != graph->spec && doc != NULL);
		xmlFreeDoc (graph->spec);
	}

	if (doc == NULL) {
		graph->spec = NULL;
		return;
	}
	graph->spec = copy ? xmlCopyDoc (doc, TRUE) : doc;

	ggd (1, {
		   puts ("gup_gnm_graph_set_spec :");
		   xmlDocDump (stdout, graph->spec);
	});

	gup_gnm_graph_markup_spec (graph);
}

void
gup_gnm_graph_set_plottype (GupGnmGraph *graph, xmlNode *plotinfo)
{
	xmlNs	*ns;
	xmlNode *plot;

	g_return_if_fail (plotinfo != NULL);

	if (graph->spec == NULL) {
		xmlNode *legend;

		graph->spec = xmlNewDoc ("1.0");
		graph->spec->xmlRootNode =
			xmlNewDocNode (graph->spec, NULL, "Graph", NULL);
		ns = xmlNewNs (graph->spec->xmlRootNode,
			"http://www.gnumeric.org/graph_v1", "graph");
		xmlSetNs (graph->spec->xmlRootNode, ns);

		legend = xmlNewChild (graph->spec->xmlRootNode, ns,
			"Legend", NULL);
		xmlNewChild (legend, ns, "Position", "east");
	} else {
		xmlNode	*p = e_xml_get_child_by_name (graph->spec->xmlRootNode,
						      "Plots");
		if (p != NULL) {
			xmlUnlinkNode (p);
			xmlFreeNode (p);
		}
		ns = graph->spec->xmlRootNode->ns;
	}
	plot = xmlNewChild (
		xmlNewChild (graph->spec->xmlRootNode, ns, "Plots", NULL),
		ns, "Plot", NULL);

	xmlAddChild (plot,
		xmlCopyNode (e_xml_get_child_by_name (plotinfo, "Type"), TRUE));

	gup_gnm_graph_generate_series (graph);
	xmlDocDump (stdout, graph->spec);
}

GuppiSeqScalar *
gup_gnm_graph_stock_index  (GupGnmGraph *graph, int const n)
{
	int i;
	GuppiSeqScalar *indicies = GUPPI_SEQ_SCALAR (guppi_seq_scalar_core_new ());
	for (i = 0; i < n ; ++i)
		guppi_seq_scalar_append (indicies, i);

	gup_gnm_graph_add_wrapper (graph, GUPPI_DATA (indicies));
	return indicies;
}

GuppiSeqString *
gup_gnm_graph_stock_labels (GupGnmGraph *graph, int const n)
{
	int i;
	GuppiSeqString *labels = GUPPI_SEQ_STRING (guppi_seq_string_core_new ());
	for (i = 0; i < n ; ++i)
		guppi_seq_string_append_nc (labels, 
			guppi_strdup_printf ("%d", i));

	gup_gnm_graph_add_wrapper (graph, GUPPI_DATA (labels));
	return labels;
}


syntax highlighted by Code2HTML, v. 0.9.1