/* dia-constraint.c
 * Copyright (C) 2001  Arjan Molenaar
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "dia-constraint.h"
#include "diamarshal.h"
#include "dia-canvas-i18n.h"

///#define D(msg) G_STMT_START { g_print (G_STRLOC": "); g_print msg; g_print ("\n"); } G_STMT_END
#define D(msg)

enum
{
	NEED_RESOLVE,
	LAST_SIGNAL
};



static void dia_constraint_init		(DiaConstraint		*constraint);
static void dia_constraint_class_init	(DiaConstraintClass	*klass);
static void dia_constraint_set_property	(GObject		*object,
					 guint			 property_id,
					 const GValue		*value,
					 GParamSpec		*pspec);
static void dia_constraint_get_property	(GObject		*object,
					 guint			 property_id,
					 GValue			*value,
					 GParamSpec		*pspec);

static void dia_constraint_finalize   	(GObject		*object);

static void changed_internal_cb		(DiaVariable		*var,
					 DiaConstraint		*con);

static GObjectClass *parent_class = NULL;
static guint signals[LAST_SIGNAL] = { 0 };

GType
dia_constraint_get_type (void)
{
	static GType object_type = 0;

	if (!object_type) {
		static const GTypeInfo object_info = {
			sizeof (DiaConstraintClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) dia_constraint_class_init,
			(GClassFinalizeFunc) NULL,
			(gconstpointer) NULL, /* class_data */
			sizeof (DiaConstraint),
			(guint16) 0, /* n_preallocs */
			(GInstanceInitFunc) dia_constraint_init,
		};
		object_type = g_type_register_static (G_TYPE_OBJECT,
						      "DiaConstraint",
						      &object_info,
						      0);
	}

	return object_type;
}


static void
dia_constraint_class_init (DiaConstraintClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass*) klass;

	parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = dia_constraint_finalize;
	object_class->set_property = dia_constraint_set_property;
	object_class->get_property = dia_constraint_get_property;

	signals[NEED_RESOLVE] = 
	  g_signal_new ("need_resolve",
			G_TYPE_FROM_CLASS (klass),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DiaConstraintClass, need_resolve),
			NULL, NULL,
			dia_marshal_VOID__OBJECT,
			G_TYPE_NONE, 1, DIA_TYPE_VARIABLE);
}


static void
dia_constraint_init (DiaConstraint *constraint)
{
	constraint->immutable = 0;
	constraint->expr = NULL;
}

static void
dia_constraint_finalize (GObject *object)
{
	DiaConstraint *con = (DiaConstraint*) object;
	guint i;

	if (con->expr) {
		for (i = 0; i < con->expr->len; i++)
			if (con->expr->elem[i].variable)
				g_signal_handlers_disconnect_by_func
					(con->expr->elem[i].variable,
					 G_CALLBACK (changed_internal_cb),
					 con);

		dia_expression_free (con->expr);
	}

	parent_class->finalize (object);
}

static void
dia_constraint_set_property (GObject *object, guint property_id,
			     const GValue *value, GParamSpec *pspec)
{
	//DiaConstraint *var = DIA_CONSTRAINT (object);

	switch (property_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
dia_constraint_get_property (GObject *object, guint property_id,
			     GValue *value, GParamSpec *pspec)
{
	//DiaConstraint *var = DIA_CONSTRAINT (object);

	switch (property_id) {
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
changed_internal_cb (DiaVariable *var, DiaConstraint *con)
{
	g_signal_emit (con, signals[NEED_RESOLVE], 0, var);
}

/**
 * dia_constraint_new:
 *
 * Create an empty constraint.
 *
 * Return value: A new constraint object.
 **/
DiaConstraint*
dia_constraint_new (void)
{
	return g_object_new (DIA_TYPE_CONSTRAINT, NULL);
}

/**
 * dia_constraint_add:
 * @constraint: 
 * @var: 
 * @c: 
 *
 * Add a @var * @c pair to the constraint.
 *
 * @constraint = @constraint + @var * @c.
 **/
void
dia_constraint_add (DiaConstraint *constraint, DiaVariable *var, gdouble c)
{
	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));
	g_return_if_fail ((var == NULL) || DIA_IS_VARIABLE (var));
	g_return_if_fail (constraint->immutable == 0);

	dia_expression_add (&constraint->expr, var, c);

	if (var)
		g_signal_connect (var, "changed_internal",
				  G_CALLBACK (changed_internal_cb), constraint);
}

/**
 * dia_constraint_add_expression:
 * @constraint: 
 * @expr: 
 *
 * Add a #DiaExpression to @constraint. A #DiaExpression containts one or more
 * variable-constant pairs.
 **/
void
dia_constraint_add_expression (DiaConstraint *constraint, DiaExpression *expr)
{
	guint i;

	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));
	g_return_if_fail (expr != NULL);
	g_return_if_fail (constraint->immutable == 0);

	dia_expression_add_expression (&constraint->expr, expr);

	for (i = 0; i < expr->len; i++)
		if (expr->elem[i].variable)
			g_signal_connect (expr->elem[i].variable,
					  "changed_internal",
					  G_CALLBACK (changed_internal_cb),
					  constraint);
}

/**
 * dia_constraint_times:
 * @constraint: 
 * @c: 
 *
 * Multiply all constants in the constraint with @c.
 *
 * @constraint = (@constraint) * @c.
 **/
void
dia_constraint_times (DiaConstraint *constraint, gdouble c)
{
	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));
	g_return_if_fail (constraint->immutable == 0);

	dia_expression_times (constraint->expr, c);
}

/**
 * dia_constraint_has_variables:
 * @constraint: 
 *
 * Determine if @constraint has any variables in its equation.
 *
 * Return value: TRUE if the constraint contains variables, FALSE otherwise.
 **/
gboolean
dia_constraint_has_variables (DiaConstraint *constraint)
{
	guint i;
	DiaExpression *expr;

	g_return_val_if_fail (DIA_IS_CONSTRAINT (constraint), FALSE);
	
	expr = constraint->expr;
	if (expr) {
		for (i = 0;  i < expr->len; i++) {
			if (expr->elem[i].variable
			    && DIA_IS_VARIABLE (expr->elem[i].variable))
				return TRUE;
		}
	}
	return FALSE;
}

/**
 * dia_constraint_optimize:
 * @constraint: 
 *
 * Optimeze the constraint. This is done by merging all variable-constant pairs
 * whose variable is the same and removing all pairs with a constant of 0.0.
 **/
void
dia_constraint_optimize (DiaConstraint *constraint)
{
	DiaExpression *expr;
	guint i;
	guint len;

	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));

	expr = constraint->expr;
	len = expr->len;

	/* Unite elements with the same variable. */
	for (i = 0; i < len; i++) {
		guint k;

		for (k = i + 1; k < expr->len; k++) {
			if (expr->elem[k].variable == expr->elem[i].variable) {
				expr->elem[i].constant += expr->elem[k].constant;
				expr->elem[k].constant = 0.0;
				if (expr->elem[k].variable) {
					g_object_unref (expr->elem[k].variable);
					expr->elem[k].variable = NULL;
				}
			}
		}
	}

	/* Remove elements whose constant == 0.0. */
	for (i = 0; i < len; i++) {
		if (expr->elem[i].constant == 0.0) {
			guint k = i;
			
			/* Skip all elements with constant == 0.0. We decrement
			 * expr->len also. */
			while ((i < len) && (expr->elem[i].constant == 0.0)) {
				i++;
				expr->len--;
			}
			if (i < len) {
				expr->elem[k].constant = expr->elem[i].constant;
				expr->elem[k].variable = expr->elem[i].variable;
				expr->elem[i].constant = 0.0;
				expr->elem[i].variable = NULL;
			}
		}
	}
}

/**
 * dia_constraint_solve:
 * @constraint: 
 * @var: 
 *
 * Solve a constraint with @var being the variable that should be changed.
 *
 * Return value: G_MAXDOUBLE on error, otherwise the value that @var should
 * 		 be assigned too.
 **/
gdouble
dia_constraint_solve (DiaConstraint *constraint, DiaVariable *var)
{
	gdouble sum = 0.0;
	gdouble cvar = 0.0; /* constant belonging to @var. */
	DiaExpression *expr;
	guint i;

	g_return_val_if_fail (DIA_IS_CONSTRAINT (constraint), G_MAXDOUBLE);
	g_return_val_if_fail (DIA_IS_VARIABLE (var), G_MAXDOUBLE);

	expr = constraint->expr;

	for (i = 0; i < expr->len; i++) {
		if (expr->elem[i].variable == var) {
			cvar += expr->elem[i].constant;
		} else {
			if (expr->elem[i].variable)
				sum += expr->elem[i].constant * dia_variable_get_value (expr->elem[i].variable);
			else
				sum += expr->elem[i].constant;
		}
	}
	if (cvar == 0.0)
		return G_MAXDOUBLE;

	return -sum / cvar;
}

/**
 * dia_constraint_freeze:
 * @constraint: 
 *
 * Make the constraint immutable. As a result no more variable-constant pairs
 * can be added. This function is typically used by the #DiaSolver to make
 * sure a constraint does not change after it has been added to the solver.
 **/
void
dia_constraint_freeze (DiaConstraint *constraint)
{
	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));

	constraint->immutable++;
}

/**
 * dia_constraint_thaw:
 * @constraint: 
 *
 * Inverse function of dia_constraint_freeze().
 **/
void
dia_constraint_thaw (DiaConstraint *constraint)
{
	g_return_if_fail (DIA_IS_CONSTRAINT (constraint));

	if (constraint->immutable > 0)
		constraint->immutable--;
}

/**
 * dia_constraint_foreach:
 * @constraint: 
 * @func: 
 * @user_data: 
 *
 * Call @func for every variable-constant pair in the constraint.
 **/
void
dia_constraint_foreach (DiaConstraint *constraint, DiaConstraintFunc func,
			gpointer user_data)
{
	guint i;

	for (i = 0; i < constraint->expr->len; i++) {
		func (constraint, constraint->expr->elem[i].variable,
		      constraint->expr->elem[i].constant, user_data);
	}
}

/**
 * dia_expression_add:
 * @expr: 
 * @var: 
 * @c: 
 *
 * Add "@c * @var" to @expr (@expr = @expr + (@c * @var)).
 **/
void
dia_expression_add (DiaExpression **expr, DiaVariable *var, gdouble c)
{
	DiaExpression e;
	e.len = 1;
	e.elem[0].variable = var;
	e.elem[0].constant = c;

	dia_expression_add_expression (expr, &e);
}

/**
 * dia_expression_add_expression:
 * @expr: 
 * @expr2: 
 *
 * Add @expr2 to @expr (@expr = @expr + @expr2).
 **/
void
dia_expression_add_expression (DiaExpression **expr, DiaExpression *expr2)
{
	guint i;
	
	if (*expr == NULL) {
		*expr = g_new (DiaExpression, expr2->len);
		(*expr)->len = 0;
	} else {
		
		*expr = g_realloc (*expr, sizeof (DiaExpression)
					  + (sizeof ((*expr)->elem[0]) * ((*expr)->len + expr2->len - 1)));
	}

	for (i = 0; i < expr2->len; i++) {
		(*expr)->elem[(*expr)->len].variable = expr2->elem[i].variable;
		if (expr2->elem[i].variable)
			g_object_ref (expr2->elem[i].variable);
		(*expr)->elem[(*expr)->len].constant = expr2->elem[i].constant;
		(*expr)->len++;
	}
}

/**
 * dia_expression_times:
 * @expr: 
 * @c: 
 *
 * Multiply @expr with value @c (@expr = @expr * c).
 **/
void
dia_expression_times (DiaExpression *expr, gdouble c)
{
	guint i;

	for (i = 0; i < expr->len; i++) {
		expr->elem[i].constant *= c;
	}
}

/**
 * dia_expression_free:
 * @expr: 
 *
 * Free an expression.
 **/
void
dia_expression_free (DiaExpression *expr)
{
	guint i;

	for (i = 0; i < expr->len; i++) {
		if (expr->elem[i].variable)
			g_object_unref (expr->elem[i].variable);
	}
	g_free (expr);
}

