/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2001-2002 CodeFactory AB
 * Copyright (C) 2001-2002 Richard Hult <rhult@codefactory.se>
 * Copyright (C) 2001-2002 Mikael Hallendal <micke@codefactory.se>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU 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 <config.h>
#include <string.h>
#include "mrp-marshal.h"
#include "mrp-intl.h"
#include "mrp-private.h"
#include "mrp-resource.h"
#include "mrp-error.h"

/* Properties */
enum {
	PROP_0,
	PROP_NAME,
	PROP_START,
	PROP_FINISH,
	PROP_LATEST_START,
	PROP_LATEST_FINISH,
	PROP_DURATION,
	PROP_WORK,
	PROP_CRITICAL,
	PROP_TYPE,
	PROP_SCHED,
	PROP_CONSTRAINT,
	PROP_NOTE,
	PROP_PERCENT_COMPLETE
};

/* Signals */
enum {
	RELATION_ADDED,
	RELATION_REMOVED,
	TASK_MOVED,
	ASSIGNMENT_ADDED,
	ASSIGNMENT_REMOVED,
	CHILD_ADDED,
	CHILD_REMOVED,
	LAST_SIGNAL
};

struct _MrpTaskPriv {
	guint             critical : 1;

	/* Used for topological order sorting. */
	guint             visited : 1;
	GNode            *sorted_node;

	/* FIXME: This might be a mistake... I can't think of any other types,
	 * besides milestone and normal. Should we have a boolean instead,
	 * is_milestone? That or a flags type variable.
	 */
	MrpTaskType       type;

	/* FIXME: See above, could use a flags type and keep this info there as
	 * well.
	 */
	MrpTaskSched      sched;
	
	/* Percent complete, 0-100. */
	gshort            percent_complete;
	
	gchar            *name;
	gchar            *note;

	/* The amount of work effort (duration*units) necessary to complete this
	 * task.
	 */
	gint              work;

	/* Calculated duration. */
	gint              duration;

	/* Slack. */
	gint              slack;
	
	/* Calculated start and finish values. */
	mrptime           start;
	mrptime           finish;

	/* The time of the first work performed on the task. Needed so clients
	 * know when to start drawing if they don't want to draw from the start
	 * if the start is during non-work time.
	 */
	mrptime           work_start;

	/* Represents the Work Breakdown Structure tree hierarchy. */
	GNode            *node;

	/* The acyclic dependency graph. */
	GList            *successors;
	GList            *predecessors;
	
	/* Calculated CPM values. */
	mrptime           latest_start;
	mrptime           latest_finish;

	MrpConstraint     constraint;

	/* List of assignments. */
	GList            *assignments;
};


static void task_init                  (MrpTask            *module);
static void task_class_init            (MrpTaskClass       *class);
static void task_finalize              (GObject            *object);
static void task_set_property          (GObject            *object,
					guint               prop_id,
					const GValue       *value,
					GParamSpec         *pspec);
static void task_get_property          (GObject            *object,
					guint               prop_id,
					GValue             *value,
					GParamSpec         *pspec);
static void task_removed               (MrpObject          *object);
static void task_assignment_removed_cb (MrpAssignment      *assignment,
					MrpTask            *task);

static void task_remove_assignments    (MrpTask            *task);
static void task_remove_relations      (MrpTask            *task);


static MrpObjectClass *parent_class;
static guint signals[LAST_SIGNAL];


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

	if (!object_type) {
		static const GTypeInfo object_info = {
			sizeof (MrpTaskClass),
			NULL,		/* base_init */
			NULL,		/* base_finalize */
			(GClassInitFunc) task_class_init,
			NULL,		/* class_finalize */
			NULL,		/* class_data */
			sizeof (MrpTask),
			0,              /* n_preallocs */
			(GInstanceInitFunc) task_init,
		};

		object_type = g_type_register_static (MRP_TYPE_OBJECT,
						      "MrpTask",
						      &object_info, 0);
	}

	return object_type;
}

static void
task_init (MrpTask *task)
{
	MrpTaskPriv *priv;
	
	priv = g_new0 (MrpTaskPriv, 1);
	task->priv = priv;

	priv->name = g_strdup ("");
	priv->node = g_node_new (task);
	priv->sorted_node = g_node_new (task);
	priv->assignments = NULL;
	priv->constraint.type = MRP_CONSTRAINT_ASAP;
}

static void
task_class_init (MrpTaskClass *klass)
{
	GObjectClass   *object_class     = G_OBJECT_CLASS (klass);
	MrpObjectClass *mrp_object_class = MRP_OBJECT_CLASS (klass);
	
	parent_class = MRP_OBJECT_CLASS (g_type_class_peek_parent (klass));

	object_class->finalize = task_finalize;
	object_class->set_property = task_set_property;
	object_class->get_property = task_get_property;

	mrp_object_class->removed  = task_removed;
	
	signals[TASK_MOVED] =
		g_signal_new ("task_moved",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__OBJECT_INT,
			      G_TYPE_NONE, 2, MRP_TYPE_TASK, G_TYPE_INT);
	
	signals[RELATION_ADDED] =
		g_signal_new ("relation_added",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__POINTER,
			      G_TYPE_NONE, 1, G_TYPE_POINTER);
	
	signals[RELATION_REMOVED] =
		g_signal_new ("relation_removed",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__POINTER,
			      G_TYPE_NONE, 1, G_TYPE_POINTER);

	signals[ASSIGNMENT_ADDED] = 
		g_signal_new ("assignment_added",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__OBJECT,
			      G_TYPE_NONE,
			      1, MRP_TYPE_ASSIGNMENT);

	signals[ASSIGNMENT_REMOVED] = 
		g_signal_new ("assignment_removed",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__OBJECT,
			      G_TYPE_NONE,
			      1, MRP_TYPE_ASSIGNMENT);
	
	signals[CHILD_ADDED] = 
		g_signal_new ("child_added",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[CHILD_REMOVED] = 
		g_signal_new ("child_removed",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      0,
			      NULL, NULL,
			      mrp_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	g_object_class_install_property (
		object_class,
		PROP_NAME,
		g_param_spec_string ("name",
				     "Name",
				     "Name of the task",
				     "",
				     G_PARAM_READWRITE));
	
	g_object_class_install_property (
		object_class,
		PROP_NOTE,
		g_param_spec_string ("note",
				     "Note",
				     "Note attached to the task",
				     "",
				     G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_START,
		mrp_param_spec_time ("start",
				     "Start time",
				     "Task Start time",
				     G_PARAM_READABLE));
	
	g_object_class_install_property (
		object_class,
		PROP_FINISH,
		mrp_param_spec_time ("finish",
				     "Finish time",
				     "Task finish time",
				     G_PARAM_READABLE));
	
	g_object_class_install_property (
		object_class,
		PROP_LATEST_START,
		mrp_param_spec_time ("latest_start",
				     "Latest start",
				     "Latest task start time",
				     G_PARAM_READABLE));
	
	g_object_class_install_property (
		object_class,
		PROP_LATEST_FINISH,
		mrp_param_spec_time ("latest_finish",
				     "Latest finish",
				     "Latest task finish time",
				     G_PARAM_READABLE));
	
	g_object_class_install_property (
		object_class,
		PROP_DURATION,
		g_param_spec_int ("duration",
				  "Duration",
				  "Duration of the task",
				  -1, G_MAXINT, 0,
				  G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_WORK,
		g_param_spec_int ("work",
				  "Work",
				  "Task work",
				  -1, G_MAXINT, 0,
				  G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CRITICAL,
		g_param_spec_boolean ("critical",
				      "Critical",
				      "In critical path",
				      FALSE,
				      G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_TYPE,
		g_param_spec_enum ("type",
				   "Type",
				   "Task type",
				   MRP_TYPE_TASK_TYPE,
				   MRP_TASK_TYPE_NORMAL,
				   G_PARAM_READWRITE));
	
	g_object_class_install_property (
		object_class,
		PROP_SCHED,
		g_param_spec_enum ("sched",
				   "Sceduling type",
				   "Task scheduling type",
				   MRP_TYPE_TASK_SCHED,
				   MRP_TASK_SCHED_FIXED_WORK,
				   G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CONSTRAINT,
		g_param_spec_boxed ("constraint",
				    "Constraint",
				    "Task scheduling constraint",
				    MRP_TYPE_CONSTRAINT,
				    G_PARAM_READWRITE));
	
	g_object_class_install_property (
		object_class,
		PROP_PERCENT_COMPLETE,
		g_param_spec_int ("percent_complete",
				  "Percent complete",
				  "Percent completed of task",
				  0, 100, 0,
				  G_PARAM_READWRITE));
}

static void
task_finalize (GObject *object)
{
	MrpTask     *task;
	MrpTaskPriv *priv;

	task = MRP_TASK (object);
	priv = task->priv;
	
	g_free (priv->name);
	g_free (priv->note);

	/* Make sure we aren't left hanging in the tree. */
	g_assert (priv->node->parent == NULL);

	/* Make sure we don't have dangling relations. */
	g_assert (priv->predecessors == NULL);
	g_assert (priv->successors == NULL);
	
	g_node_destroy (priv->node);
	g_node_destroy (priv->sorted_node);

	g_free (priv);
	task->priv = NULL;

	if (G_OBJECT_CLASS (parent_class)->finalize) {
		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
	}
}

static void
task_set_property (GObject      *object,
		   guint         prop_id,
		   const GValue *value,
		   GParamSpec   *pspec)
{
	MrpTask     *task;
	MrpTaskPriv *priv;
	const gchar *str;
	gint         i_val;
	MrpTaskType  type;
	gboolean     changed = FALSE;

	task = MRP_TASK (object);
	priv = task->priv;
	
	switch (prop_id) {
	case PROP_NAME:
		str = g_value_get_string (value);

		if (!priv->name || strcmp (priv->name, str)) {
			g_free (task->priv->name);
			priv->name = g_strdup (str);
			changed = TRUE;
		}
		
		break;

	case PROP_NOTE:
		str = g_value_get_string (value);

		if (!priv->note || strcmp (priv->note, str)) {
			g_free (task->priv->note);
			priv->note = g_strdup (str);
			changed = TRUE;
		}

		break;
		
 	case PROP_START:
		priv->start = g_value_get_long (value);
		break;

	case PROP_FINISH:
		priv->finish = g_value_get_long (value);
		break;

	case PROP_DURATION:
		if (mrp_task_get_n_children (task) > 0 ||
		    priv->type == MRP_TASK_TYPE_MILESTONE) {
			return;
		}

		if (priv->sched == MRP_TASK_SCHED_FIXED_WORK) {
			return;
		}
		
		i_val = g_value_get_int (value);
		
		if (priv->duration != i_val) {
			priv->duration = i_val;

			g_object_notify (object, "work");
			changed = TRUE;
		}
		break;
		
	case PROP_WORK:
		if (mrp_task_get_n_children (task) > 0 ||
		    priv->type == MRP_TASK_TYPE_MILESTONE) {
			return;
		}

		i_val = g_value_get_int (value);

		if (priv->work != i_val) {
			priv->work = i_val;

			g_object_notify (object, "duration");
			changed = TRUE;
		}
		break;

	case PROP_CRITICAL:
		priv->critical = g_value_get_boolean (value);
		break;

	case PROP_TYPE:
		type = g_value_get_enum (value);
		if (type != priv->type) {
			priv->type = type;
			
			if (type == MRP_TASK_TYPE_MILESTONE) {
				priv->duration = 0;
				priv->work = 0;
			} else {
				/* FIXME: we need a way to specify a default
				 * work/duration for tasks.
				 */
				priv->duration = 60*60*8;
				priv->work = 60*60*8;
			}
			
			g_object_notify (G_OBJECT (task), "duration");
			g_object_notify (G_OBJECT (task), "work");
			
			changed = TRUE;
		}
		break;

	case PROP_SCHED:
		priv->sched = g_value_get_enum (value);
		changed = TRUE;
		break;

 	case PROP_CONSTRAINT:
		/* FIXME: compare */
		priv->constraint = *(MrpConstraint *) g_value_get_boxed (value);
		changed = TRUE;
		break;

	case PROP_PERCENT_COMPLETE:
		i_val = g_value_get_int (value);
		
		if (priv->percent_complete != i_val) {
			priv->percent_complete = i_val;
			changed = TRUE;
		}
		
		break;

	default:
		break;
	}

	if (changed) {
		mrp_object_changed (MRP_OBJECT (object));
	}
}

static void
task_get_property (GObject    *object,
		   guint       prop_id,
		   GValue     *value,
		   GParamSpec *pspec)
{
	MrpTask     *task;
	MrpTaskPriv *priv;

	task = MRP_TASK (object);
	priv = task->priv;
	
	switch (prop_id) {
	case PROP_NAME:
		g_value_set_string (value, priv->name);
		break;
	case PROP_NOTE:
		g_value_set_string (value, priv->note);
		break;
	case PROP_START:
		g_value_set_long (value, priv->start);
		break;
	case PROP_FINISH:
		g_value_set_long (value, priv->finish);
		break;
	case PROP_LATEST_START:
		g_value_set_long (value, priv->latest_start);
		break;
	case PROP_LATEST_FINISH:
		g_value_set_long (value, priv->latest_finish);
		break;
	case PROP_DURATION:
		g_value_set_int (value, priv->duration);
		break;
	case PROP_WORK:
		g_value_set_int (value, priv->work);
		break;
	case PROP_CRITICAL:
		g_value_set_boolean (value, priv->critical);
		break;
	case PROP_TYPE:
		g_value_set_enum (value, priv->type);
		break;
	case PROP_SCHED:
		g_value_set_enum (value, priv->sched);
		break;
	case PROP_CONSTRAINT:
		g_value_set_boxed (value, &priv->constraint);
		break;
	case PROP_PERCENT_COMPLETE:
		g_value_set_int (value, priv->percent_complete);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
task_removed (MrpObject *object)
{
	MrpTask     *task;
	MrpTaskPriv *priv;
	
	g_return_if_fail (MRP_IS_TASK (object));
	
	task = MRP_TASK (object);
	priv = task->priv;

	task_remove_assignments (task);
	task_remove_relations (task);
	
        if (MRP_OBJECT_CLASS (parent_class)->removed) {
                (* MRP_OBJECT_CLASS (parent_class)->removed) (object);
        }
}

static void
task_assignment_removed_cb (MrpAssignment *assignment, MrpTask *task)
{
	MrpTaskPriv *priv;
	
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (MRP_IS_ASSIGNMENT (assignment));
	
	priv = task->priv;

	priv->assignments = g_list_remove (priv->assignments, assignment);

	g_signal_emit (task, signals[ASSIGNMENT_REMOVED], 0, assignment);
	g_object_unref (assignment);

	mrp_object_changed (MRP_OBJECT (task));
}

static void
task_remove_relations (MrpTask *task)
{
	GList       *l, *next;
	MrpRelation *relation;
	MrpTask     *predecessor;
	MrpTask     *successor;

	g_return_if_fail (MRP_IS_TASK (task));
	
	/* Cut relations involving the task (make sure to be robust when
	 * removing during traversing).
	 */
	l = task->priv->predecessors;
	while (l) {
		next = l->next;
		relation = l->data;

		predecessor = mrp_relation_get_predecessor (relation);
		
		mrp_task_remove_predecessor (task, predecessor);

		l = next;
	}
	
	l = task->priv->successors;
	while (l) {
		next = l->next;
		relation = l->data;

		successor = mrp_relation_get_successor (relation);
		
		mrp_task_remove_predecessor (successor, task);

		l = next;
	}
}

static void
task_remove_assignments (MrpTask *task)
{
	MrpTaskPriv   *priv;
	GList         *l;
	MrpAssignment *assignment;

	g_return_if_fail (MRP_IS_TASK (task));

	priv = task->priv;

	for (l = priv->assignments; l; l = l->next) {
		assignment = l->data;
		
		g_signal_handlers_disconnect_by_func (assignment,
						      task_assignment_removed_cb,
						      task);
		g_object_unref (assignment);
		mrp_object_removed (MRP_OBJECT (assignment));
	}
	
	g_list_free (priv->assignments);
}

static gboolean
task_remove_subtree_cb (GNode *node, gpointer data)
{
	MrpTask     *task;
	MrpTaskPriv *priv;

	task = node->data;
	priv = task->priv;
	
	task_remove_relations (task);
	task_remove_assignments (task);

	g_node_unlink (priv->node);
	g_node_unlink (priv->sorted_node);

	g_object_unref (task);
	
	return FALSE;
}

void
imrp_task_remove_subtree (MrpTask *task)
{
	MrpTask *parent;
	
	g_return_if_fail (MRP_IS_TASK (task));

	parent = NULL;
	if (task->priv->node->parent) {
		parent = task->priv->node->parent->data;
	}
	
	g_object_ref (task);
	
	/* Remove the tasks one by one using post order so we don't mess with
	 * the tree while traversing it.
	 */
	g_node_traverse (task->priv->node,
			 G_POST_ORDER,
			 G_TRAVERSE_ALL,
			 -1,
			 (GNodeTraverseFunc) task_remove_subtree_cb,
			 NULL);

	g_object_unref (task);

	if (parent) {
		g_signal_emit (parent, signals[CHILD_REMOVED], 0);
	}
}

void
imrp_task_detach (MrpTask *task)
{
	g_return_if_fail (MRP_IS_TASK (task));

	/* FIXME: Do some extra checking. */

	g_node_unlink (task->priv->node);

	/* Note: we don't unlink the sorted node, because when moving a task but
	 * keeping it under the same parent, we don't need to rebuild the
	 * tree. So when the caller wants that, it has to unlink the tree and
	 * rebuild it.
	 */
}

void
imrp_task_reattach (MrpTask  *task,
		    MrpTask  *sibling,
		    MrpTask  *parent,
		    gboolean  before)
{
	GNode *node;
	
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (sibling == NULL || MRP_IS_TASK (sibling));
	g_return_if_fail (MRP_IS_TASK (parent));

	/* FIXME: Do some extra checking. */

	if (sibling == NULL) {
		if (before) {
			node = g_node_first_child (parent->priv->node);
		} else {
			node = g_node_last_child (parent->priv->node);
		}			

		if (node) {
			sibling = node->data;
		}
	}

	if (before) {
		if (sibling != NULL) {
			g_node_insert_before (parent->priv->node,
					      sibling->priv->node,
					      task->priv->node);
		} else {
			g_node_prepend (parent->priv->node,
					task->priv->node);
		}
	} else {
		if (sibling != NULL) {
			g_node_insert_after (parent->priv->node,
					     sibling->priv->node,
					     task->priv->node);
		} else {
			g_node_append (parent->priv->node,
				       task->priv->node);
		}
	}
}

void
imrp_task_reattach_pos (MrpTask  *task,
			gint      pos,
			MrpTask  *parent)
{
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (pos >= -1);
	g_return_if_fail (MRP_IS_TASK (parent));

	/* FIXME: Do some extra checking. */

	g_node_insert (parent->priv->node,
		       pos,
		       task->priv->node);
}

/**
 * mrp_task_new:
 * 
 * Create a new task.
 * 
 * Return value: the newly created #MrpTask.
 **/
MrpTask *
mrp_task_new (void)
{
	MrpTask *task;

	task = g_object_new (MRP_TYPE_TASK, NULL);

	return task;
}

/**
 * mrp_task_get_name:
 * @task: an #MrpTask
 * 
 * Retrives the name of @task.
 * 
 * Return value: the name
 **/
const gchar *
mrp_task_get_name (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	return task->priv->name;
}

/**
 * mrp_task_set_name:
 * @task: an #MrpResource
 * @name: new name of @task
 * 
 * Sets the name of @task.
 **/
void mrp_task_set_name (MrpTask *task, const gchar *name)
{
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (name != NULL);
	
	mrp_object_set (MRP_OBJECT (task), "name", name, NULL);
}

void
imrp_task_insert_child (MrpTask *parent,
			gint     position,
			MrpTask *child)
{
	g_return_if_fail (MRP_IS_TASK (parent));
	g_return_if_fail (MRP_IS_TASK (child));

	if (child->priv->duration == -1) {
		child->priv->duration = parent->priv->duration;
	}
	
	g_node_insert (parent->priv->node,
		       position,
		       child->priv->node);

	g_node_prepend (parent->priv->sorted_node,
			child->priv->sorted_node);

	g_signal_emit (parent, signals[CHILD_ADDED], 0);
}

static MrpRelation *
task_get_predecessor_relation (MrpTask *task,
			       MrpTask *predecessor)
{
	GList *l;

	for (l = task->priv->predecessors; l; l = l->next) {
		MrpRelation *relation = l->data;

		if (mrp_relation_get_successor (relation) == task &&
		    mrp_relation_get_predecessor (relation) == predecessor) {
			return relation;
		}
	}

	return NULL;
}

static MrpRelation *
task_get_successor_relation (MrpTask *task,
			     MrpTask *successor)
{
	GList *l;

	for (l = task->priv->successors; l; l = l->next) {
		MrpRelation *relation = l->data;

		if (mrp_relation_get_predecessor (relation) == task &&
		    mrp_relation_get_successor (relation) == successor) {
			return relation;
		}
	}

	return NULL;
}

/**
 * mrp_task_has_relation_to:
 * @task_a: an #MrpTask
 * @task_b: an #MrpTask
 * 
 * Checks if @a and @b has a relation, i.e. if a is a predecessor or successor
 * of b.
 * 
 * Return value: %TRUE if @a and @b has a relation
 **/
gboolean
mrp_task_has_relation_to (MrpTask *task_a, MrpTask *task_b)
{
	return (task_get_predecessor_relation (task_a, task_b) != NULL) ||
		(task_get_predecessor_relation (task_b, task_a) != NULL);
}

/**
 * mrp_task_has_relation:
 * @task: an #MrpTask
 * 
 * Checks if a task has any relations, i.e. predecessors or successors.
 * 
 * Return value: %TRUE if there are any relations.
 **/
gboolean
mrp_task_has_relation (MrpTask *task)
{
	return (task->priv->predecessors != NULL ||
		task->priv->successors != NULL);
}

/**
 * mrp_task_add_predecessor:
 * @task: an #MrpTask
 * @predecessor: the predecessor
 * @type: type of relation
 * @lag: lag time, if negative, it means lead time
 * @error: location to store error, or %NULL
 * 
 * Adds a predecessor task to a task. Depending on type, the predecessor
 * must be started or finished before task can be started or finished,
 * with an optional lag/lead time.
 *
 * Return value: the relation that represents the predecessor/successor link.
 **/
MrpRelation *
mrp_task_add_predecessor (MrpTask          *task,
			  MrpTask          *predecessor,
			  MrpRelationType   type,
			  glong             lag,
			  GError          **error)
{
	MrpRelation    *relation;
	MrpProject     *project;
	MrpTaskManager *manager;
	
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	g_return_val_if_fail (MRP_IS_TASK (predecessor), NULL);

	/* Can't have more than one relation between the same two tasks. */
	if (mrp_task_has_relation_to (task, predecessor)) {
		g_set_error (error,
			     MRP_ERROR,
			     MRP_ERROR_TASK_RELATION_FAILED,
			     _("Could not add a predecessor relation, because the tasks are already related."));
		
		return NULL;
	}

	g_object_get (task, "project", &project, NULL);
	manager = imrp_project_get_task_manager (project);
	if (!mrp_task_manager_check_predecessor (manager, task, predecessor, error)) {
		return NULL;
	}
	
	relation = g_object_new (MRP_TYPE_RELATION,
				 "successor", task,
				 "predecessor", predecessor,
				 "type", type,
				 "lag", lag,
				 NULL);

	task->priv->predecessors = g_list_prepend (task->priv->predecessors, relation);
	predecessor->priv->successors = g_list_prepend (predecessor->priv->successors, relation);

	g_signal_emit (task, signals[RELATION_ADDED], 0, relation);
	g_signal_emit (predecessor, signals[RELATION_ADDED], 0, relation);

	mrp_object_changed (MRP_OBJECT (task));
	mrp_object_changed (MRP_OBJECT (predecessor));

	return relation;
}

/**
 * mrp_task_remove_predecessor:
 * @task: an #MrpTask
 * @predecessor: the predecessor to remove
 * 
 * Removes a predecessor previously added to task.
 **/
void
mrp_task_remove_predecessor (MrpTask *task,
			     MrpTask *predecessor)
{
	MrpRelation *relation;
	
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (MRP_IS_TASK (predecessor));

	relation = task_get_predecessor_relation (task, predecessor);

	task->priv->predecessors = g_list_remove (task->priv->predecessors, relation);
	predecessor->priv->successors = g_list_remove (predecessor->priv->successors, relation);

	/* Notify. */
	mrp_object_removed (MRP_OBJECT (relation));

	g_signal_emit (task, signals[RELATION_REMOVED], 0, relation);
	g_signal_emit (predecessor, signals[RELATION_REMOVED], 0, relation);

	mrp_object_changed (MRP_OBJECT (task));
	mrp_object_changed (MRP_OBJECT (predecessor));

	/* FIXME: ? */
	g_object_unref (relation);
}

/**
 * mrp_task_get_relation:
 * @task_a: an #MrpTask
 * @task_b: an #MrpTask
 * 
 * Fetches a relation between two tasks if it exists.
 * 
 * Return value: a #MrpRelation representing the relation between @task_a and 
 * @task_b or %NULL if they don't have any relation.
 **/
MrpRelation *
mrp_task_get_relation (MrpTask *task_a, MrpTask *task_b)
{
	MrpRelation *relation;
	
	g_return_val_if_fail (MRP_IS_TASK (task_a), NULL);
	g_return_val_if_fail (MRP_IS_TASK (task_b), NULL);
	
	relation = task_get_predecessor_relation (task_a, task_b);
	if (relation != NULL) {
		return relation;
	}

	return task_get_successor_relation (task_a, task_b);
}

/**
 * mrp_task_get_predecessor_relation:
 * @task: an #MrpTask
 * @predecessor: #an MrpTask
 * 
 * Fetches a predecessor relation between task and it's predecessor.
 * 
 * Return value: the #MrpRelation if it exists, otherwise %NULL
 **/
MrpRelation *
mrp_task_get_predecessor_relation (MrpTask *task, MrpTask *predecessor)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	g_return_val_if_fail (MRP_IS_TASK (predecessor), NULL);
	
	return task_get_predecessor_relation (task, predecessor);
}

/**
 * mrp_task_get_successor_relation:
 * @task: an #MrpTask
 * @successor: an #MrpTask
 * 
 * Fetches a successor relation between task and it's successor.
 * 
 * Return value: the #MrpRelation if it exists, otherwise %NULL
 **/
MrpRelation *
mrp_task_get_successor_relation (MrpTask *task, MrpTask *successor)
{
	MrpRelation *relation;
	
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	g_return_val_if_fail (MRP_IS_TASK (successor), NULL);
	
	relation = task_get_successor_relation (task, successor);
	return relation;
}

/**
 * mrp_task_get_predecessor_relations:
 * @task: an #MrpTask
 * 
 * Fetches a list of predecessor relations to @task. 
 * 
 * Return value: the list of predecessor relations to @task
 **/
GList *
mrp_task_get_predecessor_relations (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	return task->priv->predecessors;
}

/**
 * mrp_task_get_successor_relations:
 * @task: an #MrpTask
 * 
 * Fetches a list of successor relations to @task. 
 * 
 * Return value: a list of successor relations to @task
 **/
GList *
mrp_task_get_successor_relations (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	return task->priv->successors;
}

/**
 * mrp_task_get_parent:
 * @task: an #MrpTask
 * 
 * Fetches the parent of @task.
 * 
 * Return value: the parent of @task, or %NULL if there is no parent..
 **/
MrpTask *
mrp_task_get_parent (MrpTask *task)
{
	GNode *node;

	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	node = task->priv->node->parent;
	return node ? node->data : NULL;
}

/**
 * mrp_task_get_first_child:
 * @task: an #MrpTask
 * 
 * Fetches the first child of @task.
 * 
 * Return value: the first child of @task, or %NULL if there are no children.
 **/
MrpTask *
mrp_task_get_first_child (MrpTask *task)
{
	GNode *node;
	
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	node = g_node_first_child (task->priv->node);
	return node ? node->data : NULL;
}

/**
 * mrp_task_get_next_sibling:
 * @task: an #MrpTask
 * 
 * Fetches the next sibling of @task.
 * 
 * Return value: the next sibling of @task, or %NULL if there is no next
 * sibling.
 **/
MrpTask *
mrp_task_get_next_sibling (MrpTask *task)
{
	GNode *node;

	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	node = g_node_next_sibling (task->priv->node);
	return node ? node->data : NULL;
}

/**
 * mrp_task_get_n_children:
 * @task: an #MrpTask
 * 
 * Fetches the number of children @task has.
 * 
 * Return value: the number of children @task has
 **/
guint
mrp_task_get_n_children (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	return g_node_n_children (task->priv->node);
}

/**
 * mrp_task_get_nth_child:
 * @task: an #MrpTask
 * @n: the index of the child to get
 * 
 * Fetches the nth child of @task.
 * 
 * Return value: the nth child of @task, or %NULL if there is no such child.
 **/
MrpTask *
mrp_task_get_nth_child (MrpTask *task, guint n)
{
	GNode *node;

	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	node = g_node_nth_child (task->priv->node, n);
	return node ? node->data : NULL;
}

/**
 * mrp_task_get_position:
 * @task: an #MrpTask
 * 
 * Fetches the index or position of @task among its siblings.
 * 
 * Return value: the position of @task among its siblings.
 **/
gint
mrp_task_get_position (MrpTask *task)
{
	GNode *parent;
	
	g_return_val_if_fail (MRP_IS_TASK (task), 0);
	g_return_val_if_fail (task->priv->node->parent != NULL, 0);

	parent = task->priv->node->parent;
	
	return g_node_child_position (parent, task->priv->node);
}

/**
 * mrp_task_add_assignment:
 * @task: an #MrpTask
 * @assignment: an #MrpAssignment
 * 
 * Adds an assignment to @task.
 **/
void
imrp_task_add_assignment (MrpTask *task, MrpAssignment *assignment)
{
	MrpTaskPriv *priv;
	
	g_return_if_fail (MRP_IS_TASK (task));
	g_return_if_fail (MRP_IS_ASSIGNMENT (assignment));
	
	priv = task->priv;
	
	priv->assignments = g_list_prepend (priv->assignments, 
					    g_object_ref (assignment));

	g_signal_connect (assignment,
			  "removed",
			  G_CALLBACK (task_assignment_removed_cb),
			  task);

	g_signal_emit (task, signals[ASSIGNMENT_ADDED], 0, assignment);

	mrp_object_changed (MRP_OBJECT (task));
}

/**
 * mrp_task_get_start:
 * @task: an #MrpTask
 * 
 * Fetches the start time of @task.
 * 
 * Return value: the start time of @task.
 **/
mrptime
mrp_task_get_start (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	return task->priv->start;
}

/**
 * mrp_task_get_finish:
 * @task: an #MrpTask
 * 
 * Fetches the finish time of @task.
 * 
 * Return value: the finish time of @task.
 **/
mrptime
mrp_task_get_finish (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	return task->priv->finish;
}

/**
 * mrp_task_get_work_start:
 * @task: an #MrpTask
 * 
 * Retrieves the first time where work is performed of @task. This might be
 * different from the start time, if the start time is during non-working
 * time. In that case, the work start would be right after the non-working
 * interval.
 * 
 * Return value: The work start time of @task.
 **/
mrptime
mrp_task_get_work_start (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);
	
	return task->priv->work_start;
}

/**
 * mrp_task_get_latest_start:
 * @task: an #MrpTask
 * 
 * Retrieves the latest start time of @task, i.e. the latest time the task can
 * start without delaying the project.
 * 
 * Return value: The latest start time of @task.
 **/
mrptime
mrp_task_get_latest_start (MrpTask *task)
{
	return task->priv->latest_start;
}

/**
 * mrp_task_get_latest_finish:
 * @task: an #MrpTask
 * 
 * Retrieves the latest finish time of @task, i.e. the latest time the task can
 * finish without delaying the project.
 * 
 * Return value: The latest finish time of @task.
 **/
mrptime
mrp_task_get_latest_finish (MrpTask *task)
{
	return task->priv->latest_finish;
}

/**
 * mrp_task_get_duration:
 * @task: an #MrpTask
 * 
 * Fetches the duration of @task. This differs from the calendar duration that
 * is retrieved by (finish - start).
 * 
 * Return value: The duration of @task.
 **/
gint
mrp_task_get_duration (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	return task->priv->duration;
}

/**
 * mrp_task_get_work:
 * @task: an #MrpTask
 * 
 * Retrieves the amount of work of @task.
 * 
 * Return value: The work of @task.
 **/
gint
mrp_task_get_work (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);
	
	return task->priv->work;
}

/**
 * mrp_task_get_assignments:
 * @task: an #MrpTask
 * 
 * Fetches a list of #MrpAssignment.
 * 
 * Return value: the list of assignments.
 **/
GList *
mrp_task_get_assignments (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	
	return task->priv->assignments;
}

/**
 * mrp_task_get_assignment:
 * @task: an #MrpTask
 * @resource: an #MrpResource
 * 
 * retrieves the #MrpAssignment associated with @task and @resource if the
 * resource is assigned to @task, or %NULL if there is no such assignment.
 * 
 * Return value: The assignment if it exists, otherwise %NULL.
 **/
MrpAssignment *
mrp_task_get_assignment (MrpTask *task, MrpResource *resource)
{
	GList *l;
	
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	g_return_val_if_fail (MRP_IS_RESOURCE (resource), NULL);

	for (l = task->priv->assignments; l; l = l->next) {
		MrpAssignment *assignment = l->data;
		
		if (mrp_assignment_get_resource (assignment) == resource) {
			return assignment;
		}
	}
	
	return NULL;
}

/**
 * mrp_task_get_assigned_resources:
 * @task: an #MrpTask
 * 
 * Fetches a list of resources assigned to @task. The list needs to be freed 
 * with g_list_free() by caller.
 * 
 * Return value: A newly created list of #MrpResource.
 **/
GList *
mrp_task_get_assigned_resources (MrpTask *task)
{
	GList *list = NULL;
	GList *l;
	
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);

	for (l = task->priv->assignments; l; l = l->next) {
		MrpAssignment *assignment = l->data;

		list = g_list_prepend (
			list, mrp_assignment_get_resource (assignment));
	}
	
	list = g_list_sort (list, mrp_resource_compare);
	
	return list;
}

/**
 * mrp_task_compare:
 * @a: an #MrpTask
 * @b: an #MrpTask
 * 
 * Compares the name of the tasks, by calling strcmp() on the names.
 * 
 * Return value: the return value of strcmp (a->name, b->name).
 **/
gint
mrp_task_compare (gconstpointer a, gconstpointer b)
{
	return strcmp (MRP_TASK (a)->priv->name, MRP_TASK (b)->priv->name);
}

/**
 * mrp_task_reset_constraint:
 * @task: an #MrpTask
 * 
 * Sets the contraint type to %MRP_CONTRAINT_ASAP and notifies listeners.
 **/
void
mrp_task_reset_constraint (MrpTask *task)
{
	g_return_if_fail (MRP_IS_TASK (task));

	if (task->priv->constraint.type != MRP_CONSTRAINT_ASAP) {
		task->priv->constraint.type = MRP_CONSTRAINT_ASAP;
		g_object_notify (G_OBJECT (task), "constraint");
	}
}

/**
 * mrp_task_get_cost:
 * @task: an #MrpTask
 * 
 * Calculates the cost to complete @task.
 * 
 * Return value: The cost to complete @task.
 **/
gfloat
mrp_task_get_cost (MrpTask *task)
{
	MrpTaskPriv *priv;
	GList       *assignments, *l;
	MrpResource *resource;
	gfloat       total = 0;
	gfloat       cost;

	g_return_val_if_fail (MRP_IS_TASK (task), 0);

	/* FIXME: why don't we cache this in priv->cost and recalculate in
	 * reschedule? This is called a lot from the tree view...
	 */

	priv = task->priv;

	assignments = mrp_task_get_assignments (task);
	for (l = assignments; l; l = l->next) {
		resource = mrp_assignment_get_resource (l->data);

		mrp_object_get (resource, "cost", &cost, NULL);
		total += priv->work / 3600.0 * cost;
	}

	return total;
}

/* Boxed types. */

static MrpConstraint *
mrp_constraint_copy (const MrpConstraint *src)
{
	MrpConstraint *copy;

	copy = g_new (MrpConstraint, 1);
	memcpy (copy, src, sizeof (MrpConstraint));

	return copy;
}

GType
mrp_constraint_get_type (void)
{
	static GType our_type = 0;
  
	if (our_type == 0) {
		our_type = g_boxed_type_register_static ("MrpConstraint",
							 (GBoxedCopyFunc) mrp_constraint_copy,
							 (GBoxedFreeFunc) g_free);
	}
	
	return our_type;
}

/* Getters and setters. */

void
imrp_task_set_visited (MrpTask *task, gboolean visited)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->visited = visited;
}

gboolean
imrp_task_get_visited (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), FALSE);

	return task->priv->visited;
}

void
imrp_task_set_start (MrpTask *task, mrptime start)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->start = start;
}

void
imrp_task_set_work_start (MrpTask *task, mrptime work_start)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->work_start = work_start;
}

void
imrp_task_set_finish (MrpTask *task, mrptime finish)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->finish = finish;
}

void
imrp_task_set_duration (MrpTask *task, gint duration)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->duration = duration;
}

void
imrp_task_set_work (MrpTask *task, gint work)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->work = work;
}

void
imrp_task_set_latest_start (MrpTask *task,
			    mrptime  time)
{
	task->priv->latest_start = time;
}

void
imrp_task_set_latest_finish (MrpTask *task,
			     mrptime  time)
{
	task->priv->latest_finish = time;
}

MrpConstraint
impr_task_get_constraint (MrpTask *task)
{
	MrpConstraint c = { 0 };
	
	g_return_val_if_fail (MRP_IS_TASK (task), c);

	return task->priv->constraint;
}

void
impr_task_set_constraint (MrpTask *task, MrpConstraint constraint)
{
	g_return_if_fail (MRP_IS_TASK (task));

	task->priv->constraint = constraint;
}

gint
imrp_task_get_depth (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), 0);
	
	return g_node_depth (task->priv->node);
}

GNode *
imrp_task_get_node (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	
	return task->priv->node;
}

GNode *
imrp_task_get_sorted_node (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	
	return task->priv->sorted_node;
}

GList *
imrp_task_peek_predecessors (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	
	return task->priv->predecessors;
}

GList *
imrp_task_peek_successors (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
	
	return task->priv->successors;
}

MrpTaskType
imrp_task_get_type (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), MRP_TASK_TYPE_NORMAL);

	return task->priv->type;
}

MrpTaskSched
imrp_task_get_sched (MrpTask *task)
{
	g_return_val_if_fail (MRP_IS_TASK (task), MRP_TASK_SCHED_FIXED_WORK);

	return task->priv->sched;
}

