/*
 * Copyright (C) 2012 - Juan Ferrer Toribio
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include "db-iterator.h"

#define IS_READY(obj)	(obj->model && db_model_get_status (obj->model) == DB_MODEL_STATUS_READY)

/**
 * SECTION:db-iterator
 * @short_description: manages a iterator with its events
 * @title: DbIterator
 *
 * The DbIterator manages a connection with a data base.
 * There are different ways to create an DbIterator:
 * <itemizedlist>
 * 	<listitem>
 *   	<para>
 *     	db_iterator_new(): This constructor just needs a #DbModel object 
 *   	</para>
 * </listitem>
 * <listitem>
 *   <para>
 *    	db_iterator_new_with_stmt(): This one needs a #DbConn with the connection and
 * 								a #SqlStmt object
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
 *    	db_iterator_new_with_sql(): This one needs a #DbConn with the connection and
 * 								the sql string
 *   </para>
 * </listitem>
 *</itemizedlist>
 */
G_DEFINE_TYPE (DbIterator, db_iterator, G_TYPE_OBJECT);

enum {
	 ITER_CHANGED
	,DATA_CHANGED
	,ROW_NUM_CHANGED
	,STATUS_CHANGED
	,OPERATIONS_DONE
	,LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = {0};

/**
 * db_iterator_new:
 * @model: the #DbModel used by iterator.
 *
 * Creates a new #DbIterator.
 *
 * Return value: a #DbIterator.
 **/
DbIterator * db_iterator_new (DbModel * model)
{
	return g_object_new (DB_TYPE_ITERATOR, "model", model, NULL);
}

/**
 * db_iterator_new_with_stmt:
 * @conn: the connection used to create the model.
 * @stmt: the #SqlStmt statement used to create the model.
 *
 * Creates a new #DbIterator. #SqlStmt must be a #SqlSelect or a #SqlString
 * object with a valid SELECT statement.
 *
 * Return value: a #DbIterator.
 **/
DbIterator * db_iterator_new_with_stmt (DbConn * conn, SqlStmt * stmt)
{
	DbIterator * obj;
	DbModel * model;
	
	g_return_val_if_fail (DB_IS_CONN (conn) || !conn, NULL);
	g_return_val_if_fail (SQL_IS_STMT (stmt) || !stmt, NULL);

	model = db_model_new (conn, stmt);
	obj = g_object_new (DB_TYPE_ITERATOR, "model", model, NULL);
	g_object_unref (model);

	return obj;
}

/**
 * db_iterator_new_with_sql:
 * @conn: the connection used to create the model.
 * @sql: the sql string used to create the model.
 *
 * Creates a new #DbIterator. sql must be a valid SELECT statement.
 *
 * Return value: a #DbIterator.
 **/
DbIterator * db_iterator_new_with_sql (DbConn * conn, const gchar * sql)
{
	g_return_val_if_fail (sql, NULL);
	g_return_val_if_fail (DB_IS_CONN (conn) || !conn, NULL);

	return g_object_new (DB_TYPE_ITERATOR, "sql", sql, "conn", conn, NULL);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private

static void db_iterator_unref_param (DbIterator * obj, DbParam * param)
{
	obj->params = g_list_remove (obj->params, param);
}

/*
 * #DbIterator instances call this function every time line in
 * the model is changed.
 */
static void db_iterator_row_num_changed (DbIterator * obj)
{
	if (obj->row_selected)
		obj->row = db_model_get_path (obj->model, &obj->iter);
	else if (!obj->remember_selection)
		obj->row = -1;

	g_signal_emit (obj, signals[ROW_NUM_CHANGED], 0);
}

/*
 * #DbIterator instances call this function every time the data of the params
 * mustbe updated.
 */
static void db_iterator_iter_changed (DbIterator * obj)
{
	db_iterator_row_num_changed (obj);
	g_signal_emit (obj, signals[ITER_CHANGED], 0);
}

static void db_iterator_set_iter (DbIterator * obj, DbIter * iter)
{
	if (iter)
	{
		obj->row_selected = TRUE;
		obj->iter = *iter;
		db_iterator_iter_changed (obj);
	}
	else if (obj->row_selected)
	{
		obj->row_selected = FALSE;
		db_iterator_iter_changed (obj);
	}
}

/*
 * Function called when row is inserted on the model.
 */
static void db_iterator_on_model_line_inserted (DbModel * model, DbIter * iter, DbIterator * obj)
{
	g_signal_emit (obj, signals[DATA_CHANGED], 0);
}

/*
 * Function called when row is updated on the model.
 */
static void db_iterator_on_model_line_updated_after (DbModel * model, DbIter * iter, DbIterator * obj)
{
	if (obj->row_selected && db_iter_compare (iter, &obj->iter))
		db_iterator_iter_changed (obj);

	g_signal_emit (obj, signals[DATA_CHANGED], 0);
}

/*
 * Function called when row is deleted on the model.
 */
static void db_iterator_on_model_line_deleted (DbModel * model, gint row, DbIterator * obj)
{	
	if (obj->row_selected && row == obj->row)
	{
		DbIter iter;

		if (db_model_get_iter (model, &iter, row + 1)
		|| db_model_get_iter (model, &iter, row - 1))
			db_iterator_set_iter (obj, &iter);
		else
			db_iterator_set_iter (obj, NULL);
	}
}

static void db_iterator_on_model_line_deleted_after (DbModel * model, gint row, DbIterator * obj)
{
	if (obj->row_selected)
		db_iterator_row_num_changed (obj);

	g_signal_emit (obj, signals[DATA_CHANGED], 0);
}

/*
 * Function called when model rows are reordered.
 */
static void db_iterator_on_model_lines_reordered (DbModel * model,
	gint column, gint * new_order, DbIterator * obj)
{
	if (obj->row_selected)
		db_iterator_row_num_changed (obj);
}

/*
 * Function called when model status changes.
 */
static void db_iterator_on_model_status_changed (DbModel * model, DbModelStatus status, DbIterator * obj)
{
	if (status == DB_MODEL_STATUS_READY)
	{
		DbIter iter;
		gint nrows = db_model_get_nrows (model);

		g_signal_emit (obj, signals[STATUS_CHANGED], 0, IS_READY(obj));

		if (obj->row >= 0 && obj->row < nrows 
		&& db_model_get_iter (model, &iter, obj->row))
			db_iterator_set_iter (obj, &iter);
		else
			db_iterator_set_iter (obj, NULL);
	}
	else
	{
		db_iterator_set_iter (obj, NULL);
		g_signal_emit (obj, signals[STATUS_CHANGED], 0, IS_READY(obj));
	}
}

/*
 * Function called when model operations are done.
 */
static void db_iterator_on_model_operations_done (DbModel * model, DbIterator * obj)
{
	g_signal_emit (obj, signals[OPERATIONS_DONE], 0);
}

static void db_iterator_set_model_mode (DbIterator * obj)
{
	if (obj->mode == DB_ITERATOR_MODE_ON_CHANGE)
		db_model_set_mode (obj->model, DB_MODEL_MODE_ON_CHANGE);
	else
		db_model_set_mode (obj->model, DB_MODEL_MODE_ON_DEMAND);
}

/*
 * Sets the model used as a data source by iterator.
 */
static void db_iterator_set_model (DbIterator * obj, DbModel * model)
{
	if (!model)
		return;

	if (!obj->model)
	{
		obj->model = g_object_ref (model);
		g_object_connect (obj->model
			,"signal::lines-reordered", db_iterator_on_model_lines_reordered, obj
			,"signal::line-inserted", db_iterator_on_model_line_inserted, obj
			,"signal-after::line-updated", db_iterator_on_model_line_updated_after, obj
			,"signal::line-deleted", db_iterator_on_model_line_deleted, obj
			,"signal-after::line-deleted", db_iterator_on_model_line_deleted_after, obj
			,"signal::status-changed", db_iterator_on_model_status_changed, obj
			,"signal::operations-done", db_iterator_on_model_operations_done, obj
			,NULL
		);
		db_iterator_set_model_mode (obj);
		db_iterator_on_model_status_changed (model,
			db_model_get_status (model), obj);
	}
	else
		g_warning ("DbIterator: Can't reassign the 'model' property");
}

/*
 * Check if the iterator has any selected row, otherwise issues a message.
 */
static gboolean db_iterator_check_row_selected (DbIterator * obj)
{
	if (obj->row_selected)
		return TRUE;

	g_warning ("DbIterator: Row not selected");
	return FALSE;
}

/*
 * Attempts to create a model, if have enough information, and assigns it to
 * the iterator.
 */
static void db_iterator_try_create_model (DbIterator * obj)
{
	DbModel * new_model = NULL;

	if (obj->conn)
	{
		if (obj->sql)
		{
			if (obj->use_file)
				new_model = db_model_new_with_file (obj->conn, obj->sql);
			else
				new_model = db_model_new_with_sql (obj->conn, obj->sql);		
		}
		if (obj->stmt)
			new_model = db_model_new (obj->conn, obj->stmt);
	}
	
	if (new_model)
		db_iterator_set_model (obj, new_model);
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Public

/**
 * db_iterator_get_row:
 * @obj: a #DbIterator
 *
 * Gets the selected row number.
 *
 * Return value: the row number
 **/
gint db_iterator_get_row (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), -1);

	if (obj->row_selected)
		return obj->row;
	else
		return -1;
}

/**
 * db_iterator_get_model:
 * @obj: a #DbIterator
 *
 * Gets the model used as a data source by iterator.
 *
 * Return value: (transfer none): the #DbModel
 **/
DbModel * db_iterator_get_model (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), NULL);

	return obj->model;
}

/**
 * db_iterator_get_conn:
 * @obj: a #DbIterator
 *
 * Gets connection used by the @obj model.
 *
 * Return value: (transfer none): the #DbConn
 **/
DbConn * db_iterator_get_conn (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), NULL);
	
	if (obj->model)
		db_model_get_conn (obj->model);
	
	return NULL;
}

/**
 * db_iterator_set_conn:
 * @obj: a #DbIterator
 * @conn: the #DbConn
 *
 * Sets the connection used by the @obj model.
 **/
void db_iterator_set_conn (DbIterator * obj, DbConn * conn)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (DB_IS_CONN (conn));
	g_return_if_fail (obj->model);
	
	db_model_set_conn (obj->model, conn);
}

/**
 * db_iterator_is_ready:
 * @obj: a #DbIterator
 *
 * Gets if the iterator is ready.
 *
 * Return value: %TRUE if the iterator and its model are ready, %FALSE otherwise.
 **/
gboolean db_iterator_is_ready (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), FALSE);

	return IS_READY (obj);
}

/**
 * db_iterator_get_mode:
 * @obj: a #DbIterator
 *
 * Gets the mode in which the iterator is working.
 *
 * Return value: the #DbIteratorMode
 **/
DbIteratorMode db_iterator_get_mode (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), 0);

	return obj->mode;
}

/**
 * db_iterator_set_mode:
 * @obj: a #DbIterator
 * @mode: the #DbIteratorMode mode
 *
 * Sets the mode in which the iterator should work.
 **/
void db_iterator_set_mode (DbIterator * obj, DbIteratorMode mode)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	
	obj->mode = mode;

	if (obj->model)
		db_iterator_set_model_mode (obj);
}

/**
 * db_iterator_get_iter:
 * @obj: a #DbIterator
 * @iter: (allow-none): return location for selected row iter, or %NULL.
 *
 * Gets the currently selected iter.
 *
 * Return value: %TRUE if any row is selected, %FALSE otherwise.
 **/
gboolean db_iterator_get_iter (DbIterator * obj, DbIter * iter)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), FALSE);

	if (!obj->row_selected)
		return FALSE;

	*iter = obj->iter;
	return TRUE;
}

/**
 * db_iterator_move_iter:
 * @obj: a #DbIterator
 * @iter: a #DbIter
 *
 * Moves the iterator cursor to the specified iter, if model is ready.
 **/
void db_iterator_move_iter (DbIterator * obj, DbIter * iter)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (IS_READY (obj));
	g_return_if_fail (obj->model);

	if (obj->row_selected && db_iter_compare (&obj->iter, iter))
		return;

	if (obj->mode != DB_ITERATOR_MODE_ON_DEMAND)
		db_model_perform_operations (obj->model, FALSE);

	db_iterator_set_iter (obj, iter);
}

/**
 * db_iterator_move_first:
 * @obj: a #DbIterator
 *
 * Moves the iterator cursor to the first row.
 **/
void db_iterator_move_first (DbIterator * obj)
{
	DbIter iter;

	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (obj->model);

	if (db_model_get_iter_first (obj->model, &iter))
		db_iterator_move_iter (obj, &iter);
}

/**
 * db_iterator_move_last:
 * @obj: a #DbIterator
 *
 * Moves the iterator cursor to the last row.
 **/
void db_iterator_move_last (DbIterator * obj)
{
	DbIter iter;

	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (obj->model);

	if (db_model_get_last (obj->model, &iter))
		db_iterator_move_iter (obj, &iter);
}

/**
 * db_iterator_move_previous:
 * @obj: a #DbIterator
 *
 * Moves the iterator cursor to the previous row, or displays a message if no
 * exists. If no selected row, attempts to move the cursor to the first row.
 **/
void db_iterator_move_previous (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));

	if (obj->row_selected)
	{
		DbIter iter = obj->iter;

		if (db_model_iter_prev (obj->model, &iter))
			db_iterator_move_iter (obj, &iter);
		else
			g_warning (
				"Can't move the cursor to the previous "
				"row, because there are no more rows."
			);
	}
	else
		db_iterator_move_first (obj);
}

/**
 * db_iterator_move_next:
 * @obj: a #DbIterator
 *
 * Moves the iterator cursor to the next row, or displays a message if no exists.
 * If no selected row, attempts to move the cursor to the first row.
 **/
void db_iterator_move_next (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));

	if (obj->row_selected)
	{
		DbIter iter = obj->iter;
		
		if (db_model_iter_next (obj->model, &iter))
			db_iterator_move_iter (obj, &iter);
		else
			g_warning (
				"Can't move the cursor to the next "
				"row, because there are no more rows."
			);
	}
	else
		db_iterator_move_first (obj);
}

/**
 * db_iterator_move_to:
 * @obj: a #DbIterator
 * @move: a #DbIteratorMove
 *
 * Moves the iterator cursor to the predefined position.
 **/
void db_iterator_move_to (DbIterator * obj, DbIteratorMove move)
{
	switch (move)
	{
		case DB_ITERATOR_MOVE_FIRST:
			db_iterator_move_first (obj);
			break;
		case DB_ITERATOR_MOVE_PREVIOUS:
			db_iterator_move_previous (obj);
			break;
		case DB_ITERATOR_MOVE_NEXT:
			db_iterator_move_next (obj);
			break;
		case DB_ITERATOR_MOVE_LAST:
			db_iterator_move_last (obj);
			break;
	}
}

/**
 * db_iterator_insert:
 * @obj: a #DbIterator
 *
 * Inserts a new row in the model and moves the iter to it, if inserted.
 * successfully.
 **/
void db_iterator_insert (DbIterator * obj)
{
	DbIter iter;

	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (obj->model);
	
	if (obj->mode != DB_ITERATOR_MODE_ON_DEMAND)
		db_model_perform_operations (obj->model, FALSE);

	if (db_model_insert (obj->model, &iter))
		db_iterator_set_iter (obj, &iter);
}

/**
 * db_iterator_delete:
 * @obj: a #DbIterator
 *
 * Deletes the currently selected row from the model, if any.
 **/
void db_iterator_delete (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));

	if (db_iterator_check_row_selected (obj))
	{
		db_model_delete (obj->model, &obj->iter);

		if (obj->mode != DB_ITERATOR_MODE_ON_DEMAND)
			db_model_perform_operations (obj->model, FALSE);
	}
}

/**
 * db_iterator_refresh:
 * @obj: a #DbIterator
 *
 * Refresh the data of the model handled by iterator.
 **/
void db_iterator_refresh (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));

	if (obj->model)
		db_model_refresh (obj->model);
};

/**
 * db_iterator_get_spec:
 * @obj: a #DbIterator
 * @column: the column index.
 *
 * Gets the spec from the model of the specified column index.
 *
 * Return value: (transfer none) (allow-none): a #GvnParamSpec or %NULL if
 * can't get it because the model isn't ready.
 **/
const GvnParamSpec * db_iterator_get_spec (DbIterator * obj, gint column)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), NULL);

	if (IS_READY (obj))
		return db_model_get_spec (obj->model, column);
	else
		return NULL;
}

/**
 * db_iterator_get_value:
 * @obj: a #DbIterator
 * @column: the column index.
 *
 * Gets the value of the specified column index.
 *
 * Return value: (transfer none) (allow-none): the value or %NULL if
 * can't get it because the model isn't ready.
 **/
const GValue * db_iterator_get_value (DbIterator * obj, gint column)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), NULL);

	if (obj->row_selected)
		return db_model_get_value (obj->model, &obj->iter, column, NULL);
		
	return NULL;		
}	

/**
 * db_iterator_get_column_index:
 * @obj: a #DbIterator
 * @name: the name of a column of iterator.
 *
 * Retrieves the position in the query of the column called @name.
 *
 * Return value: the position of the column with name @name or -1 if there isn't
 * a column called name or if name is %NULL.
 **/
gint db_iterator_get_column_index (DbIterator * obj, const gchar * name)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), -1);
	g_return_val_if_fail (IS_READY (obj), -1);

	return db_model_get_column_index (obj->model, name);
}

/**
 * db_iterator_set_value:
 * @obj: a #DbIterator
 * @column: the column index.
 * @value: a #GValue with the new value.
 * @err: (out) (allow-none): the return location for a #GError or %NULL.
 *
 * Sets the value of the specified column index.
 **/
void db_iterator_set_value (DbIterator * obj, gint column, const GValue * value, GError ** err)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));

	if (db_iterator_check_row_selected (obj))
		db_model_set_value (obj->model, &obj->iter, column, value, err);
}

/**
 * db_iterator_add_param:
 * @obj: a #DbIterator
 * @param: the param to add.
 *
 * Adds a #DbParam to the list of iterator params.
 **/
void db_iterator_add_param (DbIterator * obj, DbParam * param)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (DB_IS_PARAM (param));
	
	db_param_set_iterator (param, obj);
	g_object_weak_ref (G_OBJECT (param),
		(GWeakNotify) db_iterator_unref_param, obj);
	obj->params = g_list_prepend (obj->params, param);
}

/**
 * db_iterator_get_param:
 * @obj: a #DbIterator
 * @column: the column name
 *
 * Creates a parameter for the specified column index and returns it.
 *
 * Return value: (transfer none): a #GvnParam
 **/
GvnParam * db_iterator_get_param (DbIterator * obj, const gchar * column)
{ 
	GList * n;
	GvnParam * param;
	
	g_return_val_if_fail (DB_IS_ITERATOR (obj), NULL);
	g_return_val_if_fail (column, NULL);

	for (n = obj->params; n; n = n->next)
		if (!g_strcmp0 (db_param_get_column_name (n->data), column))
			break;

	if (!n)
	{
		param = db_param_new (column);
		db_iterator_add_param (obj, DB_PARAM (param));
	}
	else
		param = n->data;

	return param;
}

/**
 * db_iterator_get_params:
 * @obj: a #DbIterator
 *
 * Gets a list of params linked with the iterator. The returned list shoud be freed.
 *
 * Return value: (element-type Db.Param) (transfer container): the #GList
 **/
GList * db_iterator_get_params (DbIterator * obj)
{
	return g_list_copy (obj->params);
}

/**
 * db_iterator_bind_param:
 * @obj: a #DbIterator
 * @column: the column index.
 * @param: the column index.
 *
 * Binds the param to the specified column index. If you want to link the same
 * param several times you should use the db_iterator_get_param function.
 **/
void db_iterator_bind_param (DbIterator * obj, const gchar * column, GvnParam * param)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (GVN_IS_PARAM (obj));

	gvn_param_set_master (param,
		db_iterator_get_param (obj, column)
	);
}

/**
 * db_iterator_link:
 * @obj: a #DbIterator
 * @field: the field name in the iterator statement
 * @src: the source #DbIterator
 * @column: the column number of @src
 *
 * Links the iterator with another iterator parameter.
 **/
void db_iterator_link (DbIterator * obj, const gchar * field, DbIterator * src, const gchar * column)
{	
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (DB_IS_ITERATOR (src));
	g_return_if_fail (field);

	db_iterator_link_with_param (obj, field,
		db_iterator_get_param (src, column));
}

/**
 * db_iterator_link_with_param:
 * @obj: a #DbIterator
 * @field: the field name in the iterator statement
 * @param: the #GvnParam
 *
 * Links the iterator with a parameter.
 **/
void db_iterator_link_with_param (DbIterator * obj, const gchar * field, GvnParam * param)
{	
	g_return_if_fail (DB_IS_ITERATOR (obj));
	g_return_if_fail (GVN_IS_PARAM (param));
	g_return_if_fail (obj->model);
	g_return_if_fail (field);

	db_model_set_default_value_from_param (obj->model, field, param, TRUE);
}

/**
 * db_iterator_get_nrows:
 * @obj: a #DbIterator
 *
 * Gets the number of rows in the model pointed by the iterator.
 *
 * Return value: the number of rows
 **/
gint db_iterator_get_nrows (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), 0);
	
	if (obj->model)
		return db_model_get_nrows (obj->model);
		
	return 0;
}

/**
 * db_iterator_get_update_flags:
 * @obj: a #DbIterator
 *
 * Gets the flags that indicate how a model can be modified.
 *
 * Return value: the flags
 **/
DbModelUpdateFlags db_iterator_get_update_flags (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), 0);
	
	if (obj->model)
		return db_model_get_update_flags (obj->model);
		
	return 0;
}

/**
 * db_iterator_perform_operations:
 * @obj: a #DbIterator
 *
 * Performs all pending operations on the model.
 **/
void db_iterator_perform_operations (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	
	if (obj->model)
		db_model_perform_operations (obj->model, FALSE);
}

/**
 * db_iterator_reverse_operations:
 * @obj: a #DbIterator
 *
 * Reverses all pending operations on the model.
 **/
void db_iterator_reverse_operations (DbIterator * obj)
{
	g_return_if_fail (DB_IS_ITERATOR (obj));
	
	if (obj->model)
		db_model_reverse_operations (obj->model);
}

/**
 * db_iterator_get_pending_operations:
 * @obj: a #DbIterator
 *
 * Gets pending operations to perform in the current row.
 *
 * Return value: the pending operations
 **/
DbModelRowOp db_iterator_get_pending_operations (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), 0);
	
	if (obj->model && obj->row_selected)
		return db_model_get_row_operations (obj->model, &obj->iter);
		
	return 0;
}

/**
 * db_iterator_has_pending_operations:
 * @obj: a #DbIterator
 *
 * Checks for pending operations to perform.
 *
 * Return value: %TRUE if there are pending operations, %FALSE otherwise
 **/
gboolean db_iterator_has_pending_operations (DbIterator * obj)
{
	g_return_val_if_fail (DB_IS_ITERATOR (obj), FALSE);
	
	if (obj->model)
		return db_model_has_pending_operations (obj->model);
		
	return FALSE;
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties

enum
{
	 PROP_MODEL = 1
	,PROP_MODE
	,PROP_REMEMBER_SELECTION
	,PROP_CONN
	,PROP_STMT
	,PROP_SQL
	,PROP_USE_FILE
};

static void db_iterator_set_property (DbIterator * obj, guint id,
	const GValue * value, GParamSpec * pspec)
{
	switch (id)
	{
		case PROP_MODEL:
			db_iterator_set_model (obj, g_value_get_object (value));
			break;
		case PROP_MODE:
			db_iterator_set_mode (obj, g_value_get_enum (value));
			break;
		case PROP_REMEMBER_SELECTION:
			obj->remember_selection = g_value_get_boolean (value);
			break;
		case PROP_CONN:
			g_clear_object (&obj->conn);
			obj->conn = g_value_dup_object (value);
			db_iterator_try_create_model (obj);
			break;
		case PROP_STMT:
			g_clear_object (&obj->stmt);
			obj->stmt = g_value_dup_object (value);
			db_iterator_try_create_model (obj);
			break;
		case PROP_SQL:
			g_free (obj->sql);
			obj->sql = g_value_dup_string (value);
			db_iterator_try_create_model (obj);
			break;
		case PROP_USE_FILE:
			obj->use_file = g_value_get_boolean (value);
			db_iterator_try_create_model (obj);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

static void db_iterator_get_property (DbIterator * obj, guint id,
	GValue * value, GParamSpec * pspec)
{	
	switch (id)
	{
		case PROP_MODEL:
			g_value_set_object (value, obj->model);
			break;
		case PROP_MODE:
			g_value_set_enum (value, obj->mode);
			break;
		case PROP_REMEMBER_SELECTION:
			g_value_set_boolean (value, obj->remember_selection);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec);
	}
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class

static void db_iterator_init (DbIterator * obj)
{
	obj->model = NULL;
	obj->params = NULL;
	obj->row = 0;
	obj->row_selected = FALSE;
	obj->conn = NULL;
	obj->stmt = NULL;
	obj->sql = NULL;
	obj->use_file = FALSE;
}

static void db_iterator_finalize (DbIterator * obj)
{
	GList * n;
	
	for (n = obj->params; n; n = n->next)
		g_object_weak_unref (n->data,
			(GWeakNotify) db_iterator_unref_param, obj);

	g_list_free (obj->params);
	
	if (obj->model)
	{
		g_object_disconnect (obj->model
			,"any_signal", db_iterator_on_model_line_inserted, obj
			,"any_signal", db_iterator_on_model_line_updated_after, obj
			,"any_signal", db_iterator_on_model_line_deleted, obj
			,"any_signal", db_iterator_on_model_line_deleted_after, obj
			,"any_signal", db_iterator_on_model_lines_reordered, obj
			,"any_signal", db_iterator_on_model_status_changed, obj
			,"any_signal", db_iterator_on_model_operations_done, obj
			,NULL
		);
		g_object_unref (obj->model);
	}
	
	g_clear_object (&obj->conn);
	g_clear_object (&obj->stmt);
	g_free (obj->sql);

	G_OBJECT_CLASS (db_iterator_parent_class)->finalize (G_OBJECT (obj));
}

static void db_iterator_class_init (DbIteratorClass * klass)
{
	GObjectClass * k = G_OBJECT_CLASS (klass);
	k->set_property = (GObjectSetPropertyFunc) db_iterator_set_property;
	k->get_property = (GObjectGetPropertyFunc) db_iterator_get_property;
	k->finalize = (GObjectFinalizeFunc) db_iterator_finalize;

	signals[ITER_CHANGED] = g_signal_new ("iter-changed",
		DB_TYPE_ITERATOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0
	);
	signals[DATA_CHANGED] = g_signal_new ("data-changed",
		DB_TYPE_ITERATOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0
	);
	signals[ROW_NUM_CHANGED] = g_signal_new ("row-num-changed",
		DB_TYPE_ITERATOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0
	);
	signals[STATUS_CHANGED] = g_signal_new ("status-changed",
		DB_TYPE_ITERATOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
		g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN
	);
	signals[OPERATIONS_DONE] = g_signal_new ("operations-done",
		DB_TYPE_ITERATOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0
	);

	g_object_class_install_property (k, PROP_MODEL,
		g_param_spec_object ("model"
			,_("Model")
			,_("The DbModel handled by the iterator")
			,DB_TYPE_MODEL
			,G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_MODE,
		g_param_spec_enum ("mode"
			,_("Mode")
			,_("The mode in which the iterator is working")
			,DB_TYPE_ITERATOR_MODE
			,DB_ITERATOR_MODE_ON_CHANGE
			,G_PARAM_CONSTRUCT | G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_REMEMBER_SELECTION,
		g_param_spec_boolean ("remember-selection"
			,_("Remember selection")
			,_("Wether to rememeber the selection when model is refreshed")
			,TRUE
			,G_PARAM_CONSTRUCT | G_PARAM_READWRITE
	));
	g_object_class_install_property (k, PROP_CONN,
		g_param_spec_object ("conn"
			,_("Connection")
			,_("The connection used by the model")
			,DB_TYPE_CONN
			,G_PARAM_WRITABLE
	));
	g_object_class_install_property (k, PROP_STMT,
		g_param_spec_object ("stmt"
			,_("Statement")
			,_("The statement used to create the model")
			,SQL_TYPE_STMT
			,G_PARAM_WRITABLE
	));
	g_object_class_install_property (k, PROP_SQL,
		g_param_spec_string ("sql"
			,_("SQL")
			,_("The SQL query used to create the model")
			, NULL
			,G_PARAM_WRITABLE
	));
	g_object_class_install_property (k, PROP_USE_FILE,
		g_param_spec_boolean ("use-file"
			,_("Use file")
			,_("Wether to interpret the sql property as query file")
			,FALSE
			,G_PARAM_CONSTRUCT | G_PARAM_WRITABLE
	));
}

GType db_iterator_mode_get_type ()
{
	static GType type = 0;

	if (type == 0)
	{
		static const GEnumValue values[] =
		{
			 {DB_ITERATOR_MODE_ON_CHANGE, "DB_ITERATOR_MODE_ON_CHANGE", "on-change"}
			,{DB_ITERATOR_MODE_ON_ITER, "DB_ITERATOR_MODE_ON_ITER", "on-iter"}
			,{DB_ITERATOR_MODE_ON_DEMAND, "DB_ITERATOR_MODE_ON_DEMAND", "on-demand"}
			,{0, NULL, NULL}
		};

		type = g_enum_register_static
			(g_intern_static_string ("DbIteratorMode"), values);
	}

	return type;
}