/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * go-line.c :
 *
 * Copyright (C) 2004 Emmanuel Pacaud (emmanuel.pacaud@univ-poitiers.fr)
 *
 * 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 "go-color.h"
#include "go-line.h"
#include "go-math.h"

#ifdef WITH_GTK
#include <goffice/gtk/go-combo-pixmaps.h>
#endif

#include <string.h>
#include <glib/gi18n.h>

typedef struct {
	int 		 n_dash;
	double 		 dash[6];
} GOLineDashDesc;

static const GOLineDashDesc line_dash_desc =		{2,	{ 18, 6 } };
static const GOLineDashDesc line_dot_desc = 		{2,	{ 3, 3 } };
static const GOLineDashDesc line_dash_dot_desc =	{4, 	{ 9, 3, 6, 3 } };
static const GOLineDashDesc line_dash_dot_dot_desc =  {6, 	{ 9, 3, 3, 3, 3, 3 } };	

struct {
	GOLineDashType type;
	char const *label;
	char const *name;
	const GOLineDashDesc *dash_desc;
} line_dashes[GO_LINE_MAX] = 
{
	{ GO_LINE_NONE,		N_("None"), 		"none",		NULL },
	{ GO_LINE_SOLID,	N_("Solid"),		"solid",	NULL },
	{ GO_LINE_DASH,		N_("Dash"), 		"dash",		&line_dash_desc },
	{ GO_LINE_DOT,		N_("Dot"),		"dot",		&line_dot_desc},
	{ GO_LINE_DASH_DOT,	N_("Dash dot"),		"dash-dot",	&line_dash_dot_desc },
	{ GO_LINE_DASH_DOT_DOT,	N_("Dash dot dot"),	"dash-dot-dot",	&line_dash_dot_dot_desc }
};
	
GOLineDashType
go_line_dash_from_str (char const *name)
{
	unsigned i;
	GOLineDashType ret = GO_LINE_NONE;

	for (i = 0; i < GO_LINE_MAX; i++) {
		if (strcmp (line_dashes[i].name, name) == 0) {
			ret = line_dashes[i].type;
			break;
		}
	}
	return ret;
}

char const *
go_line_dash_as_str (GOLineDashType type)
{
	unsigned i;
	char const *ret = "none";

	for (i = 0; i < GO_LINE_MAX; i++) {
		if (line_dashes[i].type == type) {
			ret = line_dashes[i].name;
			break;
		}
	}
	return ret;
}

void
go_line_vpath_dash_free (ArtVpathDash *dash)
{
	if (dash != NULL)
		g_free (dash->dash);
	g_free (dash);
}

ArtVpathDash *
go_line_get_vpath_dash (GOLineDashType type, double scale)
{
	int i;
	ArtVpathDash *dash = NULL;
	const GOLineDashDesc *dash_desc;

	if (type < 0 || type >= G_N_ELEMENTS (line_dashes))
		return NULL;
	
	dash_desc = line_dashes[type].dash_desc;
	if (dash_desc != NULL) {
		dash = g_new (ArtVpathDash, 1);
		dash->offset = 0.5;
		dash->n_dash = dash_desc->n_dash;
		dash->dash = g_new (double, dash->n_dash);
		for (i = 0; i < dash->n_dash; i++) 
			dash->dash[i] = scale * dash_desc->dash[i];
	}

	return dash;
}

/* Liang-Barsky line clipping */
ArtVpath *
go_line_clip_vpath (ArtVpath const *vpath, GogViewAllocation const *clip_area)
{
	double x1, y1, x2, y2;
	double p[4], q[4], r, t1 = 0., t2 = 1., delta_x, delta_y;
	double x_min, x_max, y_min, y_max;
	unsigned i = 0, j;
	gboolean clip_last, clip_first;
	ArtVpath *result_path;
	int n_result, n_result_max;
	
	x_min = clip_area->x;
	x_max = x_min + clip_area->w;
	y_min = clip_area->y;
	y_max = y_min + clip_area->h;

	n_result = 0;
	n_result_max = 16;
	result_path = art_new (ArtVpath, n_result_max);

	/* TODO clip_first computation isn't needed if previous clip_last was FALSE */
	while (vpath[i].code != ART_END) {
		gboolean reject = FALSE;
		clip_last = TRUE;
		while (vpath[i+1].code == ART_LINETO) {

			t1 = 0.;
			t2 = 1.;

			x1 = vpath[i].x;
			y1 = vpath[i].y;
			x2 = vpath[i+1].x;
			y2 = vpath[i+1].y;

			delta_x = x2 - x1;
			delta_y = y2 - y1;

			p[0] = - delta_x;
			q[0] = x1 - x_min;
			p[1] = delta_x;
			q[1] = x_max - x1;
			p[2] = - delta_y;
			q[2] = y1 - y_min;
			p[3] = delta_y;
			q[3] = y_max - y1;

			clip_last = FALSE;
			clip_first = FALSE;

			for (j = 0; j < 4; j++) {
				if (p[j] < 0.) {
					r =  q[j] / p[j];
					if (r > t1) {
						t1 = r;
						clip_first = TRUE;
					} 
				}
				else if (p[j] > 0.) {
					r =  q[j] / p[j];
					if (r < t2) {
						t2 = r;
						clip_last = TRUE;
					} 
				}
			}

			if (t1 <= t2) {
				reject = FALSE;
				if (clip_first) 
					art_vpath_add_point (&result_path, &n_result, &n_result_max,
							     ART_MOVETO,
							     x1 + t1 * delta_x,
							     y1 + t1 * delta_y);
				else
					art_vpath_add_point (&result_path, &n_result, &n_result_max,
							     vpath[i].code,
							     vpath[i].x,
							     vpath[i].y);
				if (clip_last)
					art_vpath_add_point (&result_path, &n_result, &n_result_max,
							     ART_LINETO,
							     x1 + t2 * delta_x,
							     y1 + t2 * delta_y);
			} else 
				reject = TRUE;		     
			
			i++;
		}
		if (!clip_last && !reject)
			art_vpath_add_point (&result_path, &n_result, &n_result_max,
					     ART_LINETO, vpath[i].x, vpath[i].y);

		i++;
	}
	art_vpath_add_point (&result_path, &n_result, &n_result_max,
			     ART_END, 0., 0.);

	return result_path;
}

ArtVpath *
go_line_dash_vpath (ArtVpath const *path, 
		    ArtVpathDash const *dash,
		    GogViewAllocation const *bbox)
{
	ArtVpath *dashed;

	if (dash == NULL) 
		return NULL;

	if (bbox != NULL) {
		ArtVpath *clipped = go_line_clip_vpath (path, bbox);
		dashed = art_vpath_dash (clipped, dash);
		g_free (clipped);
	} else
		dashed = art_vpath_dash (path, dash);

	return dashed;
}
					 
#ifdef WITH_GTK

#define LINE_SAMPLE_HEIGHT	5
#define LINE_SAMPLE_WIDTH	60

gpointer	 
go_line_dash_selector (GOLineDashType default_type)
{
	static GOLineDashType const elements[] = {
		GO_LINE_NONE,
		GO_LINE_SOLID,
		GO_LINE_DASH,
		GO_LINE_DOT,
		GO_LINE_DASH_DOT,
		GO_LINE_DASH_DOT_DOT,
		GO_LINE_MAX
	};

	unsigned	 i;
	gboolean	 is_auto;
	GOComboPixmaps	*w;
	GdkPixbuf	*pixbuf;
	ArtVpathDash	*dash;
	GOLineDashType	 dash_type;
	ArtVpath	 line[3], *path;
	ArtSVP		*svp;
	GogViewAllocation bbox;

	bbox.x = 0;
	bbox.y = 0;
	bbox.w = LINE_SAMPLE_WIDTH;
	bbox.h = LINE_SAMPLE_HEIGHT;

	line[0].code = ART_MOVETO;
	line[1].code = ART_LINETO;
	line[2].code = ART_END;
	line[0].x = 0.5;
	line[1].x = LINE_SAMPLE_WIDTH - 0.5;
	line[0].y = line[1].y = LINE_SAMPLE_HEIGHT / 2.0;

	w = go_combo_pixmaps_new (1);
	for (i = 0; i < G_N_ELEMENTS (elements); i++) {
		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 
					 LINE_SAMPLE_WIDTH, LINE_SAMPLE_HEIGHT);
		gdk_pixbuf_fill (pixbuf, 0); /* in case the fill colours have alpha = 0 */
		is_auto = elements[i] == GO_LINE_MAX;
		dash_type = is_auto ? default_type : i;
		if (dash_type != GO_LINE_NONE) {
			dash = go_line_get_vpath_dash (dash_type, 1.0);
			path = dash != NULL ? art_vpath_dash (line, dash) : line;
			svp = art_svp_vpath_stroke (path,
						    ART_PATH_STROKE_JOIN_MITER, ART_PATH_STROKE_CAP_ROUND,
						    1.0, 4.0, 0.5);
			if (dash != NULL) {
				go_line_vpath_dash_free (dash);
				g_free (path);
			}
			go_color_render_svp (0x000000FF, svp, 0, 0, LINE_SAMPLE_WIDTH, LINE_SAMPLE_HEIGHT,
					     gdk_pixbuf_get_pixels (pixbuf),
					     gdk_pixbuf_get_rowstride (pixbuf));
			art_svp_free (svp);
		}
		if (is_auto) {
			/* xgettext : this will appear as 'Automatic (patternname)' */
			char *name = g_strdup_printf (_("Automatic (%s)"),
						      _(line_dashes [default_type].label));
			go_combo_pixmaps_add_element (w, pixbuf, -default_type, name);
			g_free (name);
		} else
			go_combo_pixmaps_add_element (w, pixbuf, dash_type,
						      _(line_dashes[dash_type].label));
	}
	return w;
}

ArtBpath*
go_line_build_bpath (double const *x, double const *y, int n)
{
	ArtBpath *path;
	int i, j, start, cur, nb;
	double *lengths, *angles, curx, cury, t, t0, a0, a1;

	g_return_val_if_fail (n > 0, NULL);

	path = art_new (ArtBpath, n + 1);
	lengths = g_new (double, n - 1);
	angles = g_new (double, n - 1);

	j = -1;
	for (start = nb = cur = i = 0; i <= n; i++) {
		if ((i == n) || isnan (x[i]) || !go_finite (x[i]) || (fabs (x[i]) == DBL_MAX) ||
			isnan (y[i]) || !go_finite (y[i]) || (fabs (y[i]) == DBL_MAX)) {
			/* invalid or infinite points interrupt the curve; DBL_MAX is also filtered
			because this value is returned when mapping a negative value to a logarithmic
			axis. */
			switch (nb) {
			case 0: /* invalid point: don't draw anything */
				break;
			case 1: /* isolated point: don't draw anything */
				j--;
				break;
			case 2: /* two points: draw a linear segment*/
				path[start++].code = ART_MOVETO_OPEN;
				path[start++].code = ART_LINETO;
				cur = start;
				break;
			default: /* draw a bezier spline */
				path[start].code = ART_MOVETO_OPEN;
				while (start < j) {
					curx = path[start + 1].x3 - path[start].x3;
					cury = path[start + 1].y3 - path[start].y3;
					lengths[start] = sqrt (curx * curx + cury * cury) / 4.;
					angles[start] = atan2 (cury, curx);
					start++ ;
					path[start].code = ART_CURVETO;
				}
				start++;
				a0 = angles[cur];
				a1 = angles[cur + 1];
				if (fabs (a1 - a0) > M_PI)
					a1 -= (a1 > a0)? 2. * M_PI: -2. * M_PI;
				t = (a0 * lengths[cur + 1] + a1 * lengths[cur])
					/ (lengths[cur] + lengths[cur + 1]);
				t0 = (a0 * 3. - t) / 2.;
				cur++;
				path[cur].x1 = path[cur - 1].x3 + lengths[cur - 1] * cos (t0);
				path[cur].y1 = path[cur - 1].y3 + lengths[cur - 1] * sin (t0);
				path[cur].x2 = path[cur].x3 - lengths[cur - 1] * cos (t);
				path[cur].y2 = path[cur].y3 - lengths[cur - 1] * sin (t);
				cur++;
				t0 = t;
				a0 = a1;
				while (cur < j) {
					a1 = angles[cur];
					if (fabs (a1 - a0) > M_PI)
						a1 -= (a1 > a0)? 2. * M_PI: -2. * M_PI;
					t = (a0 * lengths[cur] + a1 * lengths[cur - 1])
						/ (lengths[cur] + lengths[cur - 1]);
					path[cur].x1 = path[cur - 1].x3 + lengths[cur - 1] * cos (t0);
					path[cur].y1 = path[cur - 1].y3 + lengths[cur - 1] * sin (t0);
					path[cur].x2 = path[cur].x3 - lengths[cur - 1] * cos (t);
					path[cur].y2 = path[cur].y3 - lengths[cur - 1] * sin (t);
					t0 = t;
					a0 = a1;
					cur++;
				}
				path[cur].x1 = path[cur - 1].x3 + lengths[cur - 1] * cos (t0);
				path[cur].y1 = path[cur - 1].y3 + lengths[cur - 1] * sin (t0);
				t = (a0 * 3. - t0) / 2.;
				path[cur].x2 = path[cur].x3 - lengths[cur - 1] * cos (t);
				path[cur].y2 = path[cur].y3 - lengths[cur - 1] * sin (t);
				cur++;
			}
			nb = 0;
		} else if (!nb || ((path[j].x3 != x[i]) || (path[j].y3 != y[i]))) {
			j++;
			path[j].x3 = x[i];
			path[j].y3 = y[i];
			nb++;
		}
	}

	path[start].code = ART_END;
	g_free (lengths);
	g_free (angles);
	return path;
}

ArtVpath*
go_line_build_vpath (double const *x, double const *y, int n)
{
	ArtVpath *path;
	int i, j, start, nb;

	g_return_val_if_fail (n > 0, NULL);

	path = art_new (ArtVpath, n + 1);

	j = -1;
	for (start = nb = i = 0; i <= n; i++) {
		if ((i == n) || isnan (x[i]) || !go_finite (x[i]) || (fabs (x[i]) == DBL_MAX) ||
			isnan (y[i]) || !go_finite (y[i]) || (fabs (y[i]) == DBL_MAX)) {
			/* invalid or infinite points interrupt the path; DBL_MAX is also filtered
			because this value is returned when mapping a negative value to a logarithmic
			axis. */
			switch (nb) {
			case 0: /* invalid point: don't draw anything */
				break;
			case 1: /* isolated point: don't draw anything */
				j--;
				break;
			default: 
				path[start].code = ART_MOVETO_OPEN;
				while (start < j) {
					start++ ;
					path[start].code = ART_LINETO;
				}
				start++;
			}
			nb = 0;
		} else if (!nb || ((path[j].x != x[i]) || (path[j].y != y[i]))) {
			j++;
			path[j].x = x[i];
			path[j].y = y[i];
			nb++;
		}
	}

	path[start].code = ART_END;
	return path;
}

#endif /* WITH_GTK */
