/***************************************************************************
 *   Copyright (C) 2004-2005 by Jürgen Kofler                              *
 *   kaffeine@gmx.net                                                      *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.           *
 ***************************************************************************/

/*
 *  GStreamer based Kaffeine MediaPart
 *  Based on kiss by Ronald Bultje <rbultje@ronald.bitfreak.net>
 *
 *  TODO:
 *     - DVD navigation
 *     - better position slider
 */

#include <kparts/genericfactory.h>
#include <kaction.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kxmlguifactory.h>
#include <ktoolbar.h>
#include <kio/netaccess.h>
#include <kmimetype.h>
#include <kpopupmenu.h>

#include <qtimer.h>
#include <qslider.h>
#include <qfile.h>
#include <qtooltip.h>

#include "gstreamer_part.h"
#include "gstreamer_part.moc"
#include "playlistimport.h"

#define TIMER_EVENT_PLAYBACK_FINISHED   100
#define TIMER_EVENT_ERROR               102
#define TIMER_EVENT_NEW_STATE           103
#define TIMER_EVENT_FOUND_TAG           104


typedef KParts::GenericFactory<GStreamerPart> GStreamerPartFactory;
K_EXPORT_COMPONENT_FACTORY (libgstreamerpart, GStreamerPartFactory);


GStreamerPart::GStreamerPart(QWidget* parentWidget, const char* /*widgetName*/, QObject* parent, const char* name,
                             const QStringList& /*args*/)
		: KMediaPart(parent, name ? name : "GStreamerPart"),
		m_play(NULL), m_videosink(NULL), m_audiosink(NULL), m_visual(NULL), m_videoSettings(NULL), m_gstConfig(NULL),
		m_gstReady(false), m_mute(false), m_logoPath(QString::null), m_posToolbar(NULL)
{
	// we need an instance
	setInstance(GStreamerPartFactory::instance());
	parentWidget->setPaletteBackgroundColor(QColor(0,0,0)); //black

	loadConfig();

	if (!initGStreamer())
	{
		kdError() << "GStreamerPart: Initializing of GStreamer failed!" << endl;
		emit canceled(i18n("GStreamer initializing failed!"));
		return;
	}

	kdDebug() << "GStreamerPart: Creating video window" << endl;
	m_video = new VideoWindow(parentWidget, m_videosink, m_play);
	connect(m_video, SIGNAL(signalNewFrameSize(const QSize&)), this, SIGNAL(signalNewFrameSize(const QSize&)));
	m_video->setFocusPolicy(QWidget::ClickFocus);
	setWidget(m_video);

	setXMLFile("gstreamer_part.rc");
	initActions();
	stateChanged("disable_all");

	emit setStatusBarText(i18n("Ready"));
	m_gstReady = true;

	m_logoPath = locate("data", "kaffeine/logo");
	kdDebug() << "GStreamerPart: Found logo animation: " << m_logoPath << endl;
}

GStreamerPart::~GStreamerPart()
{
	if (!m_play)
		return;

	gst_element_set_state(m_play, GST_STATE_NULL);
	saveConfig();

	delete m_timer;
	//gst_object_unref(GST_OBJECT(m_visual));
	//gst_object_unref(GST_OBJECT(m_audiosink));
	//gst_object_unref(GST_OBJECT(m_videosink));
	gst_object_unref(GST_OBJECT(m_play));

	kdDebug() << "GStreamerPart: destructed" << endl;
}

bool GStreamerPart::isPlaying()
{
	return ((GST_STATE(m_play) == GST_STATE_PLAYING || GST_STATE(m_play) == GST_STATE_PAUSED) &&  m_url != m_logoPath);
}

bool GStreamerPart::isPaused()
{
	return (GST_STATE(m_play) == GST_STATE_PAUSED);
}

bool GStreamerPart::hasVideo()
{
	//FIXME: don't work with all video codecs
	return (!m_videoCodec.isNull());
}

uint GStreamerPart::volume() const
{
	double v;
	g_object_get(G_OBJECT(m_play), "volume", &v, NULL);

	return (uint)(v * 25);
}

uint GStreamerPart::position() const
{
	return (uint)((1.0 / m_timer->getTotalTimeMS()) * m_timer->getCurrentTimeMS() * 100);
}

bool GStreamerPart::closeURL()
{
	slotStop();
	return true;
}

KAboutData *GStreamerPart::createAboutData()
{
	KAboutData* aboutData = new KAboutData( "gstreamerpart", I18N_NOOP("GStreamerPart"),
	                                        "0.1", "GStreamer based player part for Kaffeine.",
	                                        KAboutData::License_GPL,
	                                        "(c) 2005, Jürgen Kofler.", 0, "http://kaffeine.sourceforge.net",
	                                        "kaffeine-user@lists.sourceforge.net");
	aboutData->addAuthor("Jürgen Kofler.",0, "kaffeine@gmx.net");

	return aboutData;
}

bool GStreamerPart::openURL(const MRL& mrl)
{
	kdDebug() << "GStreamerPart::openURL(): " << mrl.url() << endl;

	if (!m_gstReady)
		return false;

	// FIXME: thats not the right place for that
	if (!m_posToolbar)
	{
		m_posToolbar = (KToolBar*)factory()->container("gstPositionToolBar", this);
		if (m_posToolbar)
		{
			m_posToolbar->setItemAutoSized(m_posToolbar->idAt(0), true); //set position slider to maximum width
		}
	}

	m_mrl = mrl;
	m_playlist.clear();
	m_current = 0;
	bool playlist = false;

	QString ext = m_mrl.kurl().fileName();
	ext = ext.remove( 0 , ext.findRev('.')+1 ).lower();

	if (m_mrl.mime().isNull())
	{
		KMimeType::Ptr mime = KMimeType::findByURL(m_mrl.kurl().path());
		m_mrl.setMime(mime->name());
	}

	/* is m_mrl a playlist? */
	if ((m_mrl.mime() == "text/plain") || (m_mrl.mime() == "text/xml") || (m_mrl.mime() == "application/x-kaffeine")
	        || (m_mrl.mime() == "audio/x-scpls") || (m_mrl.mime() == "audio/x-mpegurl") || (m_mrl.mime() == "audio/mpegurl")
	        || (ext == "asx") || (ext == "asf") || (ext == "wvx") || (ext == "wax")) /* windows meta files */
	{
		kdDebug() << "GStreamerPart: Check for kaffeine/noatun/m3u/pls/asx playlist\n";
		QString localFile;
		if (KIO::NetAccess::download(m_mrl.kurl(), localFile, widget()))
		{
			QFile file(localFile);
			file.open(IO_ReadOnly);
			QTextStream stream(&file);
			QString firstLine = stream.readLine();
			QString secondLine = stream.readLine();
			file.close();

			if (secondLine.contains("kaffeine", false))
			{
				kdDebug() << "GStreamerPart: Try loading kaffeine playlist\n";
				playlist = PlaylistImport::kaffeine(localFile, m_playlist);
			}
			if (secondLine.contains("noatun", false))
			{
				kdDebug() << "GStreamerPart: Try loading noatun playlist\n";
				playlist = PlaylistImport::noatun(localFile, m_playlist);
			}
			if (firstLine.contains("asx", false))
			{
				kdDebug() << "GStreamerPart: Try loading asx playlist\n";
				playlist = PlaylistImport::asx(localFile, m_playlist);
			}
			if (firstLine.contains("[playlist]", false))
			{
				kdDebug() << "GStreamerPart: Try loading pls playlist\n";
				playlist = PlaylistImport::pls(localFile, m_playlist);
			}
			if (ext == "m3u")  //indentify by extension
			{
				kdDebug() << "GStreamerPart: Try loading m3u playlist\n";
				playlist = PlaylistImport::m3u(localFile, m_playlist);
			}
		}
		else
			kdError() << "GStreamerPart: " << KIO::NetAccess::lastErrorString() << endl;
	}
	/* check for ram playlist */
	if ( (ext == "ra") || (ext == "rm") || (ext == "ram") || (ext == "lsc") || (ext == "pl") )
	{
		kdDebug() << "GStreamerPart: Try loading ram playlist\n";
		playlist = PlaylistImport::ram(m_mrl, m_playlist, widget());
	}

	int pos;
	QString s = mrl.url();
	if ( s.startsWith("cdda://") ) {
		s = s.remove("cdda://");
		if ( (pos=s.findRev("/"))>-1 ) {
			m_device = s.left( pos );
			kdDebug() << "GStreamerPart device : " << m_device << endl;
			s = s.right( s.length()-pos-1 );
			m_mrl.setURL( "cdda://"+s );
		}
	}
	else if ( s.startsWith("dvd://") ) {
		s = s.remove("dvd://");
		if ( (pos=s.findRev("/"))>-1 ) {
			m_device = s.left( pos );
			kdDebug() << "GStreamerPart device : " << m_device << endl;
			m_mrl.setURL( "dvd://" );
		}
	}
	else if ( s.startsWith("vcd://") ) {
		s = s.remove("vcd://");
		if ( (pos=s.findRev("/"))>-1 ) {
			m_device = s.left( pos );
			kdDebug() << "GStreamerPart device : " << m_device << endl;
			m_mrl.setURL( "vcd://" );
		}
	}

	if (!playlist)
	{
		kdDebug() << "GStreamerPart: Got single track" << endl;
		m_playlist.append(m_mrl);
	}

	QTimer::singleShot(0, this, SLOT(slotPlay()));
	return true;
}

void GStreamerPart::slotPlay()
{
	if (GST_STATE(m_play) == GST_STATE_PAUSED)
	{
		gst_element_set_state(m_play, GST_STATE_PLAYING);
		return;
	}

	if (m_playlist.count() > 0)
	{
		emit setStatusBarText(i18n("Opening..."));
		MRL curMRL = m_playlist[m_current];
		m_url = curMRL.url();
		QString subUrl = QString::null;
		if ((!curMRL.subtitleFiles().isEmpty()) && (curMRL.currentSubtitle() > -1))
			subUrl = curMRL.subtitleFiles()[curMRL.currentSubtitle()];

		gstPlay(m_url, subUrl);
	}
	else
	{
		signalRequestCurrentTrack();
	}
}

void GStreamerPart::slotNext()
{
	if ((m_playlist.count() > 0) && (m_current < m_playlist.count()-1))
	{
		m_current++;
		slotPlay();
	}
	else
	{
		emit signalRequestNextTrack();
	}
}

void GStreamerPart::slotPrevious()
{
	if (m_current > 0)
	{
		m_current--;
		slotPlay();
	}
	else
	{
		emit signalRequestPreviousTrack();
	}
}

void GStreamerPart::gstPlay(const QString& trackUrl, const QString& subtitleUrl)
{
	if (!m_gstReady)
		return;

	m_title = QString::null;
	m_artist = QString::null;
	m_album = QString::null;
	m_year = QString::null;
	m_genre = QString::null;
	m_track = QString::null;
	m_comment = QString::null;
	m_audioCodec = QString::null;
	m_videoCodec = QString::null;
	QString url = trackUrl;

	if (GST_STATE (m_play) > GST_STATE_READY)
		gst_element_set_state (m_play, GST_STATE_READY);
	/* make sure the xoverlay know the winId */
	m_video->refresh();

	/* KDE has the habit to do "file:/location", whereas we
	       expect "file:///location" */
	if (url.left(7).lower() == "file://")
	{
		url.insert(6, "/");
	}
	else if (url[0] == '/')
	{
		url.prepend("file://");
	}
	gchar *uri = g_strdup(url.local8Bit());
	kdDebug() << "GStreamerPart: play URL: " << uri << endl;
	g_object_set(G_OBJECT(m_play), "uri", uri, NULL);
	g_free(uri);

	//process subtitle url
	if (!subtitleUrl.isNull())
	{
		QString sub = subtitleUrl;
		if (sub.left(7).lower() == "file://")
		{
			sub.insert(6, "/");
		}
		else if (sub[0] == '/')
		{
			sub.prepend("file://");
		}
		gchar* suburi = g_strdup(sub.local8Bit());
		kdDebug() << "GStreamerPart: Setting subtitle URL to " << suburi << endl;
		g_object_set(G_OBJECT(m_play), "suburi", suburi, NULL);
		g_free(suburi);
	}
	else
	{
		g_object_set(G_OBJECT(m_play), "suburi", NULL, NULL);
	}

	if (gst_element_set_state(m_play, GST_STATE_PLAYING) != GST_STATE_SUCCESS)
	{
// 		KMessageBox::error(0, i18n("Playback failed. Reason unknown."));
// 		if (m_url != m_logoPath)
// 			emit signalPlaybackFailed();
	}
}

void GStreamerPart::audiocdMRLS(MRL::List& list, bool& ok, bool& supported, const QString& device)
{
	if (!m_gstReady)
		return;
	supported = true;
	if (!device.isNull())
		m_device = device;

	GstElement *cdda;
	gint64 tracks;
	GstFormat fmt;

	cdda = gst_element_make_from_uri(GST_URI_SRC, "cdda://", NULL);
	if (!cdda)
		return;
	GObjectClass* klass = G_OBJECT_GET_CLASS(G_OBJECT(cdda));
	if (g_object_class_find_property (klass, "device"))
	{
		kdDebug() << "GStreamer: Set source sink property 'device' to " << m_device << endl;
		g_object_set(cdda, "device", m_device.ascii(), NULL);
	}
	fmt = gst_format_get_by_nick("track");
	if (!fmt)
	{
		gst_object_unref(GST_OBJECT (cdda));
		return;
	}
	if (gst_element_set_state(cdda, GST_STATE_PAUSED) != GST_STATE_SUCCESS)
	{
		gst_object_unref(GST_OBJECT (cdda));
		return;
	}
	if (!gst_pad_query (gst_element_get_pad(cdda, "src"), GST_QUERY_TOTAL, &fmt, &tracks))
	{
		gst_element_set_state(cdda, GST_STATE_NULL);
		gst_object_unref(GST_OBJECT (cdda));
		return;
	}
	gst_element_set_state(cdda, GST_STATE_NULL);
	gst_object_unref(GST_OBJECT (cdda));

	MRL mrl;
	for (int i = 1; i <= tracks; i++)
	{
		mrl = MRL(QString("cdda://%1").arg(i));
		mrl.setTitle(QString("AudioCD ") + i18n("Track") + " " + QString::number(i));
		mrl.setTrack(QString::number(i));
		list.append(mrl);
	}

	ok = true;
}

void GStreamerPart::vcdMRLS(MRL::List& list, bool& ok, bool& supported, const QString& device)
{
	if (!m_gstReady)
		return;
	supported = true;
	ok = true;
	if (!device.isNull())
		m_device = device;

	MRL mrl(QString("vcd://"));
	mrl.setTitle("VCD");
	list.append(mrl);
}

void GStreamerPart::dvdMRLS(MRL::List& list, bool& ok, bool& supported, const QString& device)
{
	if (!m_gstReady)
		return;
	supported = true;
	ok = true;
	if (!device.isNull())
		m_device = device;

	MRL mrl(QString("dvd://"));
	mrl.setTitle("DVD");
	list.append(mrl);
}

void GStreamerPart::slotTogglePause(bool)
{
	if (GST_STATE(m_play) == GST_STATE_PAUSED)
		gst_element_set_state(m_play, GST_STATE_PLAYING);
	else
		gst_element_set_state(m_play, GST_STATE_PAUSED);
}

void GStreamerPart::slotSetPosition(uint percent)
{
	m_timer->seekPercent(percent);
}

void GStreamerPart::slotStop()
{
	if (!m_logoPath.isNull())
	{
		m_url = m_logoPath;
		gstPlay(m_logoPath, QString::null);
	}
	else
		gst_element_set_state(m_play, GST_STATE_READY);
}

void GStreamerPart::slotVolume(int vol)
{
	// kdDebug() << "GStreamerPart: Set volume to " << vol << "%" << endl;

	emit setStatusBarText(i18n("Volume") + ": " + QString::number(vol) + "%");
	double v = vol * 0.04;
	kdDebug() << "GStreamerPart: Set volume to " << v << endl;
	g_object_set(G_OBJECT(m_play), "volume", v, NULL);
}

void GStreamerPart::slotSetVolume(uint vol)
{
	slotVolume(vol);
}

void GStreamerPart::slotSaturation(int sat)
{
	emit setStatusBarText(i18n("Saturation") + ": " + QString::number(sat));
	g_object_set(G_OBJECT(m_videosink), "saturation", sat, NULL);
}

void GStreamerPart::slotHue(int hue)
{
	emit setStatusBarText(i18n("Hue") + ": " + QString::number(hue));
	g_object_set(G_OBJECT(m_videosink), "hue", hue, NULL);
}

void GStreamerPart::slotContrast(int contrast)
{
	emit setStatusBarText(i18n("Contrast") + ": " + QString::number(contrast));
	g_object_set(G_OBJECT(m_videosink), "contrast", contrast, NULL);
}

void GStreamerPart::slotBrightness(int brightness)
{
	emit setStatusBarText(i18n("Brightness") + ": " + QString::number(brightness));
	g_object_set(G_OBJECT(m_videosink), "brightness", brightness, NULL);
}

void GStreamerPart::slotMute()
{
	m_mute = !m_mute;
	if (m_mute)
		emit setStatusBarText(i18n("Mute") + ": " + i18n("On"));
	else
		emit setStatusBarText(i18n("Mute") + ": " + i18n("Off"));
	g_object_set(G_OBJECT(m_audiosink), "mute", m_mute, NULL);
}

void GStreamerPart::gstStateChanged()
{
	if (m_newState == GST_STATE_PAUSED)
	{
		kdDebug() << "GStreamerPart: New gstreamer state: PAUSE" << endl;
		emit setStatusBarText(i18n("Pause"));
	}
	else if (m_newState == GST_STATE_PLAYING)
	{
		kdDebug() << "GStreamerPart: New gstreamer state: PLAYING" << endl;
		if (m_url != m_logoPath)
			stateChanged("playing");
		else
			stateChanged("not_playing");
		QString caption = m_mrl.title();
		if (!m_mrl.artist().isEmpty())
			caption.append(QString(" (") + m_mrl.artist() + ")");
		emit setWindowCaption(caption);
		emit setStatusBarText(i18n("Playing"));
	}
	else if (m_newState == GST_STATE_READY)
	{
		kdDebug() << "GStreamerPart: New gstreamer state: READY" << endl;
		if (m_playlist.count())
			stateChanged("not_playing");
		else
			stateChanged("disable_all");
		emit setWindowCaption("");
		emit setStatusBarText(i18n("Stop"));
	}
}

void GStreamerPart::slotVideoSettings()
{
	if (!m_videoSettings)
	{
		int hue = 0, sat = 0, contr = 0, bright = 0;
		g_object_get(G_OBJECT(m_videosink), "hue", &hue, NULL);
		g_object_get(G_OBJECT(m_videosink), "saturation", &sat, NULL);
		g_object_get(G_OBJECT(m_videosink), "contrast", &contr, NULL);
		g_object_get(G_OBJECT(m_videosink), "brightness", &bright, NULL);

		m_videoSettings = new VideoSettings(hue, sat, contr, bright);
		connect(m_videoSettings, SIGNAL(signalNewBrightness(int)), this, SLOT(slotBrightness(int)));
		connect(m_videoSettings, SIGNAL(signalNewContrast(int)), this, SLOT(slotContrast(int)));
		connect(m_videoSettings, SIGNAL(signalNewHue(int)), this, SLOT(slotHue(int)));
		connect(m_videoSettings, SIGNAL(signalNewSaturation(int)), this, SLOT(slotSaturation(int)));
	}

	m_videoSettings->show();
}

void GStreamerPart::slotConfigDialog()
{
	if (m_gstConfig == NULL)
	{
		m_gstConfig = new GStreamerConfig(m_audioPluginList, m_videoPluginList);
	}

	m_gstConfig->setAudioDriver(m_audioSinkName);
	m_gstConfig->setVideoDriver(m_videoSinkName);
	m_gstConfig->setDrive(m_device);
	if (m_gstConfig->exec() == KDialogBase::Accepted)
	{
		kdDebug() << "GStreamerPart: Apply new configuration" << endl;
		if (m_audioSinkName != m_gstConfig->getAudioDriver())
			setAudioSink(m_gstConfig->getAudioDriver());
		m_videoSinkName = m_gstConfig->getVideoDriver();
		m_device = m_gstConfig->getDrive();
	}
}

void GStreamerPart::processMetaInfo()
{
	kdDebug() << "GStreamerPart: Processing meta info" << endl;

	MRL mrl = m_playlist[m_current];

	if ((mrl.title().contains("/") || mrl.title().contains(".") || (mrl.title().isEmpty()))
	        && !m_title.stripWhiteSpace().isEmpty() && m_title.length() > 1)
		mrl.setTitle(m_title);
	if (mrl.artist().isEmpty() && !m_artist.stripWhiteSpace().isEmpty())
		mrl.setArtist(m_artist);
	if (mrl.album().isEmpty() && !m_album.stripWhiteSpace().isEmpty())
		mrl.setAlbum(m_album);
	if (mrl.genre().isEmpty() && !m_genre.stripWhiteSpace().isEmpty())
		mrl.setGenre(m_genre);
	if (mrl.track().isEmpty() && !m_track.stripWhiteSpace().isEmpty())
		mrl.setTrack(m_track);
	if (mrl.comment().isEmpty() && !m_comment.stripWhiteSpace().isEmpty())
		mrl.setComment(m_comment);
	if (mrl.length().isNull())
	{
		QTime length = QTime().addMSecs(m_timer->getTotalTimeMS());
		if (!length.isNull())
			mrl.setLength(length);
	}
	m_playlist[m_current] = mrl;

	QString caption = mrl.title();
	if (!mrl.artist().isEmpty())
		caption.append(QString(" (") + mrl.artist() + ")");
	emit setWindowCaption(caption);
	/* if we don't have a playlist emit signalNewMeta() */
	if (mrl.url() == m_mrl.url())
	{
		m_mrl = mrl;
		emit signalNewMeta();
	}
}

void GStreamerPart::slotInfo()
{
	QString info;
	QTextStream ts(&info, IO_WriteOnly);
	ts << "<qt><table width=\"90%\">";
	ts << "<tr><td colspan=\"2\"><center><b>" << m_title << "</b></center></td></tr>";
	if (!m_artist.isNull())
		ts << "<tr><td><b>" << i18n("Artist") << ":</b></td><td> " << m_artist << "</td></tr>";
	if (!m_album.isNull())
		ts << "<tr><td><b>" << i18n("Album") << ":</b></td><td> " << m_album << "</td></tr>";
	if (!m_track.isNull())
		ts << "<tr><td><b>" << i18n("Track") << ":</b></td><td> " << m_track << "</td></tr>";
	if (!m_year.isNull())
		ts << "<tr><td><b>" << i18n("Year") << ":</b></td><td> " << m_year << "</td></tr>";
	if (!m_genre.isNull())
		ts << "<tr><td><b>" << i18n("Genre") << ":</b></td><td> " << m_genre << "</td></tr>";
	if (!m_comment.isNull())
		ts << "<tr><td><b>" << i18n("Comment") << ":</b></td><td> " << m_comment << "</td></tr>";

	QTime length = QTime().addMSecs(m_timer->getTotalTimeMS());
	if (!length.isNull())
		ts << "<tr><td><b>" << i18n("Length") << ":</b></td><td> " << length.toString("h:mm:ss") << "</td></tr>";

	ts << "<br><br>";

	ts << "<tr><td><b>" << i18n("Audio") << ":</b></td><td> " << m_audioCodec << "</td></tr>";
	int width = m_video->getFrameSize().width();
	int height =  m_video->getFrameSize().height();
	if (width > 0 && height > 0)
		ts << "<tr><td><b>" << i18n("Video") << ":</b></td><td> " << m_videoCodec << " " << width << "x" << height << "</td></tr>";

	ts << "</table></qt>";

	KMessageBox::information(0, info);
}

void GStreamerPart::slotSetVisualPlugin(const QString& name)
{
	if (name != "none")
	{
		GstElement* visual = gst_element_factory_make (name.ascii(), "visualization");
		if (visual)
		{
			g_object_set(G_OBJECT (m_play), "vis-plugin", visual, NULL);
			g_object_unref(m_visual);
			m_visual = visual;
			m_visualPluginName = name;
		}
		else
			kdWarning() << "GStreamer: Initialization of visualization plugin failed (" << name << ")" << endl;
	}
	else
	{
		if (m_visual)
		{
			g_object_set(G_OBJECT (m_play), "vis-plugin", NULL, NULL);
			g_object_unref(m_visual);
			m_visual = NULL;
			m_visualPluginName = "none";
		}
	}
}

void GStreamerPart::slotContextMenu(const QPoint& pos)
{
	if (factory())
	{
		KPopupMenu *pop = (KPopupMenu*)factory()->container("context_menu", this);
		if (pop)
			pop->popup(pos);
	}
}

void GStreamerPart::loadConfig()
{
	kdDebug() << "GStreamerPart: Load config" << endl;
	KConfig* config = instance()->config();
	config->setGroup("General Options");

	m_audioSinkName = config->readEntry("Audio Sink", "alsasink");
	m_videoSinkName = config->readEntry("Video Sink", "xvimagesink");
	m_visualPluginName = config->readEntry("Visual Plugin", "goom");
	m_savedVolume = config->readNumEntry("Volume", 25);
	m_device = config->readEntry("CD Device", "/dev/dvd");
}

void GStreamerPart::saveConfig()
{
	kdDebug() << "GStreamerPart: Save config" << endl;
	if (!m_gstReady)
		return;

	KConfig* config = instance()->config();
	config->setGroup("General Options");

	config->writeEntry("Audio Sink", m_audioSinkName);
	config->writeEntry("Video Sink", m_videoSinkName);
	config->writeEntry("Visual Plugin", m_visualPluginName);
	config->writeEntry("Volume", m_volume->value());
	config->writeEntry("CD Device", m_device);
}

void GStreamerPart::slotPrepareForFullscreen(bool fullscreen)
{
	if (fullscreen)
		m_video->startMouseHideTimer();
	else
		m_video->stopMouseHideTimer();
}

void GStreamerPart::timerEvent(QTimerEvent* tevent)
{
	switch ( tevent->timerId() )
	{
		case TIMER_EVENT_PLAYBACK_FINISHED:
		{
			kdDebug() << "GStreamerPart: Playback finished" << endl;
			if (GST_STATE (m_play) > GST_STATE_READY)
				gst_element_set_state (m_play, GST_STATE_READY);
			if (m_current < m_playlist.count()-1)
			{
				m_current ++;
				slotPlay();
			}
			else
			{
				if (m_url != m_logoPath)
					emit signalTrackFinished();
			}
			break;
		}
		case TIMER_EVENT_ERROR:
		{
			emit setStatusBarText(i18n("Error"));
			if (m_url != m_logoPath)
			{
				KMessageBox::detailedError(0, m_errorMsg, m_errorDetails);
				emit signalPlaybackFailed();
			}
			break;
		}
		case TIMER_EVENT_NEW_STATE:
		{
			gstStateChanged();
			break;
		}
		case TIMER_EVENT_FOUND_TAG:
		{
			processMetaInfo();
			break;
		}
		default:
		break;
	}
}

void GStreamerPart::initActions()
{
	new KAction(i18n("Play"), "player_play", 0, this, SLOT(slotPlay()), actionCollection(), "player_play");
	new KAction(i18n("Pause"), "player_pause", Key_Space, this, SLOT(slotTogglePause()), actionCollection(), "player_pause");
	new KAction(i18n("Stop"), "player_stop", Key_Backspace, this, SLOT(slotStop()), actionCollection(), "player_stop");
	new KAction(i18n("&Next"), "player_end", Key_PageDown, this, SLOT(slotNext()), actionCollection(), "player_next");
	new KAction(i18n("&Previous"), "player_start", Key_PageUp, this, SLOT(slotPrevious()), actionCollection(), "player_previous");

	m_timer = new Timer(m_play);
	new KWidgetAction(m_timer->getSlider(), i18n("Position"), 0, 0, 0, actionCollection(), "player_position");
	new KWidgetAction((QWidget*)m_timer->getLabel(), i18n("Playtime"), 0, 0, 0, actionCollection(), "player_playtime");

	m_audioVisual = new KSelectAction(i18n("Audio &Visualization"), 0, actionCollection(), "audio_visual");
	connect(m_audioVisual, SIGNAL(activated(const QString&)), this, SLOT(slotSetVisualPlugin(const QString&)));
	m_audioVisualPluginList.prepend("none");
	m_audioVisual->setItems(m_audioVisualPluginList);
	m_audioVisual->setCurrentItem(m_audioVisual->items().findIndex(m_visualPluginName));

	new KAction(i18n("&Mute"), "player_mute", Key_U, this, SLOT(slotMute()), actionCollection(), "audio_mute");
	new KAction(i18n("&Auto"), "viewmagfit", Key_F5, m_video, SLOT(slotAspectRatioAuto()), actionCollection(), "aspect_auto");
	new KAction(i18n("&4:3"), "viewmagfit", Key_F6, m_video, SLOT(slotAspectRatio4_3()), actionCollection(), "aspect_43");
	new KAction(i18n("A&namorphic"), "viewmagfit", Key_F7, m_video, SLOT(slotAspectRatioAnamorphic()), actionCollection(), "aspect_anamorphic");
	new KAction(i18n("&DVB"), "viewmagfit", Key_F8, m_video, SLOT(slotAspectRatioDVB()), actionCollection(), "aspect_dvb");
	new KAction(i18n("&Square"), "viewmagfit", Key_F9, m_video, SLOT(slotAspectRatioSquare()), actionCollection(), "aspect_square");
	new KAction(i18n("&Video Settings"), "configure", Key_V, this, SLOT(slotVideoSettings()), actionCollection(), "video_settings");
	new KAction(i18n("Track &Info"), "info", 0 , this, SLOT(slotInfo()), actionCollection(), "player_track_info");

	m_volume = new QSlider(Horizontal, 0);
	QToolTip::add(m_volume, i18n("Volume"));
	m_volume->setRange(0, 100);
	m_volume->setSteps(1, 10);
	m_volume->setFixedWidth(75);
	m_volume->setValue(m_savedVolume);
	slotSetVolume(m_savedVolume);
	connect(m_volume, SIGNAL(valueChanged(int)), this, SLOT(slotVolume(int)));
	new KWidgetAction(m_volume, i18n("Volume"), 0, 0, 0, actionCollection(), "audio_volume");
	new KAction(i18n("&GStreamer Engine Parameters"), "edit", 0, this, SLOT(slotConfigDialog()), actionCollection(), "settings_gst_parameter");

	connect(m_video, SIGNAL(signalRightClick(const QPoint&)), this, SLOT(slotContextMenu(const QPoint&)));
}

//Change audio sink on the fly
void GStreamerPart::setAudioSink(QString sinkName)
{
	GstElement* sink = gst_element_factory_make(sinkName.ascii(), "audiosink");
	if (!sink)
	{
		KMessageBox::error(0, i18n("Error: Can't init new Audio Driver %1 - using %2!").arg(sinkName).arg(m_audioSinkName));
		return;
	}

	g_object_set(G_OBJECT(m_play), "audio-sink", sink, NULL);
	m_audiosink = sink;
	m_audioSinkName = sinkName;
	kdDebug() << "GStreamerPart: Using audio driver: " << m_audioSinkName << endl;
}

/**
  *  Initialize GStreamer
  */
bool GStreamerPart::initGStreamer()
{
	if (!gst_init_check(NULL, NULL))
	{
		KMessageBox::error(0, i18n("GStreamer could not be initialized!"));
		return false;
	}

	// Check if registry exists
	GstElement* dummy = gst_element_factory_make("fakesink", "fakesink");
	if (!dummy || !gst_scheduler_factory_make( NULL, GST_ELEMENT(dummy)))
	{
		KMessageBox::error(0, i18n("Missing GStreamer-registry! Did you forget to run <b>gst-register</b> (as root) after installation?"));
		return false;
	}

	/* check GStreamer version */
	guint maj, min, mic;
	gst_version(&maj, &min, &mic);
	kdDebug() << "GStreamerPart: Found GStreamer version " << maj << "." << min << "." << mic << endl << endl;

	/* check for visualization plugins */
	GList* factories = gst_registry_pool_feature_list(GST_TYPE_ELEMENT_FACTORY);
	QString name, cat;
	while (factories)
	{
		name =  GST_PLUGIN_FEATURE_NAME(factories->data);
		cat = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(factories->data));
		// kdDebug() << "GStreamerPart: Found plugin: " << name << " - Category: " << cat << endl;
		if (cat == "Visualization")
			m_audioVisualPluginList.append(name);
		else if (cat == "Sink/Audio")
			m_audioPluginList.append(name);
		else if (cat == "Sink/Video")
			m_videoPluginList.append(name);
		factories = g_list_next(factories);
	}
	g_list_free(factories);

	/* initialize GStreamer objects */
	m_play = gst_element_factory_make ("playbin", "player");
	if (!m_play)
	{
		KMessageBox::error(0, i18n("GStreamer could not be initialized!"));
		return false;
	}
	/*
	 * Init audio driver
	 */
	m_audiosink = gst_element_factory_make(m_audioSinkName.ascii(), "audiosink");
	if (!m_audiosink)
	{
		KMessageBox::error(0, i18n("Can't init Audio Driver '%1' - trying another one...").arg(m_audioSinkName));
		if ((m_audiosink = gst_element_factory_make("alsasink", "audiosink")) != NULL)
			kdDebug() << "GStreamerPart: Using audio-driver: alsasink" << endl;
		else
			if ((m_audiosink = gst_element_factory_make("osssink", "audiosink")) != NULL)
				kdDebug() << "GStreamerPart: Using audio-driver: osssink" << endl;
			else
				if ((m_audiosink = gst_element_factory_make("artsdsink", "audiosink")) != NULL)
					kdDebug() << "GStreamerPart: Using audio-driver: artsdsink" << endl;
				else
				{
					KMessageBox::error(0, i18n("No useable audio-driver found!") + " (" + m_audioSinkName + ")");
					return false;
				}
	}
	else
		kdDebug() << "GStreamerPart: Using audio driver: " << m_audioSinkName << endl;
	/*
	 * Init video driver...
	 */
	m_videosink = gst_element_factory_make (m_videoSinkName.ascii(), "videosink");
	if (!m_videosink)
	{
		KMessageBox::error(0, i18n("Can't init Video Driver '%1' - trying another one...").arg(m_videoSinkName));
		if ((m_videosink = gst_element_factory_make ("xvimagesink", "videosink")) != NULL)
			kdDebug() << "GStreamerPart: Using video-driver: xvimagesink" << endl;
		else
			if ((m_videosink = gst_element_factory_make ("ximagesink", "videosink")) != NULL)
				kdDebug() << "GStreamerPart: Using video-driver: xvimagesink" << endl;
			else
			{
				KMessageBox::error(0, i18n("No useable video-driver found!") + " (" + m_videoSinkName + ")");
				return false;
			}
	}
	else
		kdDebug() << "GStreamerPart: Using video driver: " << m_videoSinkName << endl;
	/*
	 * Visualization
	 */
	kdDebug() << "GStreamerPart: Using visualization plugin: " << m_visualPluginName << endl;
	if (m_visualPluginName != "none")
	{
		m_visual = gst_element_factory_make (m_visualPluginName.ascii(), "visualization");
		if (!m_visual)
			kdWarning() << "GStreamer: Initialization of visualization plugin failed" << endl;
	}

	g_object_set(G_OBJECT(m_play), "video-sink", m_videosink, "audio-sink", m_audiosink, "vis-plugin", m_visual, NULL);
	//subtitle font
	//g_object_set(G_OBJECT(m_play), "subtitle-font-desc", "sans normal 14", NULL);
	g_signal_connect(m_play, "error", G_CALLBACK (cb_error), this);
	g_signal_connect(m_play, "found-tag", G_CALLBACK (cb_foundtag), this);
	g_signal_connect(m_play, "eos", G_CALLBACK (cb_eos), this);
	g_signal_connect(m_play, "state-change", G_CALLBACK (cb_state), this);
	g_signal_connect(m_play, "notify::source", G_CALLBACK(got_source), this);
	gst_element_set_state(m_play, GST_STATE_READY);
	return true;
}

/******************* CALLBACKS ************************/

void GStreamerPart::cb_error(GstElement* /*play*/, GstElement* /*src*/, GError *err, const char *debug, gpointer data)
{
	GStreamerPart* gp = (GStreamerPart*)data;
	gp->m_errorMsg = err->message;
	gp->m_errorDetails = debug;
	QApplication::postEvent(gp, new QTimerEvent(TIMER_EVENT_ERROR));
}

void GStreamerPart::cb_foundtag(GstElement* /*play*/, GstElement* /*src*/, const GstTagList *taglist, gpointer data)
{
	GStreamerPart* gp = (GStreamerPart*)data;
	kdDebug() << " Received meta tags..." << endl;

	char* string;
	guint intVal = 0;
	bool success = false;

	if (gst_tag_list_get_string(taglist, GST_TAG_TITLE, &string) && string)
	{
		gp->m_title = string;
		//kdDebug() << "GStreamerPart: 'Title': " << string << endl;
		success = true;
	}
	if (gst_tag_list_get_string( taglist, GST_TAG_ARTIST, &string) && string)
	{
		gp->m_artist = string;
		//kdDebug() << "GStreamerPart: 'Artist': " << string << endl;
		success = true;
	}
	if (gst_tag_list_get_string(taglist, GST_TAG_ALBUM, &string) && string)
	{
		gp->m_album = string;
		//kdDebug() << "GStreamerPart: 'Album': " << string << endl;
		success = true;
	}
	if (gst_tag_list_get_string(taglist, GST_TAG_GENRE, &string) && string)
	{
		gp->m_genre = string;
		//kdDebug() << "GStreamerPart: 'Genre': " << string << endl;
		success = true;
	}
	if (gst_tag_list_get_uint(taglist, GST_TAG_TRACK_NUMBER, &intVal) && intVal > 0)
	{
		gp->m_track = QString::number(intVal);
		//kdDebug() << "GStreamerPart: 'Track Number': " << gp->m_track << endl;
		success = true;
	}
	if (gst_tag_list_get_string(taglist, GST_TAG_COMMENT, &string) && string)
	{
		gp->m_comment = string;
		//kdDebug() << "GStreamerPart: 'Comment': " << string << endl;
		success = true;
	}
	if (gst_tag_list_get_string(taglist, GST_TAG_AUDIO_CODEC, &string) && string)
	{
		gp->m_audioCodec = string;
		//kdDebug() << "GStreamerPart: 'AudioCodec': " << string << endl;
	}
	if (gst_tag_list_get_string(taglist, GST_TAG_VIDEO_CODEC, &string) && string)
	{
		gp->m_videoCodec = string;
		//kdDebug() << "GStreamerPart: 'Video Codec': " << string << endl;
	}

	if (success)
		QApplication::postEvent(gp, new QTimerEvent(TIMER_EVENT_FOUND_TAG));
}

void GStreamerPart::cb_eos (GstElement* /*play*/, gpointer data)
{
	GStreamerPart* gp = (GStreamerPart*)data;
	QApplication::postEvent(gp, new QTimerEvent(TIMER_EVENT_PLAYBACK_FINISHED));
}

void GStreamerPart::cb_state(GstElement* /*play*/, GstElementState /*old_state*/, GstElementState new_state, gpointer data)
{
	//kdDebug() << "GStreamerPart: state changed callback" << endl;
	GStreamerPart* gp = (GStreamerPart*)data;
	gp->m_newState = new_state;
	QApplication::postEvent(gp, new QTimerEvent(TIMER_EVENT_NEW_STATE));
}

void GStreamerPart::got_source(GstElement* /*play*/, GParamSpec* /*pspec*/, gpointer data)
{
	GStreamerPart* gp = (GStreamerPart*)data;
	GObject* source = NULL;
	g_object_get(gp->m_play, "source", &source, NULL);
	if (!source)
		return;

	kdDebug() << "GStreamerPart: Using source sink: " << G_OBJECT_TYPE_NAME(source) << endl;

	GObjectClass* klass = G_OBJECT_GET_CLASS(G_OBJECT(source));
	if (g_object_class_find_property (klass, "device"))
	{
		kdDebug() << "GStreamer: Set source sink property 'device' to " << gp->m_device << endl;
		g_object_set(source, "device", gp->m_device.ascii(), NULL);
	}
}



