/***************************************************************************
                           Interface to access JuK
                             -------------------
    begin                : Mon Jan 15 21:09:00 CEST 2001
    copyright            : (C) 2001-2002 by Stefan Gehn
    email                : metz {AT} gehn {DOT} 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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "jukInterface.h"
#include "jukInterface.moc"

#include <kapplication.h>
#include <kdebug.h>
#include <tqstringlist.h>
#include <tqstrlist.h>
#include <tqprocess.h>
#include <kurldrag.h>

#define TIMER_FAST  250

JuKInterface::JuKInterface() : PlayerInterface(), mProc(0)
{
	mTimerValue = TIMER_FAST;
	mJuKTimer = new TQTimer ( this, "mJukTimer" );

	connect(mJuKTimer, TQT_SIGNAL(timeout()), TQT_SLOT(updateSlider()) );
	kapp->dcopClient()->setNotifications ( true );

	connect(kapp->dcopClient(), TQT_SIGNAL(applicationRegistered(const TQCString&)),
		TQT_SLOT(appRegistered(const TQCString&)) );

	connect(kapp->dcopClient(), TQT_SIGNAL(applicationRemoved(const TQCString&)),
		TQT_SLOT(appRemoved(const TQCString&)));

	TQTimer::singleShot(0, this, TQT_SLOT(myInit()));
}

JuKInterface::~JuKInterface()
{
	kapp->dcopClient()->setNotifications(false);
	delete mJuKTimer;
}

void JuKInterface::myInit()
{
	// Start the timer if juk is already running
	// Needed if user adds applet while running juk
	if ( findRunningJuK() )
	{
		emit playerStarted();
		mJuKTimer->start(mTimerValue);
	}
	else
	{
		emit playerStopped();
		emit newSliderPosition(0,0);
	}
}

void JuKInterface::appRegistered ( const TQCString &appId )
{
	if(appId.contains("juk",false) )
	{
		mAppId = appId;

		// BWAHAHAHA EVIL HACK
		// JuK blocks DCOP signals on its startup, so if we try to
		// ping it now, it'll simply cause us to block, which will
		// cause kicker to block, which is bad, m'kay?
		//
		// So what we do is launch the dcop command instead, and let
		// *it* block for us.  As soon as the command exits, we know
		// that JuK is ready to go (and so are we).
		mProc = new TQProcess(this, "jukdcopCheckProc");
		mProc->addArgument("dcop");
		mProc->addArgument("juk");
		mProc->addArgument("Player");
		mProc->addArgument("status()");

		connect(mProc, TQT_SIGNAL(processExited()), TQT_SLOT(jukIsReady()));
		mProc->start();
	}
}

void JuKInterface::appRemoved ( const TQCString &appId )
{
	if ( appId.contains("juk",false) )
	{
	 	// is there still another juk alive?
		if ( findRunningJuK() )
			return;
		mJuKTimer->stop();
		emit playerStopped();
		emit newSliderPosition(0,0);
	}
}

/* Called when the dcop process we launch terminates */
void JuKInterface::jukIsReady()
{
	emit playerStarted();
	mJuKTimer->start(mTimerValue);

	mProc->deleteLater();
	mProc = 0;
}

void JuKInterface::updateSlider ()
{
	// length/time in msecs, -1 means "no playobject in juk"
	int len = -1;
	int time = -1;
	TQByteArray data, replyData;
	TQCString replyType;

	if (kapp->dcopClient()->call(mAppId, "Player", "totalTime()", data,
		replyType, replyData))
	{
		TQDataStream reply(replyData, IO_ReadOnly);
		if (replyType == "int")
			reply >> len;
	}

	data = 0;
	replyData = 0;
	replyType = 0;

	if (kapp->dcopClient()->call(mAppId, "Player", "currentTime()", data,
		replyType, replyData))
	{
		TQDataStream reply(replyData, IO_ReadOnly);
		if (replyType == "int")
			reply >> time;
	}

	if ( (time < 0) || (len < 0)) // JuK isn't playing and thus returns -1
	{
		len = 0;
		time = 0;
	}
	emit ( newSliderPosition(len,time) );
	emit playingStatusChanged(playingStatus());
}

// Drag-n-Drop stuff =================================================================

void JuKInterface::dragEnterEvent(TQDragEnterEvent* event)
{
//	kdDebug(90200) << "JuKInterface::dragEnterEvent()" << endl;
	event->accept( KURLDrag::canDecode(event) );
}

void JuKInterface::dropEvent(TQDropEvent* event)
{
//	kdDebug(90200) << "JuKInterface::dropEvent()" << endl;
	KURL::List list;
	if (KURLDrag::decode(event, list))
	{
		TQByteArray data, replyData;
		TQStringList fileList;
		TQCString replyType;
		TQDataStream arg(data, IO_WriteOnly);

		// Juk doesn't handle KURL's yet, so we need to form a list
		// that contains the local paths.
		for (KURL::List::ConstIterator it = list.begin(); it != list.end(); ++it)
			fileList += (*it).path();

		arg << fileList << false;

		// Use call instead of send to make sure the files are added
		// before we try to play.
		if (!kapp->dcopClient()->call(mAppId, "Collection", "openFile(TQStringList)", data,
			      replyType, replyData, true))
		{
			kdDebug(90200) << "Couldn't send drop to juk" << endl;
		}

		// Apparently we should auto-play?
		TQByteArray strData;
		TQDataStream strArg(strData, IO_WriteOnly);
		strArg << *fileList.begin();

		if (!kapp->dcopClient()->send(mAppId, "Player", "play(TQString)", strData))
			kdDebug(90200) << "Couldn't send play command to juk" << endl;
	}
}

// ====================================================================================

void JuKInterface::sliderStartDrag()
{
	mJuKTimer->stop();
}

void JuKInterface::sliderStopDrag()
{
	mJuKTimer->start(mTimerValue);
}

void JuKInterface::jumpToTime( int sec )
{
	TQByteArray data;
	TQDataStream arg(data, IO_WriteOnly);
	arg << sec;
	// Used in JuK shipping with KDE < 3.3
	//kapp->dcopClient()->send(mAppId, "Player", "setTime(int)", data);
	kapp->dcopClient()->send(mAppId, "Player", "seek(int)", data);
}

void JuKInterface::playpause()
{
	if (!findRunningJuK())
		startPlayer("juk");
	TQByteArray data;
	kapp->dcopClient()->send(mAppId, "Player", "playPause()", data);
}

void JuKInterface::stop()
{
	TQByteArray data;
	kapp->dcopClient()->send(mAppId, "Player", "stop()", data);
}

void JuKInterface::next()
{
	TQByteArray data;
	kapp->dcopClient()->send(mAppId, "Player", "forward()", data);
}

void JuKInterface::prev()
{
	TQByteArray data;
	kapp->dcopClient()->send(mAppId, "Player", "back()", data);
}

void JuKInterface::volumeUp()
{
   TQByteArray data;
   kapp->dcopClient()->send(mAppId, "Player", "volumeUp()", data);
}

void JuKInterface::volumeDown()
{
   TQByteArray data;
   kapp->dcopClient()->send(mAppId, "Player", "volumeDown()", data);
}

const TQString JuKInterface::getTrackTitle() const
{
	TQString title;
	TQByteArray data, replyData;
	TQCString replyType;

	if (kapp->dcopClient()->call(mAppId, "Player", "playingString()",data,
		replyType, replyData))
	{
		TQDataStream reply(replyData, IO_ReadOnly);
		if (replyType == TQSTRING_OBJECT_NAME_STRING)
		{
			reply >> title;
			return title;
		}
	}
	return TQString("");
}

// FIXME: what if we have a dcop app named, let's say, 'jukfrontend'?
bool JuKInterface::findRunningJuK()
{
	QCStringList allApps = kapp->dcopClient()->registeredApplications();
	TQValueList<TQCString>::const_iterator iterator;

	for (iterator = allApps.constBegin(); iterator != allApps.constEnd(); ++iterator)
	{
		if ((*iterator).contains("juk",false))
		{
			mAppId = *iterator;
			return true;
		}
	}
	return false;
}

int JuKInterface::playingStatus()
{
	TQByteArray data, replyData;
	TQCString replyType;

	if (kapp->dcopClient()->call(mAppId, "Player", "status()", data, replyType,
		replyData))
	{
		int status = 0;
		TQDataStream reply(replyData, IO_ReadOnly);
		if (replyType == "int")
			reply >> status;

		if (status == 2)
			return Playing;
		else if (status == 1)
			return Paused;
	}

	return Stopped;
}