/* gstcdplay
 * Copyright (c) 2002 Charles Schmidt <cbschmid@uiuc.edu> 
 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

/* #define GST_DEBUG_ENABLED */

#include "gstcdplayer.h"

/* props */
enum {
	ARG_0,
	ARG_DEVICE,
	ARG_NUM_TRACKS,
	ARG_START_TRACK,
	ARG_END_TRACK,
	ARG_CURRENT_TRACK,
	ARG_CDDB_DISCID,
};

/* signals */
enum {
	TRACK_CHANGE,
	LAST_SIGNAL,
};

static void cdplayer_class_init(CDPlayerClass *klass);
static void cdplayer_init(CDPlayer *cdp);
static void cdplayer_set_property(GObject *object,guint prop_id,const GValue *value,GParamSpec *spec);
static void cdplayer_get_property(GObject *object,guint prop_id,GValue *value,GParamSpec *spec);
static void cdplayer_dispose(GObject *object);
static gboolean cdplayer_iterate(GstBin *bin);
static GstElementStateReturn cdplayer_change_state(GstElement *element);

static gboolean plugin_init(GModule *module,GstPlugin *plugin);



static GstElementClass *parent_class;
 static guint cdplayer_signals[LAST_SIGNAL] = {0};


static GstElementDetails cdplayer_details = {
	"CD Player",
	"Generic/Bin",
	"LGPL",
	"Play CD audio through the CD Drive",
	VERSION,
	"Charles Schmidt <cbschmid@uiuc.edu>",
	"(C) 2002",
};



GType cdplayer_get_type(void)
{
	static GType cdplayer_type = 0;

	if (!cdplayer_type) {
		static const GTypeInfo cdplayer_info = {
			sizeof(CDPlayerClass),
			NULL,
			NULL,
			(GClassInitFunc)cdplayer_class_init,
			NULL,
			NULL,
			sizeof(CDPlayer),
			0,
			(GInstanceInitFunc)cdplayer_init,
			NULL
		};

		cdplayer_type = g_type_register_static(GST_TYPE_BIN,"CDPlayer",&cdplayer_info,0);
	}

	return cdplayer_type;
}

static void cdplayer_class_init(CDPlayerClass *klass)
{
	GObjectClass *gobject_klass;
	GstElementClass *gstelement_klass;
	GstBinClass *gstbin_klass;

	gobject_klass = (GObjectClass *)klass;
	gstelement_klass = (GstElementClass *)klass;
	gstbin_klass = (GstBinClass *)klass;

	parent_class = g_type_class_ref(gst_bin_get_type());

	gobject_klass->dispose = GST_DEBUG_FUNCPTR(cdplayer_dispose);

	gstelement_klass->change_state = GST_DEBUG_FUNCPTR(cdplayer_change_state);
	gstbin_klass->iterate = GST_DEBUG_FUNCPTR(cdplayer_iterate);

	g_object_class_install_property(gobject_klass,ARG_DEVICE,g_param_spec_string("device","device","CDROM device",NULL,G_PARAM_READWRITE));
	g_object_class_install_property(gobject_klass,ARG_NUM_TRACKS,g_param_spec_int("num_tracks","num_tracks","Number of Tracks",G_MININT,G_MAXINT,0,G_PARAM_READABLE));
	g_object_class_install_property(gobject_klass,ARG_START_TRACK,g_param_spec_int("start_track","start_track","Track to start playback on",1,CDPLAYER_MAX_TRACKS-1,1,G_PARAM_READWRITE));
	g_object_class_install_property(gobject_klass,ARG_END_TRACK,g_param_spec_int("end_track","end_track","Track to end playback on",0,CDPLAYER_MAX_TRACKS-1,0,G_PARAM_READWRITE));
	g_object_class_install_property(gobject_klass,ARG_CURRENT_TRACK,g_param_spec_int("current_track","current_track","Current track playing",1,CDPLAYER_MAX_TRACKS-1,1,G_PARAM_READABLE));
	g_object_class_install_property(gobject_klass,ARG_CDDB_DISCID,g_param_spec_uint("cddb_discid","cddb_discid","CDDB Disc ID",0,G_MAXUINT,1,G_PARAM_READABLE));

	cdplayer_signals[TRACK_CHANGE] = g_signal_new("track_change",G_TYPE_FROM_CLASS(klass),G_SIGNAL_RUN_LAST,G_STRUCT_OFFSET(CDPlayerClass,track_change),NULL,NULL,gst_marshal_VOID__INT,G_TYPE_NONE,1,G_TYPE_INT);

	gobject_klass->set_property = cdplayer_set_property;
	gobject_klass->get_property = cdplayer_get_property;

	return;
}

static void cdplayer_init(CDPlayer *cdp)
{
	cdp->device = g_strdup("/dev/cdrom");
	cdp->num_tracks = -1;
	cdp->start_track = 1;
	cdp->end_track = 0;
	cdp->current_track = 1;

	cdp->paused = FALSE;

//	GST_FLAG_SET(cdp,GST_BIN_FLAG_MANAGER);

	return;
}

static void cdplayer_set_property(GObject *object,guint prop_id,const GValue *value,GParamSpec *spec)
{
	CDPlayer *cdp;

	g_return_if_fail(GST_IS_CDPLAYER(object));

	cdp = CDPLAYER(object);

	switch (prop_id) {
		case ARG_DEVICE:
// FIXME prolly should uhh.. stop it or something
			if (cdp->device) {
				g_free(cdp->device);
			} 
			
			cdp->device = g_strdup(g_value_get_string(value));
			break;
		case ARG_START_TRACK:
// FIXME prolly should uhh.. restart play, i guess... or something whatever
// FIXME we should only set current_track if its not playing...
			cdp->current_track = cdp->start_track = g_value_get_int(value);
			break;
		case ARG_END_TRACK:
// FIXME prolly should restart play, maybe, or try to set it without interrupt..
			cdp->end_track = g_value_get_int(value);
			break;
		default:
			break;
	}

	return;
}


static void cdplayer_get_property(GObject *object,guint prop_id,GValue *value,GParamSpec *spec)
{
	CDPlayer *cdp;

	g_return_if_fail(GST_IS_CDPLAYER(object));

	cdp = CDPLAYER(object);

	switch (prop_id) {
		case ARG_DEVICE:
			g_value_set_string(value,cdp->device);
			break;
		case ARG_NUM_TRACKS:
			g_value_set_int(value,cdp->num_tracks);
			break;
		case ARG_START_TRACK:
			g_value_set_int(value,cdp->start_track);
			break;
		case ARG_END_TRACK:
			g_value_set_int(value,cdp->end_track);
		case ARG_CURRENT_TRACK:
			g_value_set_int(value,cdp->current_track);
			break;	
		case ARG_CDDB_DISCID:
			g_value_set_uint(value,cdp->cddb_discid);
		default:
			break;
	}

	return;
}

static void cdplayer_dispose(GObject *object)
{
	CDPlayer *cdp;

	g_return_if_fail(GST_IS_CDPLAYER(object));

	cdp = CDPLAYER(object);
	g_free(cdp->device);

	if (G_OBJECT_CLASS(parent_class)->dispose) {
		G_OBJECT_CLASS(parent_class)->dispose(object);
	}

	return;
}

static gboolean cdplayer_iterate(GstBin *bin)
{
	CDPlayer *cdp = CDPLAYER(bin);
	gint current_track;

	switch (cd_status(CDPLAYER_CD(cdp))) {
		case CD_PLAYING:
			current_track = cd_current_track(CDPLAYER_CD(cdp));
			if (current_track > cdp->end_track && cdp->end_track != 0) {
				return FALSE;
			}

			if (current_track != -1 && current_track != cdp->current_track) {
				cdp->current_track = current_track;
				g_signal_emit(G_OBJECT(cdp),cdplayer_signals[TRACK_CHANGE],0,cdp->current_track);
			}

			return TRUE;
			break;
		case CD_ERROR:
			gst_element_set_state(GST_ELEMENT(bin),GST_STATE_PAUSED);
			return FALSE;
			break;
		case CD_COMPLETED:
			gst_element_set_state(GST_ELEMENT(bin),GST_STATE_PAUSED);
			gst_element_set_eos(GST_ELEMENT(bin));
			return FALSE;
			break;
	}

	return FALSE;	
}


static GstElementStateReturn cdplayer_change_state(GstElement *element)
{
	CDPlayer *cdp;
	GstElementState state = GST_STATE(element);
	GstElementState pending = GST_STATE_PENDING(element);

	g_return_val_if_fail(GST_IS_CDPLAYER(element),GST_STATE_FAILURE);

	cdp = CDPLAYER(element);

	switch (pending) {
		case GST_STATE_READY:
			if (state != GST_STATE_PAUSED) {
				if (cd_init(CDPLAYER_CD(cdp),cdp->device) == FALSE) {
					return GST_STATE_FAILURE;
				}
				cdp->num_tracks = cdp->cd.num_tracks;
				cdp->cddb_discid = cd_cddb_discid(CDPLAYER_CD(cdp));
			}
			break;
		case GST_STATE_PAUSED:
			/* ready->paused is not useful */
			if (state != GST_STATE_READY) {
				if (cd_pause(CDPLAYER_CD(cdp)) == FALSE) {
					return GST_STATE_FAILURE;
				}

				cdp->paused = TRUE;
			}

			break;
		case GST_STATE_PLAYING:
			if (cdp->paused == TRUE) {
				if (cd_resume(CDPLAYER_CD(cdp)) == FALSE) {
					return GST_STATE_FAILURE;
				}

				cdp->paused = FALSE;
			} else {
				if (cd_start(CDPLAYER_CD(cdp),cdp->start_track,cdp->end_track) == FALSE) {
					return GST_STATE_FAILURE;
				}
			}

			break;
		case GST_STATE_NULL:
			/* stop & close fd */
			if (cd_stop(CDPLAYER_CD(cdp)) == FALSE || cd_close(CDPLAYER_CD(cdp)) == FALSE) {
				return GST_STATE_FAILURE;
			}

			break;
		default:
			break;
	}

	if (GST_ELEMENT_CLASS(parent_class)->change_state) {
		GST_ELEMENT_CLASS(parent_class)->change_state(element);
	}

	return GST_STATE_SUCCESS;
}


static gboolean plugin_init(GModule *module,GstPlugin *plugin)
{
	GstElementFactory *factory;

	factory = gst_element_factory_new("cdplayer",GST_TYPE_CDPLAYER,&cdplayer_details);
	g_return_val_if_fail(factory != NULL,FALSE);
	gst_plugin_add_feature(plugin,GST_PLUGIN_FEATURE(factory));

	gst_plugin_set_longname(plugin,"CD Player");
	
	return TRUE;

}

GstPluginDesc plugin_desc = {
    GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    "cdplayer",
    plugin_init
};