/***************************************************************************
                        main file of mediacontrol applet
                             -------------------
    begin                : Tue Apr 25 11:53:11 CEST 2000
    copyright            : (C) 2000-2005 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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "mediacontrol.h"
#include "mediacontrol.moc"

#include "mediacontrolconfig.h"
#include "configfrontend.h"


#include "noatunInterface.h"
#include "xmmsInterface.h"
#include "jukInterface.h"
#include "amarokInterface.h"
#include "mpdInterface.h"
#include "kscdInterface.h"

#include "mcslider.h"

#include <qfile.h>
#include <qdragobject.h>
#include <qtooltip.h>
#include <qstyle.h>
#include <qslider.h>
#include <qpainter.h>
#include <qiconset.h>
#include <kpopupmenu.h>

#include <kapplication.h>
#include <kipc.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <knotifyclient.h>
#include <kbugreport.h>
#include <dcopclient.h>

const int MC_BUTTONSIZE = 18; // TODO: Might become dynamical for bigger panels
const int NO_BUTTONS = 4;

extern "C"
{
	KDE_EXPORT KPanelApplet *init( QWidget *parent, const QString &configFile)
	{
		KGlobal::locale()->insertCatalogue("mediacontrol");
		return new MediaControl(configFile, KPanelApplet::Normal,
			KPanelApplet::About | KPanelApplet::Preferences |
			KPanelApplet::ReportBug, parent, "mediacontrol");
	}
}

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

class MediaControlToolTip : public QToolTip
{
	public:
		MediaControlToolTip(QWidget *widget, PlayerInterface *pl_obj) :
			QToolTip(widget), mWidget(widget), mPlayer(pl_obj) {};

	protected:
		virtual void maybeTip(const QPoint &pt)
		{
			QRect rc( mWidget->rect());
			if (rc.contains(pt))
			{
				tip ( rc, mPlayer->getTrackTitle() );
			}
		}
	private:
		QWidget *mWidget;
		PlayerInterface *mPlayer;
};

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

MediaControl::MediaControl(const QString &configFile, Type t, int actions,
	QWidget *parent, const char *name)
	: DCOPObject("MediaControl"),
		KPanelApplet(configFile, t, actions, parent, name),
		mInstance(new KInstance("mediacontrol")),
		mAboutData(new KAboutData("mediacontrol",
			I18N_NOOP("MediaControl"),
			MEDIACONTROL_VERSION,
			I18N_NOOP("A small control-applet for various media players"),
			KAboutData::License_GPL_V2,
			"(c) 2001-2004 Stefan Gehn",
			0,
			"http://metz.gehn.net"))
{
	_player = 0L;
	_prefsDialog = 0L;

	_configFrontend = new ConfigFrontend(config());
	// My own dcopclient
	_dcopClient = new DCOPClient();
	_dcopClient->registerAs("mediacontrol", false);

	mAboutData->addAuthor("Stefan Gehn", I18N_NOOP("Main Developer"),
		"metz@gehn.net", "http://metz.gehn.net");
	mAboutData->addAuthor("Robbie Ward", I18N_NOOP("Initial About-Dialog"),
		"wardy@robbieward.co.uk", "http://www.robbieward.co.uk");

	mAboutData->addCredit("Sascha Hoffman", I18N_NOOP("Button-Pixmaps"),
		"tisch.sush@gmx.de", 0);
	mAboutData->addCredit("Christian Hoffman", I18N_NOOP("Button-Pixmaps"),
		"tisch.crix@gmx.de", "http://www.crixensgfxcorner.de.vu/");
	mAboutData->addCredit("Ulrik Mikaelsson", I18N_NOOP("Fix for Noatun-Support"),
		"rawler@rsn.bth.se", 0);
	mAboutData->addCredit("Anthony J Moulen", I18N_NOOP("Fix for Vertical Slider"),
		"ajmoulen@moulen.org", 0);
	mAboutData->addCredit("Teemu Rytilahti", I18N_NOOP("Volume Control Implementation"),
		"teemu.rytilahti@kde-fi.org", 0);
	mAboutData->addCredit("Jan Spitalnik", I18N_NOOP("Fix for JuK-Support"),
		"honza@spitalnik.net", 0);
	mAboutData->addCredit("William Robinson", I18N_NOOP("mpd-Support"),
		"airbaggins@yahoo.co.uk", 0);

	setAcceptDrops(true);

	prev_button  = new TrayButton (this, "PREVIOUS");
	playpause_button  = new TrayButton (this, "PLAYPAUSE");
	stop_button  = new TrayButton (this, "STOP");
	next_button  = new TrayButton (this, "NEXT");
	time_slider  = new MCSlider (QSlider::Horizontal, this, "time_slider" );
	time_slider->setRange(0,0);
	time_slider->setValue(0);
	time_slider->setTracking( false );
	time_slider->installEventFilter(this);

	// request notification of changes in icon style
	kapp->addKipcEventMask(KIPC::IconChanged);
	connect(kapp, SIGNAL(iconChanged(int)), this, SLOT(slotIconChanged()));

	reparseConfig();

	rmbMenu = new KPopupMenu(this, "RMBMenu");
	rmbMenu->insertTitle(i18n("MediaControl"), 0, 0);
	rmbMenu->insertItem(SmallIcon("configure"), i18n("Configure MediaControl..."),
		this, SLOT(preferences()));
	rmbMenu->insertItem(i18n("About MediaControl"), this, SLOT(about()));
}

MediaControl::~MediaControl()
{
	delete _player;
	delete _configFrontend;
	delete _dcopClient;
	KGlobal::locale()->removeCatalogue("mediacontrol");
}

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

void MediaControl::dragEnterEvent(QDragEnterEvent* event)
{
	_player->dragEnterEvent(event);  // Just pass dnd to the playerInterface
}

void MediaControl::dropEvent(QDropEvent* event)
{
	_player->dropEvent(event);  // Just pass dnd to the playerInterface
}

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


void MediaControl::setSliderPosition(int len ,int time)
{
	time_slider->blockSignals(true);
	if(orientation() == Vertical)
	  time = len - time;

	if (mLastLen != len)
	  time_slider->setRange(0,len);
	mLastLen = len;

	if (mLastTime != time)
	  time_slider->setValue(time);
	mLastTime = time;

	time_slider->blockSignals(false);
}

void MediaControl::enableAll()
{
	prev_button->setDisabled(false);
	playpause_button->setDisabled(false);
        QToolTip::remove(playpause_button);
	stop_button->setDisabled(false);
	next_button->setDisabled(false);
	time_slider->setDisabled(false);
}

void MediaControl::disableAll()
{
	prev_button->setDisabled(true);
	playpause_button->setDisabled(false);
        QToolTip::add(playpause_button, i18n("Start the player"));
	stop_button->setDisabled(true);
	next_button->setDisabled(true);
	time_slider->setDisabled(true);
	if(_configFrontend->useCustomTheme()) {
		QString skindir = locate("data", "mediacontrol/"+_configFrontend->theme()+"/");
		playpause_button->setIconSet(SmallIconSet(locate("data",skindir+"play.png")));
	}
	else
		playpause_button->setIconSet(SmallIconSet("player_play"));
}

void MediaControl::slotPlayingStatusChanged(int status)
{
	if (mLastStatus == status)
		return;

	mLastStatus = status;
	QString skindir = locate("data", "mediacontrol/"+_configFrontend->theme()+"/");

	switch (status)
	{
		case (PlayerInterface::Stopped):
		case (PlayerInterface::Paused):
			if(_configFrontend->useCustomTheme())
				playpause_button->setIconSet(SmallIconSet(locate("data",skindir+"play.png")));
			else
				playpause_button->setIconSet(SmallIconSet("player_play"));
			break;
		case (PlayerInterface::Playing):
			if(_configFrontend->useCustomTheme())
				playpause_button->setIconSet(SmallIconSet(locate("data",skindir+"pause.png")));
			else
				playpause_button->setIconSet(SmallIconSet("player_pause"));
			break;
	}
}

void MediaControl::slotIconChanged()
{
	if(!_configFrontend->useCustomTheme())
	{
		prev_button->setIconSet(SmallIconSet("player_start"));
		if (_player->playingStatus() == PlayerInterface::Playing)
			playpause_button->setIconSet(SmallIconSet("player_pause"));
		else
			playpause_button->setIconSet(SmallIconSet("player_play"));
		stop_button->setIconSet(SmallIconSet("player_stop"));
		next_button->setIconSet(SmallIconSet("player_end"));
	}
}

// Dialogs =====================================================================

void MediaControl::preferences()
{
	if ( _prefsDialog )
	{
		_prefsDialog->raise();
	}
	else
	{
		_prefsDialog = new MediaControlConfig ( _configFrontend );
		connect ( _prefsDialog, SIGNAL(closing()),
			this, SLOT(slotClosePrefsDialog()) );
		connect ( _prefsDialog, SIGNAL(destroyed()),
			this, SLOT(slotPrefsDialogClosing()) );
		connect ( _prefsDialog, SIGNAL(configChanged()),
			this, SLOT(slotConfigChanged()) );
	}
}

void MediaControl::slotConfigChanged()
{
	reparseConfig();
}

void MediaControl::slotClosePrefsDialog()
{
	delete _prefsDialog;
}

void MediaControl::slotPrefsDialogClosing()
{
	if ( _prefsDialog )
		_prefsDialog = 0L;
}


void MediaControl::about()
{
	KAboutApplication aboutDlg(mAboutData);
	aboutDlg.exec();
}


void MediaControl::reportBug()
{
	KBugReport bugReport(this, true, mAboutData);
	bugReport.exec();
}


// Fixing the orientation problem in qslider.
void MediaControl::adjustTime(int time)
{
	if(orientation() == Vertical)
		emit(newJumpToTime(mLastLen - time));
	else
		emit(newJumpToTime(time));
}

// Config Stuff ================================================================

void MediaControl::reparseConfig()
{
//	kdDebug(90200) << "reparseConfig();" << endl;
	_configFrontend->reparseConfiguration();

	if (_player != 0L) // make sure there is no player-object
	{
		_player->disconnect(); // disconnect from all things

		time_slider->disconnect();
		prev_button->disconnect();
		playpause_button->disconnect();
		stop_button->disconnect();
		next_button->disconnect();

		delete slider_tooltip; // tooltip depends on _player : delete it before _player gets deleted
		slider_tooltip = 0L;

		delete _player;
		_player = 0L;
	}

	mLastLen = -1;
	mLastTime = -1;
	mLastStatus = -1;

	QString playerString = _configFrontend->player();


#ifdef HAVE_XMMS
	if (playerString == "XMMS")
	{
		_player = new XmmsInterface ();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()*1000),
			(_configFrontend->mouseWheelSpeed()*1000));
	}
	else
#endif
	if (playerString == "JuK")
	{
		_player = new JuKInterface();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()),
			(_configFrontend->mouseWheelSpeed()));
	}
	else if (playerString == "Amarok")
	{
		_player = new AmarokInterface();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()),
			(_configFrontend->mouseWheelSpeed()));
	}
	else if (playerString == "KsCD")
	{
		_player = new KsCDInterface();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()),
			(_configFrontend->mouseWheelSpeed()));
	}
	else if (playerString == "mpd")
	{
		_player = new MpdInterface();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()),
			(_configFrontend->mouseWheelSpeed()));
	}
	else // Fallback is Noatun
	{
		_player = new NoatunInterface();
		time_slider->setSteps((_configFrontend->mouseWheelSpeed()),
			(_configFrontend->mouseWheelSpeed()));
	}

	//  this signal gets emitted by a playerInterface when the player's playtime changed
	connect(_player, SIGNAL(newSliderPosition(int,int)),
		this, SLOT(setSliderPosition(int,int)));

	connect(_player, SIGNAL(playerStarted()), SLOT(enableAll()));
	connect(_player, SIGNAL(playerStopped()), SLOT(disableAll()));
	connect(_player, SIGNAL(playingStatusChanged(int)), SLOT(slotPlayingStatusChanged(int)));

	// do we use our icons or the default ones from KDE?
	if(_configFrontend->useCustomTheme())
	{
		// load theme
		QString skindir = locate("data", "mediacontrol/"+_configFrontend->theme()+"/");

		// the user has to take care if all pixmaps are there, we only check for one of them
		if (QFile(skindir+"play.png").exists())
		{
			prev_button->setIconSet(SmallIconSet(locate("data",skindir+"prev.png")));
			if (_player->playingStatus() == PlayerInterface::Playing)
				playpause_button->setIconSet(SmallIconSet(locate("data",skindir+"play.png")));
			else
				playpause_button->setIconSet(SmallIconSet(locate("data",skindir+"pause.png")));
			stop_button->setIconSet(SmallIconSet(locate("data",skindir+"stop.png")));
			next_button->setIconSet(SmallIconSet(locate("data",skindir+"next.png")));
		}
		else // icon-theme is invalid or not there
		{
			KNotifyClient::event(winId(), KNotifyClient::warning,
				i18n("There was trouble loading theme %1. Please choose" \
					" a different theme.").arg(skindir));

			// default to kde-icons, they have to be installed :)
			slotIconChanged();

			// and open prefs-dialog
			preferences();
		}
	}
	else // KDE default-icons, assuming that these icons exist!
	{
		// sets icons from kde
		slotIconChanged();
	}

	slider_tooltip = new MediaControlToolTip(time_slider, _player);

	connect(prev_button, SIGNAL(clicked()), _player, SLOT(prev()));
	connect(playpause_button, SIGNAL(clicked()), _player, SLOT(playpause()));
	connect(stop_button, SIGNAL(clicked()), _player, SLOT(stop()));
	connect(next_button, SIGNAL(clicked()), _player, SLOT(next()));

	connect(time_slider, SIGNAL(sliderPressed()), _player, SLOT(sliderStartDrag()));
	connect(time_slider, SIGNAL(sliderReleased()), _player, SLOT(sliderStopDrag()));
	connect(time_slider, SIGNAL(valueChanged(int)), this, SLOT(adjustTime(int)));
	connect(time_slider, SIGNAL(volumeUp()), _player, SLOT(volumeUp()));
	connect(time_slider, SIGNAL(volumeDown()), _player, SLOT(volumeDown()));
	connect(this, SIGNAL(newJumpToTime(int)), _player, SLOT(jumpToTime(int)));
}

// Widget Placement ===================================================================

// kicker wants to know what width we need for a given height
// (this is called when being a HORIZONTAL panel)
int MediaControl::widthForHeight(int height) const
{
//	kdDebug(90200) << "kicker height: " << height << endl;
//	kdDebug(90200) << "slider needs: " << time_slider->minimumSizeHint().height() << endl;

	// slider height + button height
	if ( height >= (time_slider->minimumSizeHint().height()+MC_BUTTONSIZE) )
	{	// slider UNDER buttons
		// (5 * button width + spaces between them);
		return (4*MC_BUTTONSIZE+10);
	}
	else
	{	// slider ASIDE buttons
		// (5 * button width + spaces between them) * 2 [size of slider = size of all buttons]
		return ((4*MC_BUTTONSIZE+10)*2);
	}
}

// kicker wants to know what height we need for a given width
// (this is called when being a VERTICAL panel)
int MediaControl::heightForWidth(int width) const
{
//	kdDebug(90200) << "kicker width: " << width << endl;

	// slider height + button height
	if ( width >= (time_slider->minimumSizeHint().width()+MC_BUTTONSIZE) )
	{	// slider ASIDE icons
		// (5 * button width + spaces between them);
		return (4*MC_BUTTONSIZE+10);
	}
	else
	{	// slider UNDER buttons
		// (5 * button width + spaces between them) * 2
		// because the size of the slider = the size of all buttons
		return ((4*MC_BUTTONSIZE+10)*2);
	}
}

void MediaControl::mousePressEvent(QMouseEvent* e)
{
	if (e->button() == QMouseEvent::RightButton)
		rmbMenu->popup(e->globalPos());
}

bool MediaControl::eventFilter(QObject *, QEvent *e)
{
	if (e->type() == QEvent::MouseButtonPress)
	{
		QMouseEvent *me = static_cast<QMouseEvent *>(e);
		if (me->button() == QMouseEvent::RightButton)
		{
			rmbMenu->popup(me->globalPos());
			return true;
		}
	}
	return false;
}

// Danger: Weird Code ahead! ;))
void MediaControl::resizeEvent( QResizeEvent* )
{
//	kdDebug(90200) << "resizeEvent()" << endl;
	int w = width();
	int h = height();
	if ( orientation() == Vertical )
	{		// ====== VERTICAL =================================================
		time_slider->setOrientation(QSlider::Vertical);
		int slider_width = time_slider->minimumSizeHint().width();
		// some styles need more space for sliders than avilable in very small panels :(
		if ( slider_width > w ) slider_width = w;

		// that width would be needed to put the slider aside the buttons
		if ( w >= (slider_width+MC_BUTTONSIZE) )
		{	// Slider ASIDE icons
			int applet_space = (w - (slider_width+MC_BUTTONSIZE) ) / 2;
			if ( applet_space < 0 )
				applet_space = 0;

			prev_button->setGeometry  ( applet_space, 1, MC_BUTTONSIZE, MC_BUTTONSIZE );
			playpause_button->setGeometry  ( applet_space, 3+1*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			stop_button->setGeometry  ( applet_space, 5+2*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			next_button->setGeometry  ( applet_space, 7+3*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			time_slider->setGeometry  ( applet_space+MC_BUTTONSIZE, 1, slider_width, NO_BUTTONS*MC_BUTTONSIZE+8 );
		}
		else
		{	// Slider UNDER Icons
			int slider_space = (w - slider_width)/2;
			int button_space = (w - MC_BUTTONSIZE)/2;

			prev_button->setGeometry  ( button_space, 1 , MC_BUTTONSIZE, MC_BUTTONSIZE );
			playpause_button->setGeometry  ( button_space, 3+1*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			stop_button->setGeometry  ( button_space, 5+2*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			next_button->setGeometry  ( button_space, 7+3*MC_BUTTONSIZE, MC_BUTTONSIZE, MC_BUTTONSIZE );
			time_slider->setGeometry  ( slider_space, 9+4*MC_BUTTONSIZE, slider_width, NO_BUTTONS*MC_BUTTONSIZE+8 );
		}
	}
	else	// ====== HORIZONTAL ===============================================
	{
		time_slider->setOrientation(QSlider::Horizontal);
		int slider_height = time_slider->minimumSizeHint().height();
		// some styles need more space for sliders than avilable in very small panels :(
		if ( slider_height > h ) slider_height = h;

		// that h would be needed to put the slider under the buttons
		if ( h >= (slider_height+MC_BUTTONSIZE) )
		{	// Slider UNDER icons
			int applet_space = (h-(slider_height+MC_BUTTONSIZE))/2;
			if ( applet_space < 0 )
				applet_space = 0;

			prev_button->setGeometry  ( 1 , applet_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			playpause_button->setGeometry  ( 3+MC_BUTTONSIZE, applet_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			stop_button->setGeometry  ( 5+2*MC_BUTTONSIZE, applet_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			next_button->setGeometry  ( 7+3*MC_BUTTONSIZE, applet_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			time_slider->setGeometry  ( 1, applet_space + MC_BUTTONSIZE, NO_BUTTONS*MC_BUTTONSIZE+8, slider_height );
		}
		else
		{	// Slider ASIDE Icons
			int slider_space = (h - slider_height)/2;
			int button_space = (h - MC_BUTTONSIZE)/2;

			prev_button->setGeometry  ( 1 , button_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			playpause_button->setGeometry  ( 3+1*MC_BUTTONSIZE, button_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			stop_button->setGeometry  ( 5+2*MC_BUTTONSIZE, button_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			next_button->setGeometry  ( 7+3*MC_BUTTONSIZE, button_space, MC_BUTTONSIZE, MC_BUTTONSIZE );
			time_slider->setGeometry  ( 9+4*MC_BUTTONSIZE, slider_space, NO_BUTTONS*MC_BUTTONSIZE+8, slider_height );
		}
	}
}

// Our Button ========================================================================

TrayButton::TrayButton(QWidget* parent, const char* name)
	: QPushButton (parent, name)
{
	setBackgroundMode(PaletteBackground);
	setBackgroundOrigin(AncestorOrigin);
}

void TrayButton::drawButton(QPainter *p)
{
	QPixmap bg(size());
	QPainter pbg;

	pbg.begin(&bg);
	if (parentWidget() && parentWidget()->backgroundPixmap())
		pbg.drawTiledPixmap(0, 0, width(), height(), *(parentWidget()->backgroundPixmap()), x(), y());
	else
		pbg.fillRect(rect(), colorGroup().brush(QColorGroup::Background));


	if (isDown() || isOn())
	{
		// Draw shapes to indicate the down state.
		style().drawPrimitive(QStyle::PE_Panel, &pbg, rect(), colorGroup(),
			QStyle::Style_Sunken);
	}
	pbg.end();

	p->drawPixmap(0,0,bg); // draw the background

	QPixmap pixmap = iconSet()->pixmap(
		QIconSet::Automatic,
		isEnabled() ? QIconSet::Normal : QIconSet::Disabled,
		isOn() ? QIconSet::On : QIconSet::Off);

	// draw icon
	if(!pixmap.isNull())
	{
		QRect br(1, 1, width()-2, height()-2);
		int dx = (br.width() - pixmap.width()) / 2;
		int dy = (br.height() - pixmap.height()) / 2;
		p->drawPixmap(br.x() + dx, br.y() + dy, pixmap);
	}
}
