/*  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.
 */

#include <xmmsclient/xmmsclient.h>
#include <xmmsc/xmmsc_idnumbers.h>

#include <ruby.h>

#include "rb_collection.h"
#include "rb_xmmsclient.h"
#include "rb_result.h"

#define DEF_CONST(mod, prefix, name) \
	rb_define_const ((mod), #name, \
	                 INT2FIX (prefix##name));

#define CHECK_IS_COLL(o) \
	if (!rb_obj_is_kind_of ((o), cColl)) \
		rb_raise (rb_eTypeError, \
		          "wrong argument type %s (expected Collection)", \
		          rb_obj_classname ((o)));

#define COLL_METHOD_HANDLER_HEADER \
	RbCollection *coll = NULL; \
\
	Data_Get_Struct (self, RbCollection, coll); \

#define COLL_METHOD_ADD_HANDLER_RET(action) \
	COLL_METHOD_HANDLER_HEADER \
\
	ret = xmmsc_coll_##action (coll->real); \

#define COLL_METHOD_ADD_HANDLER_UINT(action, arg1) \
	COLL_METHOD_HANDLER_HEADER \
\
	xmmsc_coll_##action (coll->real, check_uint32 (arg1)); \

typedef struct {
	VALUE attributes;
	VALUE operands;
	xmmsc_coll_t *real;
} RbCollection;

static VALUE cColl, cAttributes, cOperands;
static VALUE eCollectionError, eDisconnectedError, eClientError;

static void
c_free (RbCollection *coll)
{
	xmmsc_coll_unref (coll->real);

	free (coll);
}

static void
c_mark (RbCollection *coll)
{
	rb_gc_mark (coll->attributes);
	rb_gc_mark (coll->operands);
}

static VALUE
c_alloc (VALUE klass)
{
	VALUE obj;
	RbCollection *coll = NULL;

	obj = Data_Make_Struct (klass, RbCollection, c_mark, c_free, coll);

	coll->attributes = Qnil;
	coll->operands = Qnil;

	return obj;
}

VALUE
TO_XMMS_CLIENT_COLLECTION (xmmsc_coll_t *coll)
{
	VALUE self = rb_obj_alloc (cColl);
	RbCollection *rbcoll = NULL;

	Data_Get_Struct (self, RbCollection, rbcoll);

	rbcoll->real = coll;

	/* should we call initialize here?
	 * the problem is, our initialize method creates a new collection,
	 * which we don't want.
	 * otoh i don't know whether we have a sane Ruby object yet here.
	 *
	 * rb_obj_call_init (self, 0, NULL);
	 */

	return self;
}

xmmsc_coll_t *
FROM_XMMS_CLIENT_COLLECTION (VALUE rbcoll)
{
	RbCollection *coll = NULL;

	CHECK_IS_COLL (rbcoll);
	Data_Get_Struct (rbcoll, RbCollection, coll);

	return coll->real;
}

/* call-seq:
 * c = Xmms::Collection.new(type)
 *
 * Returns a new Xmms::Collection object of type _type_.
 */
static VALUE
c_coll_init (VALUE self, VALUE type)
{
	COLL_METHOD_HANDLER_HEADER

	coll->real = xmmsc_coll_new (check_uint32 (type));

	return self;
}

/* call-seq:
 * c = Xmms::Collection.universe
 *
 * Returns a collection referencing the "All Media" set.
 */
static VALUE
c_coll_universe (VALUE klass)
{
	VALUE obj = rb_obj_alloc (klass);
	RbCollection *coll = NULL;

	Data_Get_Struct (obj, RbCollection, coll);

	coll->real = xmmsc_coll_universe ();

	return obj;
}

/* call-seq:
 * c.type
 *
 * Returns the type of the collection as an Integer.
 */
static VALUE
c_coll_type_get (VALUE self)
{
	xmmsc_coll_type_t ret;

	COLL_METHOD_ADD_HANDLER_RET (get_type)

	return UINT2NUM (ret);
}

/* call-seq:
 * c.operands
 *
 * Gets a list of the operands that make up the collection.
 */
static VALUE
c_coll_operands (VALUE self)
{
	RbCollection *coll = NULL;

	Data_Get_Struct (self, RbCollection, coll);

	if (NIL_P (coll->operands))
		coll->operands = rb_class_new_instance (1, &self, cOperands);

	return coll->operands;
}

/* call-seq:
 * c.idlist
 *
 * Gets the list of media ids that make up the collection.
 */
static VALUE
c_coll_idlist_get (VALUE self)
{
	int i;
	VALUE ary = rb_ary_new ();
	uint32_t *ret = NULL;

	COLL_METHOD_ADD_HANDLER_RET (get_idlist)

	for (i = 0; ret[i]; i++)
		rb_ary_push (ary, UINT2NUM (ret[i]));

	return ary;
}

/* call-seq:
 * c.idlist=(_idlist_)
 *
 * Sets the list of media ids that make up the collection.
 */
static VALUE
c_coll_idlist_set (VALUE self, VALUE ids)
{
	int i;
	unsigned int *ary = NULL;
	struct RArray *rb_ary = NULL;

	Check_Type (ids, T_ARRAY);
	COLL_METHOD_HANDLER_HEADER

	rb_ary = RARRAY (ids);
	ary = malloc (sizeof (unsigned int *) * (rb_ary->len + 1));

	for (i = 0; i < rb_ary->len; i++)
		ary[i] = NUM2UINT (rb_ary->ptr[i]);

	ary[i] = 0;

	xmmsc_coll_set_idlist (coll->real, ary);

	return self;
}

/* call-seq:
 * c.attributes
 *
 * Returns the attributes of the collection.
 */
static VALUE
c_coll_attributes (VALUE self)
{
	RbCollection *coll = NULL;

	Data_Get_Struct (self, RbCollection, coll);

	if (NIL_P (coll->attributes))
		coll->attributes = rb_class_new_instance (1, &self, cAttributes);

	return coll->attributes;
}

static VALUE
c_attrs_init (VALUE self, VALUE collection)
{
	rb_iv_set (self, "collection", collection);

	return self;
}

static VALUE
attrs_inspect_cb (VALUE args, VALUE s)
{
	VALUE key, value;

	key = RARRAY (args)->ptr[0];
	value = RARRAY (args)->ptr[1];

	if (RSTRING (s)->len > 1)
		rb_str_buf_cat2 (s, ", ");

	rb_str_buf_append (s, rb_inspect (key));
	rb_str_buf_cat2 (s, "=>");
	rb_str_buf_append (s, rb_inspect (value));

	return Qnil;
}

static VALUE
attrs_inspect (VALUE self)
{
	VALUE ret;

	ret = rb_str_new2 ("{");
	rb_iterate (rb_each, self, attrs_inspect_cb, ret);
	rb_str_buf_cat2 (ret, "}");

	OBJ_INFECT (ret, self);

	return ret;
}

static VALUE
c_attrs_inspect (VALUE self)
{
	return rb_protect_inspect (attrs_inspect, self, 0);
}

static VALUE
c_attrs_aref (VALUE self, VALUE key)
{
	RbCollection *coll = NULL;
	VALUE tmp;
	int s;
	char *value;

	StringValue (key);

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	s = xmmsc_coll_attribute_get (coll->real, StringValuePtr (key), &value);
	if (!s)
		return Qnil;

	return rb_str_new2 (value);
}

static VALUE
c_attrs_aset (VALUE self, VALUE key, VALUE value)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	StringValue (key);
	StringValue (value);

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	xmmsc_coll_attribute_set (coll->real, StringValuePtr (key),
	                          StringValuePtr (value));

	return Qnil;
}

static VALUE
c_attrs_has_key (VALUE self, VALUE key)
{
	RbCollection *coll = NULL;
	VALUE tmp;
	int s;

	StringValue (key);

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	s = xmmsc_coll_attribute_get (coll->real, StringValuePtr (key), NULL);

	return s ? Qtrue : Qfalse;
}

static VALUE
c_attrs_delete (VALUE self, VALUE key)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	StringValue (key);

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	xmmsc_coll_attribute_remove (coll->real, StringValuePtr (key));

	return Qnil;
}

static void
attr_each (const char *key, const char *value, void *udata)
{
	switch (XPOINTER_TO_INT (udata)) {
		case EACH_PAIR:
			rb_yield_values (2, rb_str_new2 (key), rb_str_new2 (value));
			break;
		case EACH_KEY:
			rb_yield_values (1, rb_str_new2 (key));
			break;
		case EACH_VALUE:
			rb_yield_values (1, rb_str_new2 (value));
			break;
	}
}

static VALUE
c_attrs_each (VALUE self)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	xmmsc_coll_attribute_foreach (coll->real, attr_each,
	                               XINT_TO_POINTER (EACH_PAIR));

	return self;
}

static VALUE
c_attrs_each_key (VALUE self)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	xmmsc_coll_attribute_foreach (coll->real, attr_each,
	                               XINT_TO_POINTER (EACH_KEY));

	return self;
}

static VALUE
c_attrs_each_value (VALUE self)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	xmmsc_coll_attribute_foreach (coll->real, attr_each,
	                               XINT_TO_POINTER (EACH_VALUE));

	return self;
}

static VALUE
c_operands_init (VALUE self, VALUE collection)
{
	rb_iv_set (self, "collection", collection);

	return self;
}

static VALUE
c_operands_push (VALUE self, VALUE arg)
{
	RbCollection *coll = NULL, *coll2 = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	Data_Get_Struct (arg, RbCollection, coll2);

	xmmsc_coll_add_operand (coll->real, coll2->real);

	return self;
}

static VALUE
c_operands_delete (VALUE self, VALUE arg)
{
	RbCollection *coll = NULL, *coll2 = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	Data_Get_Struct (arg, RbCollection, coll2);

	xmmsc_coll_remove_operand (coll->real, coll2->real);

	return Qnil;
}

static VALUE
c_operands_each (VALUE self)
{
	RbCollection *coll = NULL;
	VALUE tmp;

	tmp = rb_iv_get (self, "collection");
	Data_Get_Struct (tmp, RbCollection, coll);

	if (!xmmsc_coll_operand_list_first (coll->real))
		return self;

	while (xmmsc_coll_operand_list_valid (coll->real)) {
		xmmsc_coll_t *operand = NULL;

		xmmsc_coll_operand_list_entry (coll->real, &operand);
		xmmsc_coll_ref (operand);

		rb_yield (TO_XMMS_CLIENT_COLLECTION (operand));

		xmmsc_coll_operand_list_next (coll->real);
	}

	return self;
}

void
Init_Collection (VALUE mXmms)
{
	cColl = rb_define_class_under (mXmms, "Collection", rb_cObject);

	rb_define_alloc_func (cColl, c_alloc);

	rb_define_singleton_method (cColl, "universe", c_coll_universe, 0);

	rb_define_method (cColl, "initialize", c_coll_init, 1);

	/* type methods */
	rb_define_method (cColl, "type", c_coll_type_get, 0);

	/* idlist methods */
	rb_define_method (cColl, "idlist", c_coll_idlist_get, 0);
	rb_define_method (cColl, "idlist=", c_coll_idlist_set, 1);

	/* operand methods */
	rb_define_method (cColl, "operands", c_coll_operands, 0);

	/* attribute methods */
	rb_define_method (cColl, "attributes", c_coll_attributes, 0);

	rb_define_const (cColl, "NS_ALL", rb_str_new2 (XMMS_COLLECTION_NS_ALL));
	rb_define_const (cColl, "NS_COLLECTIONS",
	                 rb_str_new2 (XMMS_COLLECTION_NS_COLLECTIONS));
	rb_define_const (cColl, "NS_PLAYLISTS",
	                 rb_str_new2 (XMMS_COLLECTION_NS_PLAYLISTS));
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_REFERENCE)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_UNION)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_INTERSECTION)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_COMPLEMENT)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_HAS)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_EQUALS)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_MATCH)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_SMALLER)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_GREATER)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_IDLIST)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_QUEUE)
	DEF_CONST (cColl, XMMS_COLLECTION_, TYPE_PARTYSHUFFLE)

	eCollectionError = rb_define_class_under (cColl, "CollectionError",
	                                        rb_eStandardError);
	eClientError = rb_define_class_under (cColl, "ClientError",
	                                      rb_eStandardError);
	eDisconnectedError = rb_define_class_under (cColl, "DisconnectedError",
	                                            eClientError);

	cAttributes = rb_define_class_under (cColl, "Attributes", rb_cObject);

	rb_define_method (cAttributes, "initialize", c_attrs_init, 1);
	rb_define_method (cAttributes, "inspect", c_attrs_inspect, 0);

	rb_define_method (cAttributes, "[]", c_attrs_aref, 1);
	rb_define_method (cAttributes, "[]=", c_attrs_aset, 2);
	rb_define_method (cAttributes, "has_key?", c_attrs_has_key, 1);
	rb_define_method (cAttributes, "delete", c_attrs_delete, 1);
	rb_define_method (cAttributes, "each", c_attrs_each, 0);
	rb_define_method (cAttributes, "each_key", c_attrs_each_key, 0);
	rb_define_method (cAttributes, "each_value", c_attrs_each_value, 0);

	rb_define_alias (cAttributes, "include?", "has_key?");
	rb_define_alias (cAttributes, "key?", "has_key?");
	rb_define_alias (cAttributes, "member?", "has_key?");
	rb_define_alias (cAttributes, "each_pair", "each");

	rb_include_module (cAttributes, rb_mEnumerable);

	cOperands = rb_define_class_under (cColl, "Operands", rb_cObject);

	rb_define_method (cOperands, "initialize", c_operands_init, 1);
	rb_define_method (cOperands, "push", c_operands_push, 1);
	rb_define_method (cOperands, "delete", c_operands_delete, 1);
	rb_define_method (cOperands, "each", c_operands_each, 0);

	rb_define_alias (cOperands, "<<", "push");

	rb_include_module (cOperands, rb_mEnumerable);
}
