/*
 * Unit tests for the Solver
 */

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

DiaSolver *solver = NULL;
gboolean is_destroyed = FALSE;

void
setup (void)
{
	solver = dia_solver_new ();
	TEST_WEAK_REF (solver, is_destroyed);
	g_assert (solver != NULL);
	is_destroyed = FALSE;
}

void
teardown (void)
{
	if (solver) {
		g_object_unref (solver);
		TEST (is_destroyed == TRUE);
	}
	solver = NULL;
}

TEST_BEGIN (DiaSolver, setup, teardown)

TEST_NEW (dia_solver_new)
{
	TEST (DIA_IS_SOLVER (solver));
	TEST (solver->constraints == NULL);
	TEST (solver->current_con == NULL);
	TEST (solver->marked_cons == NULL);
	TEST (solver->marked_vars == NULL);
}

TEST_NEW (dia_solver_add_constraint)
{
	DiaConstraint *con1 = dia_constraint_new ();
	DiaConstraint *con2 = dia_constraint_new ();
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean con1_destroyed = FALSE;
	gboolean con2_destroyed = FALSE;
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	TEST_WEAK_REF (con1, con1_destroyed);
	TEST_WEAK_REF (con2, con2_destroyed);
	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

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

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

	dia_solver_add_constraint (solver, con1);
	TEST (g_list_length (solver->constraints) == 1);
	TEST (g_slist_length (solver->marked_cons) == 1);
	TEST (g_slist_length (solver->marked_vars) == 2);
	TEST (G_OBJECT (con1)->ref_count == 2);
	TEST (con1->immutable == 1);

	dia_solver_add_constraint (solver, con2);
	TEST (g_list_length (solver->constraints) == 2);
	TEST (g_slist_length (solver->marked_cons) == 2);
	TEST (g_slist_length (solver->marked_vars) == 2);
	TEST (G_OBJECT (con2)->ref_count == 2);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con1);
	g_object_unref (con2);
	TEST (con1_destroyed == FALSE);
	TEST (con2_destroyed == FALSE);
	TEST (var1_destroyed == FALSE);
	TEST (var2_destroyed == FALSE);

	/* Ignore further errors. */
}

TEST_NEW (dia_solver_add_constraint_twice)
{
	DiaConstraint *con = dia_constraint_new ();
	DiaVariable *var = dia_variable_new ();
	gboolean con_destroyed = FALSE;
	gboolean var_destroyed = FALSE;

	TEST_WEAK_REF (con, con_destroyed);
	TEST_WEAK_REF (var, var_destroyed);

	dia_constraint_add (con, var, 1.0);

	dia_solver_add_constraint (solver, con);
	TEST (g_list_length (solver->constraints) == 1);
	TEST (g_slist_length (solver->marked_cons) == 1);
	TEST (g_slist_length (solver->marked_vars) == 1);
	TEST (G_OBJECT (con)->ref_count == 2);
	TEST (con->immutable == 1);

	dia_solver_add_constraint (solver, con);
	TEST (g_list_length (solver->constraints) == 1);
	TEST (g_slist_length (solver->marked_cons) == 1);
	TEST (g_slist_length (solver->marked_vars) == 1);
	TEST (G_OBJECT (con)->ref_count == 2);
	TEST (con->immutable == 1);

	g_object_unref (var);
	g_object_unref (con);
	TEST (con_destroyed == FALSE);
	TEST (var_destroyed == FALSE);
}

TEST_NEW (dia_solver_remove_constraint)
{
	DiaConstraint *con1 = dia_constraint_new ();
	DiaConstraint *con2 = dia_constraint_new ();
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean con1_destroyed = FALSE;
	gboolean con2_destroyed = FALSE;
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	TEST_WEAK_REF (con1, con1_destroyed);
	TEST_WEAK_REF (con2, con2_destroyed);
	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

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

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

	dia_solver_add_constraint (solver, con1);
	dia_solver_add_constraint (solver, con2);

	dia_solver_remove_constraint (solver, con2);
	TEST (g_list_length (solver->constraints) == 1);
	TEST (solver->constraints->data == con1);
	TEST (G_OBJECT (con2)->ref_count == 1);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con1);
	g_object_unref (con2);
	TEST (con1_destroyed == FALSE);
	TEST (con2_destroyed == TRUE);
	TEST (var1_destroyed == FALSE);
	TEST (var2_destroyed == FALSE);

	teardown ();

	TEST (con1_destroyed == TRUE);
	TEST (con2_destroyed == TRUE);
	TEST (var1_destroyed == TRUE);
	TEST (var2_destroyed == TRUE);
}

TEST_NEW (dia_solver_resolve_1)
{
	DiaConstraint *con1 = dia_constraint_new ();
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean con1_destroyed = FALSE;
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

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

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

	dia_solver_add_constraint (solver, con1);

	/* How can we check this doesn't deadlock? */
	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TEST (dia_variable_get_value (var1) == 0.0);
	TEST (dia_variable_get_value (var2) == 0.0);

	dia_variable_set_value (var1, 2.0);
	TEST (g_slist_length (solver->marked_vars) == 1);
	TEST (g_slist_length (solver->marked_cons) == 1);

	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TEST (dia_variable_get_value (var1) == 2.0);
	TEST (dia_variable_get_value (var2) == -1.0);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con1);
}

TEST_NEW (dia_solver_resolve_2)
{
	DiaConstraint *con1 = dia_constraint_new ();
	DiaConstraint *con2 = dia_constraint_new ();
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean con1_destroyed = FALSE;
	gboolean con2_destroyed = FALSE;
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	TEST_WEAK_REF (con1, con1_destroyed);
	TEST_WEAK_REF (con2, con2_destroyed);
	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

	dia_constraint_add (con1, var1, 1.0);
	dia_constraint_add (con1, var2, 1.0);

	dia_constraint_add (con2, var1, 1.0);
	dia_constraint_add (con2, var2, 1.0);
	dia_constraint_add (con2, NULL, 1.0);

	dia_solver_add_constraint (solver, con1);
	dia_solver_add_constraint (solver, con2);

	/* How can we check this doesn't deadlock? */
	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TESTFL (dia_variable_get_value (var1), -1.0);
	TESTFL (dia_variable_get_value (var2), 0.0);

	dia_variable_set_value (var1, 2.0);
	TEST (g_slist_length (solver->marked_cons) == 2);
	TEST (g_slist_length (solver->marked_vars) == 1);

	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TESTFL (dia_variable_get_value (var1), 2.0);
	TESTFL (dia_variable_get_value (var2), -2.0);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con1);
	g_object_unref (con2);
}

TEST_NEW (dia_solver_resolve_3)
{
	DiaConstraint *con1 = dia_constraint_new ();
	DiaConstraint *con2 = dia_constraint_new ();
	DiaVariable *var1 = dia_variable_new ();
	DiaVariable *var2 = dia_variable_new ();
	gboolean con1_destroyed = FALSE;
	gboolean con2_destroyed = FALSE;
	gboolean var1_destroyed = FALSE;
	gboolean var2_destroyed = FALSE;

	dia_variable_set_strength (var1, DIA_STRENGTH_STRONG);

	TEST_WEAK_REF (con1, con1_destroyed);
	TEST_WEAK_REF (con2, con2_destroyed);
	TEST_WEAK_REF (var1, var1_destroyed);
	TEST_WEAK_REF (var2, var2_destroyed);

	dia_constraint_add (con1, var1, 1.0);
	dia_constraint_add (con1, var2, 1.0);

	dia_constraint_add (con2, var1, 1.0);
	dia_constraint_add (con2, var2, 1.0);
	dia_constraint_add (con2, NULL, 1.0);

	dia_solver_add_constraint (solver, con1);
	dia_solver_add_constraint (solver, con2);

	/* How can we check this doesn't deadlock? */
	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TESTFL (dia_variable_get_value (var1), 0.0);
	TESTFL (dia_variable_get_value (var2), -1.0);

	dia_variable_set_value (var1, 2.0);
	TEST (g_slist_length (solver->marked_cons) == 2);
	TEST (g_slist_length (solver->marked_vars) == 1);

	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TESTFL (dia_variable_get_value (var1), 2.0);
	TESTFL (dia_variable_get_value (var2), -2.0);

	dia_variable_set_value (var2, 20.0);
	dia_solver_resolve (solver);
	TEST (solver->marked_vars == NULL);
	TEST (solver->marked_cons == NULL);
	TESTFL (dia_variable_get_value (var1), 2.0);
	TESTFL (dia_variable_get_value (var2), -3.0);

	g_object_unref (var1);
	g_object_unref (var2);
	g_object_unref (con1);
	g_object_unref (con2);
}

TEST_END ()
