/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2007 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 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
 *  Lesser General Public License for more details.
 */


/** @file
 *  Functions to serialize (save/restore) collections.
 */

#include "xmmspriv/xmms_collserial.h"
#include "xmmspriv/xmms_collection.h"
#include "xmmspriv/xmms_medialib.h"


/* Internal helper structures */

typedef struct {
	xmms_medialib_session_t *session;
	guint collid;
	xmms_collection_namespace_id_t nsid;
} coll_dbwrite_t;


static xmmsc_coll_t *xmms_collection_dbread_operator (xmms_medialib_session_t *session, gint id, xmmsc_coll_type_t type);
static guint xmms_collection_dbwrite_operator (xmms_medialib_session_t *session, guint collid, xmmsc_coll_t *coll);

static void dbwrite_operator (void *key, void *value, void *udata);
static void dbwrite_coll_attributes (const char *key, const char *value, void *udata);
static void dbwrite_strip_tmpprops (void *key, void *value, void *udata);

static gint cmdval_get_dict_int (xmms_object_cmd_value_t *cmdval, const gchar *key);
static const gchar *cmdval_get_dict_string (xmms_object_cmd_value_t *cmdval, const gchar *key);



/** Save the collection DAG in the database.
 *
 * @param dag  The collection DAG to save.
 */
void
xmms_collection_dag_save (xmms_coll_dag_t *dag)
{
	gint i;
	xmms_medialib_session_t *session;

	session = xmms_medialib_begin ();

	/* Empty Collection* tables */
	xmms_medialib_select (session, "DELETE FROM CollectionAttributes", NULL);
	xmms_medialib_select (session, "DELETE FROM CollectionConnections", NULL);
	xmms_medialib_select (session, "DELETE FROM CollectionIdlists", NULL);
	xmms_medialib_select (session, "DELETE FROM CollectionLabels", NULL);
	xmms_medialib_select (session, "DELETE FROM CollectionOperators", NULL);

	/* Write all collections in all namespaces */
	coll_dbwrite_t dbinfos = { session, 1, 0 }; /* ids start at 1 */
	for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
		dbinfos.nsid = i;
		xmms_collection_foreach_in_namespace (dag, i, dbwrite_operator, &dbinfos);
	}

	xmms_collection_foreach_in_namespace (dag, XMMS_COLLECTION_NSID_ALL,
	                                      dbwrite_strip_tmpprops, NULL);

	xmms_medialib_end (session);
}

/** Restore the collection DAG from the database.
 *
 * @param dag  The collection DAG to restore to.
 */
void
xmms_collection_dag_restore (xmms_coll_dag_t *dag)
{
	xmmsc_coll_t *coll = NULL;
	xmms_medialib_session_t *session;
	xmms_object_cmd_value_t *cmdval;
	gchar *query;
	GList *res;
	GList *n;
	gint previd;

	session = xmms_medialib_begin ();

	/* Fetch all label-coll_operator for all namespaces, register in table */
	query = "SELECT op.id AS id, lbl.name AS label, "
	        "       lbl.namespace AS nsid, op.type AS type "
	        "FROM CollectionOperators AS op, CollectionLabels as lbl "
	        "WHERE op.id=lbl.collid "
	        "ORDER BY id";
	res = xmms_medialib_select (session, query, NULL);

	previd = -1;
	for (n = res; n; n = n->next) {
		gint id, type, nsid;
		const gchar *label;

		cmdval = (xmms_object_cmd_value_t*)n->data;
		id = cmdval_get_dict_int (cmdval, "id");
		type = cmdval_get_dict_int (cmdval, "type");
		nsid = cmdval_get_dict_int (cmdval, "nsid");
		label = cmdval_get_dict_string (cmdval, "label");

		/* Do not duplicate operator if same id */
		if (previd < 0 || id != previd) {
			coll = xmms_collection_dbread_operator (session, id, type);
			previd = id;
		}
		else {
			xmmsc_coll_ref (coll);  /* New label references the coll */
		}
		xmms_collection_dag_replace (dag, nsid, g_strdup (label), coll);
	}

	xmms_medialib_end (session);

	/* FIXME: validate ? */

	/* Link references in collections to actual operators */
	xmms_collection_apply_to_all_collections (dag, bind_all_references, NULL);
}

/** Given a collection id, query the DB to build the corresponding
 *  collection DAG.
 *
 * @param session  The medialib session connected to the DB.
 * @param id  The id of the collection to create.
 * @param type  The type of the collection operator.
 * @return  The created collection DAG.
 */
static xmmsc_coll_t *
xmms_collection_dbread_operator (xmms_medialib_session_t *session,
                                 gint id, xmmsc_coll_type_t type)
{
	xmmsc_coll_t *coll;
	xmmsc_coll_t *op;
	GList *res;
	GList *n;
	xmms_object_cmd_value_t *cmdval;
	gchar *query;

	coll = xmmsc_coll_new (type);

	/* Retrieve the attributes */
	query = g_strdup_printf ("SELECT attr.key AS key, attr.value AS value "
	                         "FROM CollectionOperators AS op, CollectionAttributes AS attr "
	                         "WHERE op.id=%d AND attr.collid=op.id", id);
	res = xmms_medialib_select (session, query, NULL);
	for (n = res; n; n = n->next) {
		const gchar *key, *value;

		cmdval = (xmms_object_cmd_value_t*)n->data;
		key = cmdval_get_dict_string (cmdval, "key");
		value = cmdval_get_dict_string (cmdval, "value");
		xmmsc_coll_attribute_set (coll, key, value);

		xmms_object_cmd_value_free (n->data);
	}
	g_list_free (res);
	g_free (query);

	/* Retrieve the idlist */
	query = g_strdup_printf ("SELECT idl.mid AS mid "
	                         "FROM CollectionOperators AS op, CollectionIdlists AS idl "
	                         "WHERE op.id=%d AND idl.collid=op.id "
	                         "ORDER BY idl.position", id);
	res = xmms_medialib_select (session, query, NULL);
	for (n = res; n; n = n->next) {

		cmdval = (xmms_object_cmd_value_t*)n->data;
		xmmsc_coll_idlist_append (coll, cmdval_get_dict_int (cmdval, "mid"));

		xmms_object_cmd_value_free (n->data);
	}
	g_list_free (res);
	g_free (query);

	/* Retrieve the operands */
	query = g_strdup_printf ("SELECT op.id AS id, op.type AS type "
	                         "FROM CollectionOperators AS op, CollectionConnections AS conn "
	                         "WHERE conn.to_id=%d AND conn.from_id=op.id", id);
	res = xmms_medialib_select (session, query, NULL);
	for (n = res; n; n = n->next) {
		gint id;
		gint type;

		cmdval = (xmms_object_cmd_value_t*)n->data;
		id = cmdval_get_dict_int (cmdval, "id");
		type = cmdval_get_dict_int (cmdval, "type");

		op = xmms_collection_dbread_operator (session, id, type);
		xmmsc_coll_add_operand (coll, op);

		xmmsc_coll_unref (op);
		xmms_object_cmd_value_free (n->data);
	}
	g_list_free (res);
	g_free (query);

	return coll;
}

/** Write the given operator to the database under the given id.
 *
 * @param session  The medialib session connected to the DB.
 * @param collid  The id under which to save the collection.
 * @param coll  The structure of the collection to save.
 * @return  The next free collection id.
 */
static guint
xmms_collection_dbwrite_operator (xmms_medialib_session_t *session,
                                  guint collid, xmmsc_coll_t *coll)
{
	gchar *query;
	guint *idlist;
	gint i;
	xmmsc_coll_t *op;
	gint newid, nextid;
	coll_dbwrite_t dbwrite_infos = { session, collid, 0 };

	/* Write operator */
	query = g_strdup_printf ("INSERT INTO CollectionOperators VALUES(%d, %d)",
	                         collid, xmmsc_coll_get_type (coll));
	xmms_medialib_select (session, query, NULL);
	g_free (query);

	/* Write attributes */
	xmmsc_coll_attribute_foreach (coll, dbwrite_coll_attributes, &dbwrite_infos);

	/* Write idlist */
	idlist = xmmsc_coll_get_idlist (coll);
	for (i = 0; idlist[i] != 0; i++) {
		query = g_strdup_printf ("INSERT INTO CollectionIdlists VALUES(%d, %d, %d)",
		                         collid, i, idlist[i]);
		xmms_medialib_select (session, query, NULL);
		g_free (query);
	}

	/* Save operands and connections (don't recurse in ref operand) */
	newid = collid + 1;
	if (xmmsc_coll_get_type (coll) != XMMS_COLLECTION_TYPE_REFERENCE) {
		xmmsc_coll_operand_list_save (coll);
		xmmsc_coll_operand_list_first (coll);
		while (xmmsc_coll_operand_list_entry (coll, &op)) {
			nextid = xmms_collection_dbwrite_operator (session, newid, op);
			query = g_strdup_printf ("INSERT INTO CollectionConnections VALUES(%d, %d)",
			                         newid, collid);
			xmms_medialib_select (session, query, NULL);
			g_free (query);
			newid = nextid;
			xmmsc_coll_operand_list_next (coll);
		}
		xmmsc_coll_operand_list_restore (coll);
	}

	/* return next available id */
	return newid;
}

/* For all label-operator pairs, write the operator and all its
 * operands to the DB recursively. */
static void
dbwrite_operator (void *key, void *value, void *udata)
{
	gchar *query;
	gchar *label = key;
	xmmsc_coll_t *coll = value;
	coll_dbwrite_t *dbinfos = udata;
	gchar *esc_label;
	gint serial_id;

	/* Only serialize each operator once, get previous id if exists */
	if (!xmms_collection_get_int_attr (coll, XMMS_COLLSERIAL_ATTR_ID, &serial_id)) {
		serial_id = dbinfos->collid;
		dbinfos->collid = xmms_collection_dbwrite_operator (dbinfos->session,
		                                                    dbinfos->collid, coll);
		xmms_collection_set_int_attr (coll, XMMS_COLLSERIAL_ATTR_ID, serial_id);
	}

	esc_label = sqlite_prepare_string (label);
	query = g_strdup_printf ("INSERT INTO CollectionLabels VALUES(%d, %d, %s)",
	                         serial_id, dbinfos->nsid, esc_label);
	xmms_medialib_select (dbinfos->session, query, NULL);

	g_free (query);
	g_free (esc_label);
}

/* Write all attributes of a collection to the DB. */
static void
dbwrite_coll_attributes (const char *key, const char *value, void *udata)
{
	gchar *query;
	coll_dbwrite_t *dbwrite_infos = udata;
	gchar *esc_key;
	gchar *esc_val;

	esc_key = sqlite_prepare_string (key);
	esc_val = sqlite_prepare_string (value);
	query = g_strdup_printf ("INSERT INTO CollectionAttributes VALUES(%d, %s, %s)",
	                         dbwrite_infos->collid, esc_key, esc_val);
	xmms_medialib_select (dbwrite_infos->session, query, NULL);

	g_free (query);
	g_free (esc_key);
	g_free (esc_val);
}

/* Remove all temp utility properties used to write collections to the DB. */
static void
dbwrite_strip_tmpprops (void *key, void *value, void *udata)
{
	xmmsc_coll_t *coll = value;
	xmmsc_coll_attribute_remove (coll, XMMS_COLLSERIAL_ATTR_ID);
}


/* Extract the int value out of a xmms_object_cmd_value_t object. */
static gint
cmdval_get_dict_int (xmms_object_cmd_value_t *cmdval, const gchar *key)
{
	xmms_object_cmd_value_t *buf;
	buf = g_hash_table_lookup (cmdval->value.dict, key);
	return buf->value.int32;
}

/* Extract the string value out of a xmms_object_cmd_value_t object. */
static const gchar *
cmdval_get_dict_string (xmms_object_cmd_value_t *cmdval, const gchar *key)
{
	xmms_object_cmd_value_t *buf;
	buf = g_hash_table_lookup (cmdval->value.dict, key);
	return buf->value.string;
}
