/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * xl-surface.c
 *
 * Copyright (C) 2005 Jean Brefort (jean.brefort@normalesup.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include <goffice/goffice-config.h>
#include "xl-surface.h"

#include <glib/gi18n-lib.h>
#include <gsf/gsf-impl-utils.h>

#include <goffice/data/go-data-simple.h>
#include <goffice/graph/gog-axis.h>
#include <goffice/utils/go-format.h>
#include <goffice/utils/go-math.h>

static GogObjectClass *xl_contour_parent_klass;
typedef GogSeries XLSurfaceSeries;
typedef GogSeriesClass XLSurfaceSeriesClass;

#define XL_SURFACE_SERIES_TYPE	(xl_surface_series_get_type ())
#define XL_SURFACE_SERIES(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), XL_SURFACE_SERIES_TYPE, XLSurfaceSeries))
#define XL_IS_SURFACE_SERIES(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), XL_SURFACE_SERIES_TYPE))

static GType xl_surface_series_get_type (void);

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

typedef GogContourPlotClass XLContourPlotClass;

static double *
xl_contour_plot_build_matrix (GogContourPlot const *plot,
			gboolean *cardinality_changed)
{
	unsigned i, j, length;
	GogAxisMap *map;
	GogAxisTick *zticks;
	GogAxis *axis = plot->base.axis[GOG_AXIS_PSEUDO_3D];
	unsigned nticks;
	double x[2], val;
	GogSeries *series = NULL;
	GODataVector *vec;
	unsigned n = plot->rows * plot->columns;
	double *data, minimum, maximum;
	unsigned max;
	GSList *ptr;

	if (!gog_axis_get_bounds (axis, &minimum, &maximum))
		return NULL;
	data = g_new (double, n);
	nticks = gog_axis_get_ticks (axis, &zticks);
	map = gog_axis_map_new (axis, 0, 1);
	for (i = j = 0; i < nticks; i++)
		if (zticks[i].type == GOG_AXIS_TICK_MAJOR) {
			x[j++] = gog_axis_map_to_view (map, zticks[i].position);
			if (j > 1)
				break;
		}
	x[1] -= x[0];

	for (i = 0, ptr = plot->base.series ; ptr != NULL ; ptr = ptr->next) {
		series = ptr->data;
		if (!gog_series_is_valid (GOG_SERIES (series)))
			continue;
		vec = GO_DATA_VECTOR (series->values[1].data);
		length = go_data_vector_get_len (vec);
		for (j = 0; j < plot->columns; j++) {
			/* The vector might be too short, excel is so ugly ;-) */
			val = (j < length)? gog_axis_map_to_view (map,
					go_data_vector_get_value (vec, j)): 0.;
			/* This is an excel compatible plot, so let's be compatible */
			if (val == go_nan || !go_finite (val))
				val = 0.;
			if (fabs (val) == DBL_MAX)
				val = go_nan;
			else {
				val = val/ x[1] - x[0];
				if (val < 0) {
					val = go_nan;
				}
			}
			data[i * plot->columns + j] = val;
		}
		i++;
	}
	g_return_val_if_fail (series != NULL, NULL);
	max = (unsigned) ceil (1 / x[1]);
	series = plot->base.series->data;
	if (series->num_elements != max) {
		series->num_elements = max;
		*cardinality_changed = TRUE;
	}
	gog_axis_map_free (map);
	return data;
}

static void
xl_contour_plot_update (GogObject *obj)
{
	GogContourPlot * model = GOG_CONTOUR_PLOT(obj);
	XLSurfaceSeries * series;
	double zmin =  DBL_MAX, zmax = -DBL_MAX, tmp_min, tmp_max;
	GSList *ptr;
	model->rows = 0;
	model->columns = 0;

	ptr = model->base.series;
	series = ptr->data;
	while (!gog_series_is_valid (GOG_SERIES (series))) {
		ptr = ptr->next;
		if (!ptr)
			return;
		series = ptr->data;
	}
	/* for first series, num_elements is used for zaxis, so we
	can't use it to evaluate model->columns */
	if (series->values[1].data != NULL) {
		model->columns = go_data_vector_get_len (
			GO_DATA_VECTOR (series->values[1].data));
		if (series->values[0].data != NULL)
				model->rows = go_data_vector_get_len (
				GO_DATA_VECTOR (series->values[0].data));
		if (model->rows < model->columns)
			model->columns = model->rows;
	} else if (series->values[0].data != NULL)
		model->columns = go_data_vector_get_len (
			GO_DATA_VECTOR (series->values[0].data));
	model->rows = 1;

	for (ptr = ptr->next; ptr != NULL; ptr = ptr->next) {
		series = ptr->data;
		if (!gog_series_is_valid (GOG_SERIES (series)))
			continue;
		if (series->num_elements > model->columns)
			model->columns = series->num_elements;
		model->rows++;
		go_data_vector_get_minmax (GO_DATA_VECTOR (
			series->values[1].data), &tmp_min, &tmp_max);
		if (zmin > tmp_min) zmin = tmp_min;
		if (zmax < tmp_max) zmax = tmp_max;
	}
	g_free (model->plotted_data);
	model->plotted_data = NULL;
	if ((zmin != model->z.minima)
			|| (zmax != model->z.maxima)) {
		model->z.minima = zmin;
		model->z.maxima = zmax;
		gog_axis_bound_changed (model->base.axis[GOG_AXIS_PSEUDO_3D], GOG_OBJECT (model));
	} else
		gog_plot_update_3d (GOG_PLOT (model));

	gog_axis_bound_changed (model->base.axis[GOG_AXIS_X], obj);
	gog_axis_bound_changed (model->base.axis[GOG_AXIS_Y], obj);
}

static GODataVector *
get_y_vector (GogPlot *plot)
{
	XLContourPlot *contour = XL_CONTOUR_PLOT (plot);
	GSList *ptr;
	int i;

	g_free (contour->y_labels);
	contour->y_labels = g_new0 (char const *, contour->base.rows);

	for (ptr = plot->series, i = 0 ; ptr != NULL ; ptr = ptr->next, i++) {
		XLSurfaceSeries *series = ptr->data;

		if (!gog_series_is_valid (GOG_SERIES (series)))
			continue;
		contour->y_labels[i] = go_data_scalar_get_str (GO_DATA_SCALAR (
				series->values[-1].data));
	}

	return GO_DATA_VECTOR (go_data_vector_str_new (contour->y_labels, i, NULL));
}

static GOData *
xl_contour_plot_axis_get_bounds (GogPlot *plot, GogAxisType axis, 
				GogPlotBoundInfo * bounds)
{
	XLContourPlot *contour = XL_CONTOUR_PLOT (plot);
	GODataVector *vec = NULL;
	GOFormat *fmt;

	if (axis == GOG_AXIS_X) {
		XLSurfaceSeries *series = XL_SURFACE_SERIES (plot->series->data);
		vec = GO_DATA_VECTOR (series->values[0].data);
		fmt = contour->base.x.fmt;
	} else if (axis == GOG_AXIS_Y) {
		if (!contour->base.rows)
			return NULL;
		vec = get_y_vector (plot);
		fmt = contour->base.y.fmt;
	} else {
		if (bounds->fmt == NULL && contour->base.z.fmt != NULL)
			bounds->fmt = go_format_ref (contour->base.z.fmt);
		bounds->val.minima = contour->base.z.minima;
		bounds->val.maxima = contour->base.z.maxima;
		return NULL;
	}
	if (bounds->fmt == NULL && fmt != NULL)
		bounds->fmt = go_format_ref (fmt);
	bounds->val.minima = 1.;
	bounds->logical.minima = 1.;
	bounds->logical.maxima = go_nan;
	bounds->is_discrete    = TRUE;
	bounds->center_on_ticks = TRUE;
	bounds->val.maxima = (axis == GOG_AXIS_X)? 
		contour->base.columns: 
		contour->base.rows;
	return (GOData*) vec;
}

static void
xl_contour_plot_finalize (GObject *obj)
{
	XLContourPlot *plot = XL_CONTOUR_PLOT (obj);
	g_free (plot->y_labels);
	G_OBJECT_CLASS (xl_contour_parent_klass)->finalize (obj);
}

static void
xl_contour_plot_class_init (GogContourPlotClass *klass)
{
	GogPlotClass *gog_plot_klass = (GogPlotClass*) klass;
	GogObjectClass *gog_object_klass = (GogObjectClass *) klass;
	GObjectClass   *gobject_klass = (GObjectClass *) klass;

	xl_contour_parent_klass = g_type_class_peek_parent (klass);

	gobject_klass->finalize     = xl_contour_plot_finalize;

	/* Fill in GOGObject superclass values */
	gog_object_klass->update	= xl_contour_plot_update;
	gog_object_klass->populate_editor	= NULL;

	{
		static GogSeriesDimDesc dimensions[] = {
			{ N_("X"), GOG_SERIES_REQUIRED, FALSE,
			  GOG_DIM_LABEL, GOG_MS_DIM_CATEGORIES },
			{ N_("Z"), GOG_SERIES_REQUIRED, FALSE,
			  GOG_DIM_VALUE, GOG_MS_DIM_VALUES },
		};
		gog_plot_klass->desc.series.dim = dimensions;
		gog_plot_klass->desc.series.num_dim = G_N_ELEMENTS (dimensions);
		gog_plot_klass->desc.series.style_fields = 0;
	}
	/* Fill in GogPlotClass methods */
	gog_plot_klass->axis_get_bounds	= xl_contour_plot_axis_get_bounds;
	gog_plot_klass->series_type = xl_surface_series_get_type();

	klass->build_matrix = xl_contour_plot_build_matrix;
}

static void
xl_contour_plot_init (XLContourPlot *contour)
{
	contour->y_labels = NULL;
}

GSF_DYNAMIC_CLASS (XLContourPlot, xl_contour_plot,
	xl_contour_plot_class_init, xl_contour_plot_init,
	GOG_CONTOUR_PLOT_TYPE)

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

static GogStyledObjectClass *series_parent_klass;

static void
xl_surface_series_update (GogObject *obj)
{
	XLSurfaceSeries *series = XL_SURFACE_SERIES (obj);
	int x_len = 0, z_len = 0;
/*	unsigned old_num = series->num_elements;*/

	if (series->values[1].data != NULL)
		z_len = go_data_vector_get_len (
			GO_DATA_VECTOR (series->values[1].data));
	if (series->values[0].data != NULL)
		x_len = go_data_vector_get_len (
			GO_DATA_VECTOR (series->values[0].data));
	else
		x_len = z_len;
	series->num_elements = MIN (x_len, z_len);

	/* queue plot for redraw */
	gog_object_request_update (GOG_OBJECT (series->plot));
/*	if (old_num != series->base.num_elements)
		gog_plot_request_cardinality_update (series->plot);*/

	if (series_parent_klass->base.update)
		series_parent_klass->base.update (obj);
}

static void
xl_surface_series_init_style (GogStyledObject *gso, GogStyle *style)
{
	series_parent_klass->init_style (gso, style);
}

static void
xl_surface_series_class_init (GogStyledObjectClass *gso_klass)
{
	GogObjectClass * obj_klass = (GogObjectClass *) gso_klass;

	series_parent_klass = g_type_class_peek_parent (gso_klass);
	gso_klass->init_style = xl_surface_series_init_style;
	obj_klass->update = xl_surface_series_update;
}


GSF_DYNAMIC_CLASS (XLSurfaceSeries, xl_surface_series,
	xl_surface_series_class_init, NULL,
	GOG_SERIES_TYPE)


syntax highlighted by Code2HTML, v. 0.9.1