/*
 * 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 "vn-model.h"

/**
 * vn_model_new:
 * @model: a #DbModel
 * 
 * Creates a new #VnModel with @model as the data model.
 * 
 * Return value: (transfer full): a #VnModel. 
 **/
VnModel * vn_model_new (DbModel * model)
{
	return g_object_new (VN_TYPE_MODEL, "model", model, NULL);
}

/**
 * vn_gtk_tree_iter_from_db_iter:
 * @dest_iter: the #GtkTreeIter to set using the data on @src_iter
 * @src_iter: a valid #DbIter
 * 
 * Sets a #GtkTreeIter with the data of a #DbIter. This function is mostly used
 * internally.
 **/
void gtk_tree_iter_from_db_iter (GtkTreeIter * dest_iter, const DbIter * src_iter)
{
	dest_iter->stamp = src_iter->stamp;
	dest_iter->user_data = src_iter->data;
}

/**
 * vn_gtk_tree_iter_to_db_iter:
 * @src_iter: a valid #GtkTreeIter
 * @dest_iter: the #DbIter to set using the data on @src_iter
 * 
 * Sets a with #DbIter the data of a #GtkTreeIter. This function is mostly used
 * internally.
 **/
void gtk_tree_iter_to_db_iter (const GtkTreeIter * src_iter, DbIter * dest_iter)
{
	dest_iter->stamp = src_iter->stamp;
	dest_iter->data = src_iter->user_data;
}

/**
 * vn_model_iter_is_valid:
 * @iter: a #GtkTreeIter
 * @model: a #GtkTreeModel
 * 
 * Checks if @iter is a valid #GtkTreeIter pointing inside #GtkTreeModel.
 **/
gboolean vn_model_iter_is_valid (GtkTreeIter * iter, GtkTreeModel * model)
{
	DbIter db_iter;

	g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);

	gtk_tree_iter_to_db_iter (iter, &db_iter);
	return db_model_iter_is_valid (&db_iter, VN_MODEL (model)->model);
}

static gint vn_model_get_nrows (GtkTreeModel * obj)
{
	return db_model_get_nrows (VN_MODEL (obj)->model);
}

//++++ Broadcast of GtkTreeModel and GtkTreeSortable signals:

static void vn_model_on_line_updated (DbModel * model, DbIter * db_iter, GtkTreeModel * obj)
{
	GtkTreeIter iter;
	gtk_tree_iter_from_db_iter (&iter, db_iter);

	GtkTreePath * path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path, db_model_get_path (model, db_iter));

	gtk_tree_model_row_changed (obj, path, &iter);

	gtk_tree_path_free (path);
}

static void vn_model_on_line_inserted (DbModel * model, DbIter * db_iter, GtkTreeModel * obj)
{
	GtkTreeIter iter;
	gtk_tree_iter_from_db_iter (&iter, db_iter);

	GtkTreePath * path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path,
		db_model_get_path (model, db_iter));
	
	gtk_tree_model_row_inserted (obj, path, &iter);

	gtk_tree_path_free (path);
}

static void vn_model_on_line_deleted (DbModel * model, gint position, GtkTreeModel * obj)
{
	GtkTreePath * path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path, position);

	if (gtk_tree_path_get_indices (path))
		gtk_tree_model_row_deleted  (obj, path);

	gtk_tree_path_free (path);
}

static void vn_model_on_lines_reordered (DbModel * model, guint col, gint * new_order, GtkTreeModel * obj)
{
	GtkTreePath * path = gtk_tree_path_new ();
	gtk_tree_model_rows_reordered (obj, path, NULL, new_order);
	gtk_tree_path_free (path);
}

static void vn_model_on_sort_changed (DbModel * model, GtkTreeSortable * obj)
{
	g_signal_emit_by_name (obj, "sort-column-changed");
}

//++++ Implementation of GtkTreeModel methods (using DbModel methods)

static GtkTreeModelFlags vn_model_get_flags (GtkTreeModel * obj)
{
	return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}

static gint vn_model_get_ncols (GtkTreeModel * obj)
{
	return db_model_get_ncols (VN_MODEL (obj)->model);
}

static GType vn_model_get_column_type (GtkTreeModel * obj, gint index_)
{
	return db_model_get_column_type (VN_MODEL (obj)->model, index_);
}

static gboolean vn_model_get_iter (GtkTreeModel * obj, GtkTreeIter * iter,
								GtkTreePath * path)
{
	DbIter db_iter;
	gint p = gtk_tree_path_get_indices (path)[0];

	gboolean ret = db_model_get_iter (VN_MODEL (obj)->model, &db_iter, p);

	if (ret)
		gtk_tree_iter_from_db_iter (iter, &db_iter);

	return ret;
}

static GtkTreePath * vn_model_get_path (GtkTreeModel * obj, GtkTreeIter * iter)
{
	GtkTreePath * path;
	DbIter db_iter;

	gtk_tree_iter_to_db_iter (iter, &db_iter);

	path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path,
		db_model_get_path (VN_MODEL (obj)->model, &db_iter));

	return path;
}

static void vn_model_get_value (GtkTreeModel * obj, GtkTreeIter * iter, 
					gint column, GValue * value)
{
	const GValue * v;
	DbIter db_iter;
	
	gtk_tree_iter_to_db_iter (iter, &db_iter);

	v = db_model_get_value (VN_MODEL (obj)->model, &db_iter, column, NULL);

	if (v)
	{
		g_value_init (value, G_VALUE_TYPE (v));
		g_value_copy (v, value);
	}
	else
		g_value_init (value, GVN_TYPE_NULL);
}

static gboolean vn_model_iter_next (GtkTreeModel * obj, GtkTreeIter * iter)
{
	DbIter db_iter;
	gtk_tree_iter_to_db_iter (iter, &db_iter);

	gboolean ret_val = db_model_iter_next (VN_MODEL (obj)->model, &db_iter);

	iter->user_data = db_iter.data;

	return ret_val;
}

static gboolean vn_model_iter_previous (GtkTreeModel * obj, GtkTreeIter * iter)
{
	DbIter db_iter;
	gtk_tree_iter_to_db_iter (iter, &db_iter);

	gboolean ret_val = db_model_iter_prev (VN_MODEL (obj)->model, &db_iter);

	iter->user_data = db_iter.data;
	
	return ret_val;
}

static gboolean vn_model_iter_children (GtkTreeModel * obj, GtkTreeIter * iter,
									GtkTreeIter * parent)
{	
	if (parent == NULL)
	{
		DbIter db_iter;
		
		gboolean ret_val = db_model_get_iter_first (VN_MODEL (obj)->model
			,&db_iter);

		if (ret_val)
			gtk_tree_iter_from_db_iter (iter, &db_iter);
		
		return ret_val;
	}

	return FALSE;
}

static gboolean vn_model_iter_has_child (GtkTreeModel * obj, GtkTreeIter * iter) 
{
	return FALSE;
}

static gint vn_model_iter_n_children (GtkTreeModel * obj, GtkTreeIter * iter)
{
	if (iter == NULL)
		return vn_model_get_nrows (obj);

	return 0;
}

static gboolean vn_model_iter_nth_child (GtkTreeModel * obj, GtkTreeIter * iter
							,GtkTreeIter * parent, gint n)
{
	if (parent == NULL)
	{
		DbIter db_iter;
		gboolean ret = db_model_get_iter (VN_MODEL (obj)->model, &db_iter, n);

		if (ret)
			gtk_tree_iter_from_db_iter (iter, &db_iter);
	}

	return FALSE;
}

static gboolean vn_model_iter_parent (GtkTreeModel * obj, GtkTreeIter * iter,
							GtkTreeIter * child)
{
	return FALSE;
}

//++++ Implementation of GtkTreeSortable methods (using DbModel methods)

static gboolean vn_model_get_sort_column_id (GtkTreeSortable * obj,
										gint * sort_column_id,
										GtkSortType * order)
{
	return db_model_get_sort_column_id (VN_MODEL (obj)->model
		,sort_column_id, (DbSortType *) order);
}

static void vn_model_set_sort_column_id (GtkTreeSortable * obj, gint sort_column_id,
									GtkSortType order)
{
	DbSortType db_order;

	if (order == GTK_SORT_ASCENDING)
		db_order = DB_SORT_ASCENDING;
	else
		db_order = DB_SORT_DESCENDING;

	db_model_set_sort_column_id (VN_MODEL (obj)->model, sort_column_id, db_order);
}

static void vn_model_set_sort_func (GtkTreeSortable *sortable, gint sort_column_id,
	GtkTreeIterCompareFunc sort_func, gpointer user_data, GDestroyNotify destroy)
{
	g_log (g_quark_to_string (VN_MODEL_LOG_DOMAIN)
		,G_LOG_LEVEL_WARNING, _("Function vn_model_set_sort_func not implemented"));
}

static void vn_model_set_default_sort_func (GtkTreeSortable *sortable,
	GtkTreeIterCompareFunc sort_func, gpointer user_data, GDestroyNotify destroy)
{
	g_log (g_quark_to_string (VN_MODEL_LOG_DOMAIN)
		,G_LOG_LEVEL_WARNING, _("Function vn_model_set_default_sort_func not implemented"));
}

static gboolean vn_model_has_default_sort_func (GtkTreeSortable * obj)
{
	return TRUE;
}

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

typedef enum
{
	PROP_MODEL = 1
}
VnModelProp;

static void vn_model_set_property (VnModel * obj, guint property_id,
	const GValue * value, GParamSpec * pspec)
{
	switch (property_id)
	{
		case PROP_MODEL:
			obj->model = g_value_get_object (value);
			g_object_ref_sink (obj->model);
			g_object_connect (obj->model
				,"signal-after::line-updated", vn_model_on_line_updated, obj
				,"signal::line-inserted", vn_model_on_line_inserted, obj
				,"signal-after::line-deleted", vn_model_on_line_deleted, obj
				,"signal::lines-reordered", vn_model_on_lines_reordered, obj
				,"signal::sort-changed", vn_model_on_sort_changed, obj, NULL);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
	}
}

static void vn_model_get_property (VnModel * obj, guint property_id,
	GValue * value, GParamSpec * pspec)
{
	switch (property_id)
	{
		case PROP_MODEL:
			g_value_set_object (value, obj->model);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
	}
}

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

static void vn_model_finalize (VnModel * obj)
{
	GObjectClass * parent;
	parent = g_type_class_peek_parent (VN_MODEL_GET_CLASS (obj));

	g_object_disconnect (obj->model
		,"any_signal", vn_model_on_line_updated, obj
		,"any_signal", vn_model_on_line_inserted, obj
		,"any_signal", vn_model_on_line_deleted, obj
		,"any_signal", vn_model_on_lines_reordered, obj
		,"any_signal", vn_model_on_sort_changed, obj, NULL);

	g_object_unref (obj->model);
	
	parent->finalize (G_OBJECT (obj));
}

static void vn_model_class_init (VnModelClass * k)
{
	GObjectClass * klass = G_OBJECT_CLASS (k);
	klass->set_property = (GObjectSetPropertyFunc) vn_model_set_property;
	klass->get_property = (GObjectGetPropertyFunc) vn_model_get_property;
	klass->finalize = (GObjectFinalizeFunc) vn_model_finalize;

	g_object_class_install_property (klass, PROP_MODEL,
		g_param_spec_object ("model"
			,"Model"
			,"The #DbModel with the data used by the #VnModel"
			,DB_TYPE_MODEL
			,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE
	));
}

// tienen que implementarse con métodos heredados de db-model
static void vn_model_tree_model_interface_init (GtkTreeModelIface * iface)
{
	iface->get_flags = vn_model_get_flags;	
	iface->get_n_columns = vn_model_get_ncols;
	iface->get_column_type = vn_model_get_column_type;
	iface->get_iter = vn_model_get_iter;
	iface->get_path = vn_model_get_path;
	iface->get_value = vn_model_get_value;
	iface->iter_next = vn_model_iter_next;
	iface->iter_previous = vn_model_iter_previous;

	iface->iter_children = vn_model_iter_children;
	iface->iter_has_child = vn_model_iter_has_child;
	iface->iter_n_children = vn_model_iter_n_children;
	iface->iter_nth_child = vn_model_iter_nth_child;
	iface->iter_parent = vn_model_iter_parent;
}

static void vn_model_tree_sortable_interface_init (GtkTreeSortableIface * iface)
{
	iface->get_sort_column_id = vn_model_get_sort_column_id;
	iface->set_sort_column_id = vn_model_set_sort_column_id;
	iface->set_sort_func = vn_model_set_sort_func;
	iface->set_default_sort_func = vn_model_set_default_sort_func;
	iface->has_default_sort_func = vn_model_has_default_sort_func;

}

static void vn_model_init (VnModel * obj)
{
	obj->model = NULL;
}

G_DEFINE_TYPE_WITH_CODE (VnModel, vn_model, G_TYPE_OBJECT,
	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
		vn_model_tree_model_interface_init)
	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_SORTABLE,
		vn_model_tree_sortable_interface_init)
);