/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * go-line.c :
 *
 * Copyright (C) 2004-2006 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 <goffice/goffice.h>

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

/**
 * GOLineDashType
 * @GO_LINE_NONE: No line displayed.
 * @GO_LINE_SOLID: Solid line.
 * @GO_LINE_S_DOT:
 * @GO_LINE_S_DASH_DOT:
 * @GO_LINE_S_DASH_DOT_DOT:
 * @GO_LINE_DASH_DOT_DOT_DOT:
 * @GO_LINE_DOT:
 * @GO_LINE_S_DASH:
 * @GO_LINE_DASH:
 * @GO_LINE_LONG_DASH:
 * @GO_LINE_DASH_DOT:
 * @GO_LINE_DASH_DOT_DOT:
 * @GO_LINE_MAX:
 **/

/**
 * GOLineInterpolation
 * @GO_LINE_INTERPOLATION_LINEAR: Linear interpolation.
 * @GO_LINE_INTERPOLATION_SPLINE: Bezier cubic spline interpolation.
 * @GO_LINE_INTERPOLATION_CLOSED_SPLINE: Closed Bezier cubic spline interpolation.
 * @GO_LINE_INTERPOLATION_CUBIC_SPLINE: Cubic spline interpolation with natural limits.
 * @GO_LINE_INTERPOLATION_PARABOLIC_CUBIC_SPLINE: Cubic spline interpolation with parabolic limits.
 * @GO_LINE_INTERPOLATION_CUBIC_CUBIC_SPLINE: Cubic spline interpolation with cubic limits.
 * @GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE: Cubic spline interpolation with fixed derivatives at both ends.
 * @GO_LINE_INTERPOLATION_STEP_START: Steps using first y value.
 * @GO_LINE_INTERPOLATION_STEP_END: Steps using last y value.
 * @GO_LINE_INTERPOLATION_STEP_CENTER_X: Steps centered around each point.
 * @GO_LINE_INTERPOLATION_STEP_CENTER_Y: Steps using mean y value.
 * @GO_LINE_INTERPOLATION_MAX: First invalid value.
 **/

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

static GOLineDashDesc const line_short_dot_desc = 		{2, 10,	{ 0, 2 } };
static GOLineDashDesc const line_dot_desc = 			{2, 12,	{ 3, 3 } };
static GOLineDashDesc const line_short_dash_desc =		{2, 9,	{ 6, 3 } };
static GOLineDashDesc const line_short_dash_dot_desc =		{4, 12,	{ 6, 3, 0, 3 } };
static GOLineDashDesc const line_short_dash_dot_dot_desc =    	{6, 15,	{ 6, 3, 0, 3, 0, 3 } };
static GOLineDashDesc const line_dash_dot_dot_dot_desc =    	{8, 21,	{ 9, 3, 0, 3, 0, 3, 0, 3 } };
static GOLineDashDesc const line_dash_dot_desc =		{4, 24,	{ 9, 6, 3, 6 } };
static GOLineDashDesc const line_dash_dot_dot_desc =    	{6, 24,	{ 9, 3, 3, 3, 3, 3 } };
static GOLineDashDesc const line_dash_desc =			{2, 16,	{ 12, 4 } };
static GOLineDashDesc const line_long_dash_desc =		{2, 22,	{ 18, 4 } };

static struct {
	GOLineDashType type;
	char const *label;
	char const *name;
	GOLineDashDesc const *dash_desc;
} line_dashes[GO_LINE_MAX] = {
	{ GO_LINE_NONE,			N_("None"),
		"none",			NULL },
	{ GO_LINE_SOLID,		N_("Solid"),
		"solid",		NULL },
	{ GO_LINE_S_DOT,		N_("Dot"),
		"s-dot",		&line_short_dot_desc},
	{ GO_LINE_S_DASH_DOT,		N_("Dash dot"),
		"s-dash-dot",		&line_short_dash_dot_desc },
	{ GO_LINE_S_DASH_DOT_DOT,	N_("Dash dot dot"),
		"s-dash-dot-dot",	&line_short_dash_dot_dot_desc },
	{ GO_LINE_DASH_DOT_DOT_DOT,	N_("Dash dot dot dot"),
		"dash-dot-dot-dot",	&line_dash_dot_dot_dot_desc },
	{ GO_LINE_DOT,			N_("Short dash"),
		"dot",			&line_dot_desc},
	{ GO_LINE_S_DASH,		N_("Dash"),
		"s-dash",		&line_short_dash_desc },
	{ GO_LINE_DASH,			N_("Long dash"),
		"dash",			&line_dash_desc },
	{ GO_LINE_LONG_DASH,		N_("Very long dash"),
		"l-dash",		&line_long_dash_desc },
	{ GO_LINE_DASH_DOT,		N_("Long dash dash"),
		"dash-dot",		&line_dash_dot_desc },
	{ GO_LINE_DASH_DOT_DOT,		N_("Long dash dash dash"),
		"dash-dot-dot",		&line_dash_dot_dot_desc }
};

static struct {
	GOLineInterpolation type;
	char const *label;
	char const *name;
	gboolean supports_radial;
	gboolean auto_skip;
} line_interpolations[GO_LINE_INTERPOLATION_MAX] =
{
	{ GO_LINE_INTERPOLATION_LINEAR,			N_("Linear"),
		"linear",		TRUE,  FALSE },
	{ GO_LINE_INTERPOLATION_SPLINE,			N_("Bezier cubic spline"),
		"spline",		TRUE,  FALSE },
	{ GO_LINE_INTERPOLATION_CLOSED_SPLINE,		N_("Closed Bezier cubic spline"),
		"closed-spline",	TRUE,  TRUE },
	{ GO_LINE_INTERPOLATION_CUBIC_SPLINE,		N_("Natural cubic spline"),
		"cspline",		FALSE, FALSE },
	{ GO_LINE_INTERPOLATION_PARABOLIC_CUBIC_SPLINE,	N_("Cubic spline with parabolic extrapolation"),
		"parabolic-cspline",	FALSE, FALSE },
	{ GO_LINE_INTERPOLATION_CUBIC_CUBIC_SPLINE,	N_("Cubic spline with cubic extrapolation"),
		"cubic-cspline",	FALSE, FALSE },
	{ GO_LINE_INTERPOLATION_CLAMPED_CUBIC_SPLINE,   N_("Clamped cubic spline"),
		"clamped-cspline",	FALSE, TRUE },
	{ GO_LINE_INTERPOLATION_STEP_START,		N_("Step at start"),
		"step-start",		TRUE,  FALSE },
	{ GO_LINE_INTERPOLATION_STEP_END,		N_("Step at end"),
		"step-end",		TRUE,  FALSE },
	{ GO_LINE_INTERPOLATION_STEP_CENTER_X,		N_("Step at center"),
		"step-center-x",	TRUE,  FALSE },
	{ GO_LINE_INTERPOLATION_STEP_CENTER_Y,		N_("Step to mean"),
		"step-center-y",	TRUE,  FALSE }
};

/**
 * go_line_dash_from_str:
 * @name: Name of the dash type
 *
 * Returns: a #GOLineDashType corresponding to name, or %GO_LINE_NONE
 * 	if not found.
 **/
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;
}

/**
 * go_line_dash_as_str:
 * @type: a #GOLineDashType
 *
 * Returns: a pointer to the nickname of the dash type, or "none" if
 * 	type is invalid. The returning string should not be freed.
 **/
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;
}

/**
 * go_line_dash_as_label:
 * @type: a #GOLineDashType
 *
 * Returns: a pointer to the user readable name of the dash type,
 * 	or the name of %GO_LINE_NONE if type is invalid. The returned
 * 	string should not be freed.
 **/
char const *
go_line_dash_as_label (GOLineDashType type)
{
	unsigned i;
	char const *ret = line_dashes[0].label;

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

/**
 * go_line_dash_get_length:
 * @type: #GOLineDashType
 *
 * Returns: the unscaled length of the dash sequence.
 **/
double
go_line_dash_get_length (GOLineDashType type)
{
	GOLineDashDesc const *dash_desc;

	if (type < 0 || type >= G_N_ELEMENTS (line_dashes))
		return 1.0;

	dash_desc = line_dashes[type].dash_desc;
	return dash_desc != NULL ? dash_desc->length : 1.0;
}

/**
 * go_line_dash_get_sequence:
 * @type: a #GOLineDashType
 * @scale: dash scale
 *
 * Returns: a struct containing the dash sequence corresponding to @type,
 * 	or %NULL if type is invalid or equal to %GO_LINE_NONE.
 * 	The lengths are scaled according to @scale.
 **/
GOLineDashSequence *
go_line_dash_get_sequence (GOLineDashType type, double scale)
{
	unsigned int i;
	GOLineDashSequence *sequence = NULL;
	GOLineDashDesc const *dash_desc;

	if (type < 0 || type >= G_N_ELEMENTS (line_dashes))
		return NULL;

	dash_desc = line_dashes[type].dash_desc;
	if (dash_desc != NULL) {
		sequence = g_new (GOLineDashSequence, 1);
		sequence->offset = 0.0;
		sequence->n_dash = dash_desc->n_dash;
		sequence->dash = g_new (double, sequence->n_dash);
		for (i = 0; i < sequence->n_dash; i++)
			sequence->dash[i] = scale * dash_desc->dash[i];
	}

	return sequence;
}

/**
 * go_line_dash_sequence_free:
 * @sequence: a #GOLineDashSequence
 *
 * Frees the dash sequence struct.
 **/
void
go_line_dash_sequence_free (GOLineDashSequence *sequence)
{
	if (sequence != NULL)
		g_free (sequence->dash);
	g_free (sequence);
}

/**
 * go_line_interpolation_from_str:
 * @name: an interpolation type nickname
 *
 * Returns: a #GOLineInterpolation corresponding to @name, or
 * 	%GO_LINE_INTERPOLATION_LINEAR if not found.
 **/
GOLineInterpolation
go_line_interpolation_from_str (char const *name)
{
	unsigned i;
	GOLineInterpolation ret = GO_LINE_INTERPOLATION_LINEAR;

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

/**
 * go_line_interpolation_as_str:
 * @type: an interpolation type
 *
 * Returns: a pointer to the nickname of @type, or "linear" if type
 * 	is invalid. The returned string should not be freed.
 **/
char const *
go_line_interpolation_as_str (GOLineInterpolation type)
{
	unsigned i;
	char const *ret = "linear";

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

/**
 * go_line_interpolation_as_label:
 * @type: an interpolation type
 *
 * Returns: a pointer to the label of @type, or the name of
 * %GO_LINE_INTERPOLATION_LINEAR if type is invalid.
 * The returned string should not be freed.
 **/
char const *
go_line_interpolation_as_label (GOLineInterpolation type)
{
	unsigned i;
	char const *ret = _("Linear");

	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
		if (line_interpolations[i].type == type) {
			ret = _(line_interpolations[i].label);
			break;
		}
	}
	return ret;
}

/**
 * go_line_interpolation_as_str:
 * @type: an interpolation type
 *
 * Returns: TRUE if the line interpolation type can be used with radial
 * axes set, FALSE it it can't.
 **/
gboolean
go_line_interpolation_supports_radial (GOLineInterpolation type)
{
	unsigned i;

	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
		if (line_interpolations[i].type == type) {
			return line_interpolations[i].supports_radial;
		}
	}
	return FALSE;
}

/**
 * go_line_interpolation_as_str:
 * @type: an interpolation type
 *
 * Returns: TRUE if the line interpolation type forces skipping invalid
 * data, FALSE if it is only optional.
 **/
gboolean
go_line_interpolation_auto_skip	(GOLineInterpolation type)
{
	unsigned i;

	for (i = 0; i < G_N_ELEMENTS (line_interpolations); i++) {
		if (line_interpolations[i].type == type) {
			return line_interpolations[i].auto_skip;
		}
	}
	return FALSE;
}

/* ------------------------------------------------------------------------- */

static struct {
	GOArrowType typ;
	char const *name;
} arrow_types[] = {
	{ GO_LINE_NONE, "none" },
	{ GO_ARROW_KITE, "kite" },
	{ GO_ARROW_OVAL, "oval" }
};

char const *
go_arrow_type_as_str (GOArrowType typ)
{
	unsigned ui;

	for (ui = 0; ui < G_N_ELEMENTS (arrow_types); ui++)
		if (typ == arrow_types[ui].typ)
			return arrow_types[ui].name;

	return NULL;
}

GOArrowType
go_arrow_type_from_str (const char *name)
{
	unsigned ui;
	GOArrowType ret = GO_ARROW_NONE;

	for (ui = 0; ui < G_N_ELEMENTS (arrow_types); ui++) {
		if (strcmp (arrow_types[ui].name, name) == 0) {
			ret = arrow_types[ui].typ;
			break;
		}
	}

	return ret;
}

GType
go_arrow_get_type (void)
{
	static GType t = 0;

	if (t == 0) {
		t = g_boxed_type_register_static ("GOArrow",
			 (GBoxedCopyFunc)go_arrow_dup,
			 (GBoxedFreeFunc)g_free);
	}
	return t;
}

void
go_arrow_init (GOArrow *res, GOArrowType typ,
	       double a, double b, double c)
{
	res->typ = typ;
	res->a = a;
	res->b = b;
	res->c = c;
}

void
go_arrow_clear (GOArrow *dst)
{
	go_arrow_init (dst, GO_ARROW_NONE, 0, 0, 0);
}

void
go_arrow_init_kite (GOArrow *dst, double a, double b, double c)
{
	go_arrow_init (dst, GO_ARROW_KITE, a, b, c);
}

void
go_arrow_init_oval (GOArrow *dst, double ra, double rb)
{
	go_arrow_init (dst, GO_ARROW_OVAL, ra, rb, 0);
}

GOArrow *
go_arrow_dup (GOArrow *src)
{
	return g_memdup (src, sizeof (*src));
}
