/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * gog-histogram.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 "gog-histogram.h"
#include <goffice/data/go-data.h>
#include <goffice/graph/gog-axis.h>
#include <goffice/graph/gog-chart.h>
#include <goffice/graph/gog-renderer.h>
#include <goffice/graph/gog-series-lines.h>
#include <goffice/utils/go-format.h>
#include <goffice/utils/go-math.h>
#include <glib/gi18n-lib.h>
#include <gsf/gsf-impl-utils.h>
#include <libart_lgpl/libart.h>

#define GOG_HISTOGRAM_PLOT_SERIES_TYPE	(gog_histogram_plot_series_get_type ())
#define GOG_HISTOGRAM_PLOT_SERIES(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_HISTOGRAM_PLOT_SERIES_TYPE, GogHistogramPlotSeries))
#define IS_GOG_HISTOGRAM_PLOT_SERIES(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_HISTOGRAM_PLOT_SERIES_TYPE))

typedef struct {
	GogSeries base;
	GogObject *droplines;
	double *x, *y;
} GogHistogramPlotSeries;
typedef GogSeriesClass GogHistogramPlotSeriesClass;

GType gog_histogram_plot_series_get_type (void);
GType gog_histogram_plot_view_get_type (void);

static GogObjectClass *histogram_plot_parent_klass;

static void
gog_histogram_plot_clear_formats (GogHistogramPlot *model)
{
	if (model->x.fmt != NULL) {
		go_format_unref (model->x.fmt);
		model->x.fmt = NULL;
	}
	if (model->y.fmt != NULL) {
		go_format_unref (model->y.fmt);
		model->y.fmt = NULL;
	}
}

static char const *
gog_histogram_plot_type_name (G_GNUC_UNUSED GogObject const *item)
{
	/* xgettext : the base for how to name histogram-plot objects
	 * eg The 2nd histogram-plot in a chart will be called
	 * 	Histogram2 */
	return N_("Histogram");
}

static void
gog_histogram_plot_update (GogObject *obj)
{
	GogHistogramPlot *model = GOG_HISTOGRAM_PLOT (obj);
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (
		model->base.series->data);
	double x_min, x_max, y_min = DBL_MAX, y_max = -DBL_MAX, *x_vals, *y_vals, val;
	unsigned i;

	if (!gog_series_is_valid (GOG_SERIES (series)) || series->base.num_elements == 0)
			return;
	
	g_free (series->x);
	series->x = g_new (double, series->base.num_elements);
	if (series->base.values[0].data != NULL) {
		x_vals = go_data_vector_get_values (GO_DATA_VECTOR (series->base.values[0].data));
		x_min = x_vals[0];
		x_max = x_vals[series->base.num_elements];
		if (model->x.fmt == NULL)
			model->x.fmt = go_data_preferred_fmt (series->base.values[0].data);
		for (i = 0; i < series->base.num_elements; i++)
			series->x[i] = (x_vals[i] + x_vals[i+1]) / 2;
	} else {
		x_vals = NULL;
		x_min = 0;
		x_max = series->base.num_elements;
		for (i = 0; i < series->base.num_elements; i++)
			series->x[i] = (double) i + 0.5;
	}
	if (model->x.minima != x_min || model->x.maxima != x_max) {
		model->x.minima = x_min;
		model->x.maxima = x_max;
		gog_axis_bound_changed (model->base.axis[0], GOG_OBJECT (model));
	}
	g_free (series->y);
	series->y = NULL;
	if (series->base.values[1].data != NULL) {
		if (x_vals) {
			series->y = g_new (double, series->base.num_elements);
			y_vals = go_data_vector_get_values (GO_DATA_VECTOR (series->base.values[1].data));
			for (i = 0; i < series->base.num_elements; i++)
				if (go_finite (y_vals[i])) {
					series->y[i] = val = y_vals[i] / (x_vals[i+1] - x_vals[i]);
					if (val < y_min)
						y_min = val;
					if (val > y_max)
						y_max = val;
				} else
					series->y[i] = 0.;
		} else
			go_data_vector_get_minmax (
				GO_DATA_VECTOR (series->base.values[1].data), &y_min, &y_max);
		if (model->y.fmt == NULL)
			model->y.fmt = go_data_preferred_fmt (series->base.values[1].data);
	}
	if (y_min > y_max)
		y_min = y_max = go_nan;
	if (model->y.minima != y_min || model->y.maxima != y_max) {
		model->y.minima = y_min;
		model->y.maxima = y_max;
		gog_axis_bound_changed (model->base.axis[1], GOG_OBJECT (model));
	}
	gog_object_emit_changed (GOG_OBJECT (obj), FALSE);
}

static GOData *
gog_histogram_plot_axis_get_bounds (GogPlot *plot, GogAxisType axis,
			      GogPlotBoundInfo *bounds)
{
	GogHistogramPlot *model = GOG_HISTOGRAM_PLOT (plot);


	if (axis == GOG_AXIS_X) {
		bounds->val.minima = model->x.minima;
		bounds->val.maxima = model->x.maxima;
		if (bounds->fmt == NULL && model->x.fmt != NULL)
			bounds->fmt = go_format_ref (model->x.fmt);
	} else {
		bounds->val.minima = model->y.minima;
		bounds->val.maxima = model->y.maxima;
		if (bounds->fmt == NULL && model->y.fmt != NULL)
			bounds->fmt = go_format_ref (model->y.fmt);
	}
	bounds->is_discrete = FALSE;
	return NULL;
}

static void
gog_histogram_plot_finalize (GObject *obj)
{
	gog_histogram_plot_clear_formats (GOG_HISTOGRAM_PLOT (obj));
	G_OBJECT_CLASS (histogram_plot_parent_klass)->finalize (obj);
}

static void
gog_histogram_plot_class_init (GogPlotClass *gog_plot_klass)
{
	GObjectClass *gobject_klass = (GObjectClass *) gog_plot_klass;
	GogObjectClass *gog_object_klass = (GogObjectClass *) gog_plot_klass;
	GogPlotClass   *plot_klass = (GogPlotClass *) gog_plot_klass;
	
	histogram_plot_parent_klass = g_type_class_peek_parent (gog_plot_klass);

	gobject_klass->finalize = gog_histogram_plot_finalize;

	gog_object_klass->type_name	= gog_histogram_plot_type_name;
	gog_object_klass->view_type	= gog_histogram_plot_view_get_type ();
	gog_object_klass->update	= gog_histogram_plot_update;

	{
		static GogSeriesDimDesc dimensions[] = {
			{ N_("Limits"), GOG_SERIES_REQUIRED, FALSE,
			  GOG_DIM_INDEX, GOG_MS_DIM_CATEGORIES },
			{ N_("Values"), GOG_SERIES_REQUIRED, FALSE,
			  GOG_DIM_VALUE, GOG_MS_DIM_VALUES },
		};
		plot_klass->desc.series.dim = dimensions;
		plot_klass->desc.series.num_dim = G_N_ELEMENTS (dimensions);
	}
	plot_klass->desc.num_series_min = 1;
	plot_klass->desc.num_series_max = 1;
	plot_klass->series_type = gog_histogram_plot_series_get_type ();
	plot_klass->axis_set = GOG_AXIS_SET_XY;
	plot_klass->desc.series.style_fields	= GOG_STYLE_LINE | GOG_STYLE_FILL;
	plot_klass->axis_get_bounds   		= gog_histogram_plot_axis_get_bounds;
}

GSF_DYNAMIC_CLASS (GogHistogramPlot, gog_histogram_plot,
	gog_histogram_plot_class_init, NULL,
	GOG_PLOT_TYPE)

/*****************************************************************************/
typedef GogPlotView		GogHistogramPlotView;
typedef GogPlotViewClass	GogHistogramPlotViewClass;

static void
gog_histogram_plot_view_render (GogView *view, GogViewAllocation const *bbox)
{
	GogHistogramPlot const *model = GOG_HISTOGRAM_PLOT (view->model);
	GogChart *chart = GOG_CHART (view->model->parent);
	GogChartMap *chart_map;
	GogAxisMap *x_map, *y_map;
	GogViewAllocation const *area;
	GogHistogramPlotSeries const *series;
	double *x_vals = NULL, *y_vals, curx, cury;
	unsigned i, j, nb;
	ArtVpath *path ;
	GSList *ptr;
	GogStyle *style;

	if (model->base.series == NULL)
		return;
	series = GOG_HISTOGRAM_PLOT_SERIES (model->base.series->data);
	nb = (series->base.num_elements + 1) * 2 + 2;
	style = GOG_STYLED_OBJECT (series)->style;
	if (series->base.num_elements < 1)
		return;
	area = gog_chart_view_get_plot_area (view->parent);
	chart_map = gog_chart_map_new (chart, area, 
				       GOG_PLOT (model)->axis[GOG_AXIS_X], 
				       GOG_PLOT (model)->axis[GOG_AXIS_Y],
				       NULL, FALSE);
	if (!gog_chart_map_is_valid (chart_map)) {
		gog_chart_map_free (chart_map);
		return;
	}

	x_map = gog_chart_map_get_axis_map (chart_map, 0);
	y_map = gog_chart_map_get_axis_map (chart_map, 1);

	if (series->base.values[0].data)
		x_vals = go_data_vector_get_values (
			GO_DATA_VECTOR (series->base.values[0].data));
	y_vals = (x_vals)? series->y: go_data_vector_get_values (
		GO_DATA_VECTOR (series->base.values[1].data));

	path = art_new (ArtVpath, nb);
	path[0].code = ART_MOVETO;
	curx = path[0].x = gog_axis_map_to_view (x_map, ((x_vals)? x_vals[0]: 0.));
	path[0].y = gog_axis_map_get_baseline (y_map);
	for (i = 0, j = 1; i < series->base.num_elements; i++) {
		path[j].code = ART_LINETO;
		path[j].x = curx;
		cury = path[j++].y = gog_axis_map_to_view (y_map, y_vals[i]);
		path[j].code = ART_LINETO;
		curx = path[j].x = gog_axis_map_to_view (x_map, ((x_vals)? x_vals[i+1]: 0.));
		path[j++].y = cury;
	}
	path[j].code = ART_LINETO;
	path[j].x = curx;
	path[j++].y = path[0].y;
	path[j].code = ART_LINETO;
	path[j].x = path[0].x;
	path[j++].y = path[0].y;
	path[j].code = ART_END;
	gog_renderer_push_style (view->renderer, style);
	gog_renderer_draw_sharp_polygon (view->renderer, path, FALSE);

	if (series->droplines) {
		ArtVpath droppath[3];
		droppath[0].code = ART_MOVETO;
		droppath[1].code = ART_LINETO;
		droppath[2].code = ART_END;
		gog_renderer_push_style (view->renderer,
			gog_styled_object_get_style (GOG_STYLED_OBJECT (series->droplines)));
		for (i = 1; i < series->base.num_elements; i++) {
			droppath[0].x = droppath[1].x =
				gog_axis_map_to_view (x_map, ((x_vals)? x_vals[i]: 0.));
			if (y_vals[i-1] * y_vals[i] > 0.) {
				droppath[0].y = path[0].y;
				if (y_vals[i] > 0.)
					droppath[1].y = gog_axis_map_to_view (y_map,
						MIN (y_vals[i-1], y_vals[i]));
				else
					droppath[1].y = gog_axis_map_to_view (y_map,
						MAX (y_vals[i-1], y_vals[i]));
			} else {
				droppath[0].y = gog_axis_map_to_view (y_map, y_vals[i-1]);
				droppath[1].y = gog_axis_map_to_view (y_map, y_vals[i]);
			}
			gog_renderer_draw_path (view->renderer, droppath);
		}
		gog_renderer_pop_style (view->renderer);
	}
/*	int nb = series->base.num_elements - 1;
	x = g_new (double, nb);
	y = g_new (double, nb);

	g_free (x);
	g_free (y);*/
	j--;
	path[j].code = ART_END;
	gog_renderer_draw_path (view->renderer, path);
	gog_renderer_pop_style (view->renderer);
	art_free (path);
	/* Now render children */
	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next)
		gog_view_render	(ptr->data, bbox);

	gog_chart_map_free (chart_map);
}

static GogViewClass *histogram_plot_view_parent_klass;

static void
gog_histogram_plot_view_size_allocate (GogView *view, GogViewAllocation const *allocation)
{
	GSList *ptr;
	for (ptr = view->children; ptr != NULL; ptr = ptr->next)
		gog_view_size_allocate (GOG_VIEW (ptr->data), allocation);
	(histogram_plot_view_parent_klass->size_allocate) (view, allocation);
}

static void
gog_histogram_plot_view_class_init (GogViewClass *view_klass)
{
	histogram_plot_view_parent_klass = (GogViewClass*) g_type_class_peek_parent (view_klass);
	view_klass->render	  = gog_histogram_plot_view_render;
	view_klass->size_allocate = gog_histogram_plot_view_size_allocate;
	view_klass->clip	  = FALSE;
}

GSF_DYNAMIC_CLASS (GogHistogramPlotView, gog_histogram_plot_view,
	gog_histogram_plot_view_class_init, NULL,
	GOG_PLOT_VIEW_TYPE)

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

typedef GogView		GogHistogramSeriesView;
typedef GogViewClass	GogHistogramSeriesViewClass;

#define GOG_HISTOGRAM_SERIES_VIEW_TYPE	(gog_histogram_series_view_get_type ())
#define GOG_HISTOGRAM_SERIES_VIEW(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_HISTOGRAM_SERIES_VIEW_TYPE, GogHistogramSeriesView))
#define IS_GOG_HISTOGRAM_SERIES_VIEW(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_HISTOGRAM_SERIES_VIEW_TYPE))

static void
gog_histogram_series_view_render (GogView *view, GogViewAllocation const *bbox)
{
	GSList *ptr;
	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next)
		gog_view_render	(ptr->data, bbox);
}

static void
gog_histogram_series_view_size_allocate (GogView *view, GogViewAllocation const *allocation)
{
	GSList *ptr;

	for (ptr = view->children; ptr != NULL; ptr = ptr->next)
		gog_view_size_allocate (GOG_VIEW (ptr->data), allocation);
}

static void
gog_histogram_series_view_class_init (GogHistogramSeriesViewClass *gview_klass)
{
	GogViewClass *view_klass = GOG_VIEW_CLASS (gview_klass);
	view_klass->render = gog_histogram_series_view_render;
	view_klass->size_allocate = gog_histogram_series_view_size_allocate;
	view_klass->build_toolkit = NULL;
}

GSF_DYNAMIC_CLASS (GogHistogramSeriesView, gog_histogram_series_view,
	gog_histogram_series_view_class_init, NULL,
	GOG_VIEW_TYPE)

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

static gboolean
drop_lines_can_add (GogObject const *parent)
{
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (parent);
	return (series->droplines == NULL);
}

static void
drop_lines_post_add (GogObject *parent, GogObject *child)
{
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (parent);
	series->droplines = child;
	gog_object_request_update (child);
}

static void
drop_lines_pre_remove (GogObject *parent, GogObject *child)
{
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (parent);
	series->droplines = NULL;
}

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

static GogObjectClass *gog_histogram_plot_series_parent_klass;

static GObjectClass *series_parent_klass;

static void
gog_histogram_plot_series_update (GogObject *obj)
{
	double *x_vals = NULL, *y_vals = NULL, cur;
	int x_len = 1, y_len = 0, i, max;
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (obj);
	unsigned old_num = series->base.num_elements;
	GSList *ptr;

	if (series->base.values[1].data != NULL) {
		y_vals = go_data_vector_get_values (GO_DATA_VECTOR (series->base.values[1].data));
		y_len = go_data_vector_get_len (
			GO_DATA_VECTOR (series->base.values[1].data));
	}
	if (series->base.values[0].data != NULL) {
		x_vals = go_data_vector_get_values (GO_DATA_VECTOR (series->base.values[0].data));
		max = go_data_vector_get_len 
			(GO_DATA_VECTOR (series->base.values[0].data));
		if (max > 0 && go_finite (x_vals[0])) {
			cur = x_vals[0];
			for (i = 1; i< max; i++) {
				if (go_finite (x_vals[i]) && x_vals[i] > cur) {
					cur = x_vals[i];
					x_len++;
				} else
					break;
			}
		}
	} else
		x_len = y_len + 1;
	series->base.num_elements = MIN (x_len - 1, y_len);

	/* update children */
	for (ptr = obj->children; ptr != NULL; ptr = ptr->next)
		if (!IS_GOG_SERIES_LINES (ptr->data))
			gog_object_request_update (GOG_OBJECT (ptr->data));

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

	if (gog_histogram_plot_series_parent_klass->update)
		gog_histogram_plot_series_parent_klass->update (obj);
}

static void
gog_histogram_plot_series_init_style (GogStyledObject *gso, GogStyle *style)
{
	((GogStyledObjectClass*) gog_histogram_plot_series_parent_klass)->init_style (gso, style);

	style->outline.dash_type = GO_LINE_NONE;
}

static void
gog_histogram_plot_series_finalize (GObject *obj)
{
	GogHistogramPlotSeries *series = GOG_HISTOGRAM_PLOT_SERIES (obj);

	g_free (series->y);
	series->y = NULL;
	g_free (series->x);
	series->x = NULL;

	G_OBJECT_CLASS (series_parent_klass)->finalize (obj);
}

static unsigned
gog_histogram_plot_series_get_xy_data (GogSeries const *series,
					double const **x, double const **y)
{
	GogHistogramPlotSeries *hist_ser = GOG_HISTOGRAM_PLOT_SERIES (series);
	*x = hist_ser->x;
	*y = (hist_ser->y)? hist_ser->y:
		go_data_vector_get_values (GO_DATA_VECTOR (series->values[1].data));
	return series->num_elements;
}

static void
gog_histogram_plot_series_class_init (GogObjectClass *obj_klass)
{
	static GogObjectRole const roles[] = {
		{ N_("Drop lines"), "GogSeriesLines", 0,
			GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
			drop_lines_can_add,
			NULL,
			NULL,
			drop_lines_post_add,
			drop_lines_pre_remove,
			NULL },
	};
	GogSeriesClass *series_klass = (GogSeriesClass*) obj_klass;
	GogStyledObjectClass *gso_klass = (GogStyledObjectClass*) obj_klass;
	GogObjectClass *gog_klass = (GogObjectClass *)gso_klass;
	GObjectClass *gobject_klass = (GObjectClass *) gso_klass;

	series_parent_klass = g_type_class_peek_parent (obj_klass);
	gobject_klass->finalize		= gog_histogram_plot_series_finalize;

	gog_histogram_plot_series_parent_klass = g_type_class_peek_parent (obj_klass);
	obj_klass->update = gog_histogram_plot_series_update;
	gog_klass->view_type	= gog_histogram_series_view_get_type ();
	gso_klass->init_style = gog_histogram_plot_series_init_style;

	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));

	series_klass->get_xy_data = gog_histogram_plot_series_get_xy_data;
}

static void
gog_histogram_plot_series_init (GObject *obj)
{
	GogSeries *series = GOG_SERIES (obj);

	series->acceptable_children = GOG_SERIES_ACCEPT_TREND_LINE;
}

GSF_DYNAMIC_CLASS (GogHistogramPlotSeries, gog_histogram_plot_series,
	gog_histogram_plot_series_class_init, gog_histogram_plot_series_init,
	GOG_SERIES_TYPE)


syntax highlighted by Code2HTML, v. 0.9.1