/*
 * Unit tests for variables
 */

#include <diacanvas/dia-constraint.h>
#include "unit-test.h"

DiaConstraint *con = NULL;

gboolean is_destroyed;

void
need_resolve_cb (DiaConstraint *con, DiaVariable *var, gboolean *notify)
{
	*notify = TRUE;
}

void
setup (void)
{
	con = dia_constraint_new ();
	g_assert (con != NULL);
	TEST_WEAK_REF (con, is_destroyed);
	is_destroyed = FALSE;
}

void
teardown (void)
{
	/* This will allow individual tests to destroy @con if nessesary. */
	if (con) {
		g_object_unref (con);
		TEST (is_destroyed == TRUE)
	}
	con = NULL;
}

TEST_BEGIN (DiaConstraint, setup, teardown)

TEST_NEW (dia_constraint_new)
{
	TEST (DIA_IS_CONSTRAINT (con));
	TEST (con->immutable == 0);
	TEST (con->expr == NULL);
	TEST (dia_constraint_has_variables (con) == FALSE);
}

TEST_NEW (dia_constraint_add)
{
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

	dia_constraint_add (con, var1, 3.0);
	TEST (con->expr != NULL);
	TEST (con->expr->len == 1);
	TEST (con->expr->elem[0].variable == var1);
	TEST (con->expr->elem[0].constant == 3.0);
	TEST (dia_constraint_has_variables (con) == TRUE);

	dia_constraint_add (con, var2, 4.0);
	TEST (con->expr != NULL);
	TEST (con->expr->len == 2);
	TEST (con->expr->elem[0].variable == var1);
	TEST (con->expr->elem[0].constant == 3.0);
	TEST (con->expr->elem[1].variable == var2);
	TEST (con->expr->elem[1].constant == 4.0);
	TEST (dia_constraint_has_variables (con) == TRUE);

	dia_constraint_add (con, NULL, 5.0);
	TEST (con->expr != NULL);
	TEST (con->expr->len == 3);
	TEST (con->expr->elem[0].variable == var1);
	TEST (con->expr->elem[0].constant == 3.0);
	TEST (con->expr->elem[1].variable == var2);
	TEST (con->expr->elem[1].constant == 4.0);
	TEST (con->expr->elem[2].variable == NULL);
	TEST (con->expr->elem[2].constant == 5.0);
	TEST (dia_constraint_has_variables (con) == TRUE);
	
	g_object_unref (var1);
	g_object_unref (var2);
	TEST (var1_destroyed == FALSE);
	TEST (var2_destroyed == FALSE);

	g_object_unref (con);
	TEST (var1_destroyed == TRUE);
	TEST (var2_destroyed == TRUE);
	TEST (is_destroyed == TRUE);
 	con = NULL;
}

TEST_NEW (dia_constraint_add_expression)
{
	DiaExpression *expr = NULL;
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

	dia_expression_add (&expr, var1, 1.0);
	dia_expression_add (&expr, var2, 1.0);

	dia_constraint_add (con, var1, 3.0);
	dia_constraint_add_expression (con, expr);
	TEST (con->expr->len == 3);
	TEST (G_OBJECT (var1)->ref_count == 4);
	TEST (G_OBJECT (var2)->ref_count == 3);

	dia_expression_free (expr);
	TEST (G_OBJECT (var1)->ref_count == 3);
	TEST (G_OBJECT (var2)->ref_count == 2);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con);
	TEST (var1_destroyed == TRUE);
	TEST (var2_destroyed == TRUE);
	TEST (is_destroyed == TRUE);
	con = NULL;
}


TEST_NEW (dia_constraint_times)
{
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();

	dia_constraint_add (con, var1, 1.0);
	dia_constraint_add (con, var2, 2.0);

	dia_constraint_times (con, 3.0);
	TEST (con->expr->elem[0].constant == 3.0);
	TEST (con->expr->elem[1].constant == 6.0);

	g_object_unref (var1);
	g_object_unref (var2);
}

TEST_NEW (dia_constraint_solve_1)
{
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();

	dia_constraint_add (con, var1, 1.0);
	dia_constraint_add (con, var2, 2.0);

	dia_variable_set_value (var1, 2.0);
	/* var1(2.0) * 1.0 + var2 * 2.0 = 0 ==> var2 = -(var1(2.0)) / 2.0 */
	TEST (dia_constraint_solve (con, var2) == -1.0);

	dia_variable_set_value (var2, 2.0);
	TEST (dia_constraint_solve (con, var1) == -4.0);

	g_object_unref (var1);
	g_object_unref (var2);
}

TEST_NEW (dia_constraint_solve_2)
{
	DiaVariable *var = dia_variable_new ();

	dia_constraint_add (con, var, 1.0);
	dia_constraint_add (con, NULL, 10.0);

	TEST (dia_constraint_solve (con, var) == -10.0);

	g_object_unref (var);
}

TEST_NEW (dia_constraint_optimize)
{
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	DiaVariable *var3 = dia_variable_new ();
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;
	gboolean var3_destroyed = FALSE;

	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);
	TEST_WEAK_REF (var3, var3_destroyed);

	dia_constraint_add (con, var1, 3.0);
	dia_constraint_add (con, var2, 2.0);
	dia_constraint_add (con, var3, 1.0);
	dia_constraint_add (con, var2, 4.0);
	dia_constraint_add (con, var1, 1.0);
	dia_constraint_add (con, var3, 1.0);

	TEST (con->expr->len == 6);
	TEST (G_OBJECT (var1)->ref_count == 3);
	TEST (G_OBJECT (var2)->ref_count == 3);
	TEST (G_OBJECT (var3)->ref_count == 3);

	dia_constraint_optimize (con);
	TEST (con->expr->len == 3);
	TEST (G_OBJECT (var1)->ref_count == 2);
	TEST (G_OBJECT (var2)->ref_count == 2);
	TEST (G_OBJECT (var3)->ref_count == 2);

	TEST (con->expr->elem[0].variable == var1);
	TEST (con->expr->elem[0].constant == 4.0);
	TEST (con->expr->elem[1].variable == var2);
	TEST (con->expr->elem[1].constant == 6.0);
	TEST (con->expr->elem[2].variable == var3);
	TEST (con->expr->elem[2].constant == 2.0);
	
	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (var3);

	teardown ();

	TEST (var1_destroyed == TRUE);
	TEST (var2_destroyed == TRUE);
	TEST (var3_destroyed == TRUE);
}

TEST_NEW (dia_constraint_freeze)
{
	DiaExpression *expr = NULL;
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();

	dia_expression_add (&expr, var1, 1.0);
	dia_expression_add (&expr, var2, 1.0);

	dia_constraint_add (con, var1, 3.0);
	TEST (con->expr->len == 1);
	TEST (G_OBJECT (var1)->ref_count == 3);
	TEST (G_OBJECT (var2)->ref_count == 2);
	
	dia_constraint_freeze (con);

	dia_constraint_add_expression (con, expr);
	TEST (con->expr->len == 1);
	TEST (G_OBJECT (var1)->ref_count == 3);
	TEST (G_OBJECT (var2)->ref_count == 2);

	dia_constraint_thaw (con);

	dia_constraint_add_expression (con, expr);
	TEST (con->expr->len == 3);
	TEST (G_OBJECT (var1)->ref_count == 4);
	TEST (G_OBJECT (var2)->ref_count == 3);

	dia_expression_free (expr);
	g_object_unref (var1);
	g_object_unref (var2);
}

TEST_NEW (signal_need_resolve)
{
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean need_resolve_called = FALSE;
	
	g_signal_connect (con, "need_resolve", G_CALLBACK (need_resolve_cb),
			  &need_resolve_called);

	dia_constraint_add (con, var1, 1.0);
	dia_constraint_add (con, var2, 2.0);

	dia_variable_set_value (var1, 2.0);
	TEST (dia_constraint_solve (con, var2) == -1.0);
	TEST (need_resolve_called == TRUE);
	need_resolve_called = FALSE;

	dia_variable_set_value (var2, 2.0);
	TEST (dia_constraint_solve (con, var1) == -4.0);
	TEST (need_resolve_called == TRUE);
	need_resolve_called = FALSE;

	g_object_unref (var1);
	g_object_unref (var2);
}

TEST_END ()
