/***************************************************************************
	kjtexdisplay.cpp
	---------------------------------------------
	Displays for time and other things
	using fonts provided by KJFont
	---------------------------------------------
	Maintainer: Stefan Gehn <sgehn@gmx.net>

 ***************************************************************************/

// local includes
#include "kjtextdisplay.h"
#include "kjfont.h"
#include "kjprefs.h"

// kde includes
#include <klocale.h>
#include <kdebug.h>
#include <kpixmap.h>
#include <kurl.h>
#include <krun.h>
#include <kmimemagic.h>

// arts-includes, needed for pitch
#include <artsmodules.h>
#include <arts/reference.h>
#include <arts/soundserver.h>
#include <arts/kmedia2.h>

// noatun includes
#include <noatun/player.h>
#include <noatun/engine.h>

/*******************************************
 * KJFilename
 *******************************************/

KJFilename::KJFilename(const QStringList &l, KJLoader *p)
	: QObject(0), KJWidget(p), mBack(0)
{
	int x  = l[1].toInt();
	int y  = l[2].toInt();
	int xs = l[3].toInt() - x;
	int ys = l[4].toInt() - y;

	// fix for all those weird skins where the filenamewindow has more
	// height than needed for the font
	// ( ... usually resulting in garbage on-screen )
	if ( ys > (textFont().fontHeight()) )
		ys = textFont().fontHeight();

	// background under filename-scroller
	QPixmap tmp  = p->pixmap(p->item("backgroundimage")[1]);
	mBack = new KPixmap ( QSize(xs,ys) );
	bitBlt( mBack, 0, 0, &tmp, x, y, xs, ys, Qt::CopyROP );

	setRect(x,y,xs,ys);

	// how far it moves per cycle
	// TODO: make that configurable for the user

	//mDistance = 1;
//	mDistance = (int)(textFont().fontWidth()/2);
	readConfig();

	prepareString(i18n("Welcome to Noatun").local8Bit());
	killTimers();
}

KJFilename::~KJFilename()
{
	delete mBack;
}

void KJFilename::paint(QPainter *p, const QRect &)
{
	QPixmap temp ( rect().width(), rect().height() );

	// draw background into buffer
	bitBlt ( &temp, 0, 0, mBack, 0, 0, -1, -1, Qt::CopyROP );
	// draw font into buffer
	bitBlt( &temp, 0, 0, &mView, 0, 0, rect().width(), rect().height(), Qt::CopyROP);
	// and draw it on screen
	bitBlt(p->device(), rect().topLeft(), &temp,
	       QRect(0,0,-1,-1), Qt::CopyROP);
}

void KJFilename::timerEvent(QTimerEvent *)
{
	int height = mView.height();
	int width = mView.width();

	QBitmap cycleMask ( mDistance, height ); // temporary-space for moving parts of the mask
	QPixmap cycle ( mDistance, height );	// temporary-space for moving parts of the pixmap
	QBitmap newMask ( *mView.mask() );		// save old mask

	// copy mask like the same way we're doing it with the pixmap
	// a mask does not get copied on a bitblt automatically, we have to do
	// it "by hand"
	bitBlt(&cycleMask, 0,0, &newMask, 0,0, mDistance, height, Qt::CopyROP);
	bitBlt(&newMask, 0,0, &newMask, mDistance, 0, width-mDistance, height, Qt::CopyROP);
	bitBlt(&newMask, width-mDistance, 0, &cycleMask, 0,0, mDistance, height, Qt::CopyROP);

	bitBlt(&cycle, 0,0, &mView, 0,0, mDistance, height, Qt::CopyROP);
	bitBlt(&mView, 0,0, &mView, mDistance, 0, width-mDistance, height, Qt::CopyROP);
	bitBlt(&mView, width-mDistance, 0, &cycle, 0,0, mDistance, height, Qt::CopyROP);

	// apply the newly created mask
	mView.setMask(newMask);

	repaint();
}

bool KJFilename::mousePress(const QPoint &)
{
	return true;
}

void KJFilename::mouseRelease(const QPoint &, bool in)
{
	if (!in) // only do something if users is still inside the button
		return;

	if ( !napp->player()->current() )
		return;

	KURL dirURL = napp->player()->current().url().upURL();

	KMimeMagicResult *result = KMimeMagic::self()->findFileType( dirURL.path() );

	// TODO: Maybe test for protocol type?
//	if ( napp->player()->current().url().protocol() == "file" )
	if ( result->isValid() )
		KRun::runURL ( dirURL, result->mimeType() );
}

void KJFilename::readConfig()
{
//	kdDebug(66666) << "KJFilename::readConfig()" << endl;
	mDistance = (int)( textFont().fontWidth() * KJLoader::kjofol->prefs()->titleMovingDistance() );
	if ( mDistance <= 0 )
		mDistance = 1;
	mTimerUpdates = KJLoader::kjofol->prefs()->titleMovingUpdates();
	textFont().recalcSysFont();
	mLastTitle=""; // invalidate title so it gets repainted on next timeUpdate()
}

void KJFilename::prepareString(const QCString &str)
{
	killTimers(); // i.e. stop timers

	mView = textFont().draw(str, rect().width());

	startTimer(mTimerUpdates);
}

void KJFilename::timeUpdate(int)
{
	if ( !napp->player()->current() ) // just for safety
		return;

	QCString title = QCString( napp->player()->current().title().local8Bit() );

	if ( title == mLastTitle )
		return;

	mLastTitle = title;

	QCString timestring = napp->player()->lengthString().local8Bit();
	timestring = timestring.mid(timestring.find('/')+1);
	prepareString ( title + " (" + timestring + ")   ");
}

QString KJFilename::tip()
{
	if ( !napp->player()->current() ) // just for safety
		return i18n("Filename");
	else
		return napp->player()->current().url().prettyURL();
}


/*******************************************
 * KJTime
 *******************************************/

KJTime::KJTime(const QStringList &l, KJLoader *p)
	: KJWidget(p), mBack(0)
{
	int x  = l[1].toInt();
	int y  = l[2].toInt();
	int xs = l[3].toInt() - x;
	int ys = l[4].toInt() - y;

	// fix for all those weird skins where the timewindow
	// has more space than needed for the font
	int maxNeededHeight = timeFont().fontHeight();
	if ( ys > maxNeededHeight )
		ys = maxNeededHeight;

	// five digits + spacing between them
	int maxNeededWidth = ( 5 *timeFont().fontWidth() ) + ( 4 * timeFont().fontSpacing() );
	if ( xs > maxNeededWidth )
		xs = maxNeededWidth;

	// background under time-display
	QPixmap tmp  = p->pixmap(p->item("backgroundimage")[1]);
	mBack = new KPixmap ( QSize(xs,ys) );
	bitBlt( mBack, 0, 0, &tmp, x, y, xs, ys, Qt::CopyROP );

	setRect(x,y,xs,ys);

	readConfig();

	prepareString("00:00");
}

KJTime::~KJTime()
{
	delete mBack;
}

void KJTime::paint(QPainter *p, const QRect &)
{
//	kdDebug(66666) << "KJTime::paint(QPainter *p, const QRect &)" << endl;
	QPixmap temp ( rect().width(), rect().height() );

	// draw background into buffer
	bitBlt ( &temp, 0, 0, mBack, 0, 0, -1, -1, Qt::CopyROP );
	// draw time-display into buffer (that's a pixmap with a mask applied)
	bitBlt( &temp, 0, 0, &mTime, 0, 0, rect().width(), rect().height(), Qt::CopyROP);

	// and draw it on screen
	bitBlt(p->device(), rect().topLeft(), &temp,
	       QRect(0,0, rect().width(), rect().height()), Qt::CopyROP);
}

bool KJTime::mousePress(const QPoint &)
{
	return true;
}

void KJTime::mouseRelease(const QPoint &, bool in)
{
	if (!in) // only do something if users is still inside the button
		return;

	countDown = !countDown;
	napp->setDisplayRemaining( countDown );
//	KJLoader::kjofol->prefs()->setTimeCountMode( countDown );
}

void KJTime::readConfig()
{
//	kdDebug(66666) << "KJTime::readConfig()" << endl;
	countDown = napp->displayRemaining();
	timeFont().recalcSysFont();
	mLastTime=""; // invalidate time so it gets repainted on next timeUpdate()
}

QString KJTime::lengthString ( void )
{
	int pos = 0;
	QString posString;
	int secs = 0,
	seconds = 0,
	minutes = 0,
	hours = 0;

	if ( countDown )
	{	// current remaining time
		pos = napp->player()->getLength() - napp->player()->getTime();
	}
	else
	{	// current time
		pos = napp->player()->getTime();
	}

	if ( pos < 0 )
	{
		posString = "00:00";
	}
	else
	{ // get the position
		secs = pos / 1000; // convert milliseconds -> seconds

		seconds = secs % 60;
		minutes = (secs - seconds) / 60;
		hours = minutes / 60;
		minutes %= 60; // remove the hours from minutes ;)

//		cerr << "  " << hours << ":" << minutes << ":" << seconds << endl;

//		if ( hours > 0 ) // looks ugly :)
		if ( (napp->player()->getLength()/1000) >= 3600 ) // displays hh:mm if file is long
		{
			posString.sprintf("%d:%.2d", hours, minutes);
		}
		else // displays mm:ss
		{
			posString.sprintf("%.2d:%.2d", minutes, seconds);
		}
	}

	return posString;
}

void KJTime::timeUpdate(int)
{
//	kdDebug(66666) << "START KJTime::timeUpdate(int)" << endl;
	if (!napp->player()->current())
		return;

	prepareString( (lengthString()).latin1() );

//	kdDebug(66666) << "END KJTime::timeUpdate(int)" << endl;
}

void KJTime::prepareString(const QCString &str)
{
//	kdDebug(66666) << "START KJTime::prepareString(const QCString &str)" << endl;
	if ( str == mLastTime )
		return;

	mLastTime = str;
	mTime = timeFont().draw(str, rect().width());

	repaint();
//	kdDebug(66666) << "END KJTime::prepareString(const QCString &str)" << endl;
}

QString KJTime::tip()
{
	if ( countDown )
		return i18n("Play time left");
	else
		return i18n("Current play time");
}


/*******************************************
 * KJVolumeText
 *******************************************/

KJVolumeText::KJVolumeText(const QStringList &l, KJLoader *p)
	: KJWidget(p), mBack(0)
{
	int x=l[1].toInt();
	int y=l[2].toInt();
	int xs=l[3].toInt()-x;
	int ys=l[4].toInt()-y;

	// fix for all those weird skins where the timewindow has more space than needed for the font
	if ( ys > (volumeFont().fontHeight()) )
		ys = volumeFont().fontHeight();

	// 3 digits for volume (1-100)
	// + spaces according to spacing
	// + percentage letter (seems to be 1px wider than a normal char)
	int tempWidth = (3*volumeFont().fontWidth()) + (2*volumeFont().fontSpacing()) + ((volumeFont().fontWidth()+1));
	if ( xs > ( tempWidth ) )
		xs = tempWidth;

	// background under volumetext-display
	QPixmap tmp  = p->pixmap(p->item("backgroundimage")[1]);
	mBack = new KPixmap ( QSize(xs,ys) );
	bitBlt( mBack, 0, 0, &tmp, x, y, xs, ys, Qt::CopyROP );

	setRect(x,y,xs,ys);

	prepareString("100%");
}

KJVolumeText::~KJVolumeText()
{
	delete mBack;
}

void KJVolumeText::paint(QPainter *p, const QRect &)
{
	QPixmap temp ( rect().width(), rect().height() );

	// draw background into buffer
	bitBlt ( &temp, 0, 0, mBack, 0, 0, -1, -1, Qt::CopyROP );
	// draw time-display into buffer
	bitBlt( &temp, 0, 0, &mVolume, 0, 0, rect().width(), rect().height(), Qt::CopyROP);

	// and draw it on screen
	bitBlt(p->device(), rect().topLeft(), &temp,
	       QRect(0,0,-1,-1), Qt::CopyROP);
}

bool KJVolumeText::mousePress(const QPoint &)
{
	return false;
}

void KJVolumeText::readConfig()
{
	volumeFont().recalcSysFont();
	mLastVolume=""; // invalidate value so it gets repainted on next timeUpdate()
}

void KJVolumeText::timeUpdate(int)
{
	QCString volume;

	if (!napp->player()->current())
		return;

	volume.sprintf("%d%%", napp->player()->volume() ); // FIXME: is sprintf safe to use?

	prepareString(volume);
}

void KJVolumeText::prepareString(const QCString &str)
{
	if ( str == mLastVolume )
		return;

	mLastVolume = str;
	mVolume = volumeFont().draw(str, rect().width());

	repaint();
}

QString KJVolumeText::tip()
{
	return i18n("Volume");
}


/*******************************************
 * KJPitchText
 *******************************************/

KJPitchText::KJPitchText(const QStringList &l, KJLoader *p)
	: KJWidget(p), mBack(0)
{
	int x  = l[1].toInt();
	int y  = l[2].toInt();
	int xs = l[3].toInt() - x;
	int ys = l[4].toInt() - y;

	// fix for all those weird skins where the timewindow has more space than needed for the font
	if ( ys > (pitchFont().fontHeight()) )
		ys = pitchFont().fontHeight();

	// 3 digits for volume (1-100), spaces according to spacing and percentage letter
	int tempWidth = (3*pitchFont().fontWidth()) + (2*pitchFont().fontSpacing());
	if ( xs > tempWidth )
		xs = tempWidth;

	// background under time-display
	QPixmap tmp  = p->pixmap(p->item("backgroundimage")[1]);
	mBack = new KPixmap ( QSize(xs,ys) );
	bitBlt( mBack, 0, 0, &tmp, x, y, xs, ys, Qt::CopyROP );

	setRect(x,y,xs,ys);

	prepareString("100");
}

KJPitchText::~KJPitchText()
{
	delete mBack;
}


void KJPitchText::paint(QPainter *p, const QRect &)
{
	QPixmap temp ( rect().width(), rect().height() );

	// draw background into buffer
	bitBlt ( &temp, 0, 0, mBack, 0, 0, -1, -1, Qt::CopyROP );
	// draw time-display into buffer
	bitBlt( &temp, 0, 0, &mSpeed, 0, 0, rect().width(), rect().height(), Qt::CopyROP);
	// and draw it on screen
	bitBlt(p->device(), rect().topLeft(), &temp, QRect(0,0,-1,-1), Qt::CopyROP);
}

bool KJPitchText::mousePress(const QPoint &)
{
	return true;
}

void KJPitchText::mouseRelease(const QPoint &, bool in)
{
	if (!in)
		return;

	Arts::PlayObject playobject = napp->player()->engine()->playObject();
	Arts::PitchablePlayObject pitchable = Arts::DynamicCast(playobject);

	if (pitchable.isNull())
		return;

	pitchable.speed( 1.00f ); // reset pitch
}

void KJPitchText::readConfig()
{
	pitchFont().recalcSysFont();
	mLastPitch=""; // invalidate value so it gets repainted on next timeUpdate()
}

void KJPitchText::timeUpdate(int)
{
	QCString speed;

	if (!napp->player()->current())
		return;

	Arts::PlayObject playobject = napp->player()->engine()->playObject();
	Arts::PitchablePlayObject pitchable = Arts::DynamicCast(playobject);

	if (pitchable.isNull())
		return;

	speed.setNum ( (int) ((float)pitchable.speed()*(float)100) );
	prepareString ( speed );
}

void KJPitchText::prepareString(const QCString &str)
{
	if ( str == mLastPitch )
		return;

	mLastPitch = str;
	mSpeed = pitchFont().draw(str, rect().width());

	repaint();
}

QString KJPitchText::tip()
{
	return i18n("Pitch");
}


/*******************************************
 * KJFileInfo
 *******************************************/

KJFileInfo::KJFileInfo(const QStringList &l, KJLoader *p)
	: KJWidget(p), mBack(0)
{
	mInfoType = l[0]; // type of info-display

	int x  = l[1].toInt();
	int y  = l[2].toInt();
	int xs = l[3].toInt() - x;
	int ys = l[4].toInt() - y;

	// fix for all those weird skins where the timewindow
	// has more space than needed for the font
	int maxNeededHeight = timeFont().fontHeight();
	if ( ys > maxNeededHeight )
		ys = maxNeededHeight;

	// five digits + spacing between them
	int maxNeededWidth = ( 3 *timeFont().fontWidth() ) + ( 2 * timeFont().fontSpacing() );
	if ( xs > maxNeededWidth )
		xs = maxNeededWidth;

	// background under info-display
	QPixmap tmp  = p->pixmap(p->item("backgroundimage")[1]);
	mBack = new KPixmap ( QSize(xs,ys) );
	bitBlt( mBack, 0, 0, &tmp, x, y, xs, ys, Qt::CopyROP );

	setRect(x,y,xs,ys);

	prepareString("");
}

KJFileInfo::~KJFileInfo()
{
	delete mBack;
}

void KJFileInfo::paint(QPainter *p, const QRect &)
{
	QPixmap temp ( rect().width(), rect().height() );

	// draw background into buffer
	bitBlt ( &temp, 0, 0, mBack, 0, 0, -1, -1, Qt::CopyROP );
	// draw time-display into buffer (that's a pixmap with a mask applied)
	bitBlt( &temp, 0, 0, &mTime, 0, 0, rect().width(), rect().height(), Qt::CopyROP);

	// and draw it on screen
	bitBlt(p->device(), rect().topLeft(), &temp,
	       QRect(0,0, rect().width(), rect().height()), Qt::CopyROP);
}

bool KJFileInfo::mousePress(const QPoint &)
{
	return false;
}

void KJFileInfo::readConfig()
{
	textFont().recalcSysFont();
	mLastTime=""; // invalidate value so it gets repainted on next timeUpdate()
}

void KJFileInfo::timeUpdate(int)
{
	if (!napp->player()->current())
		return;

	const PlaylistItem &item = napp->player()->current();
	QString prop;

	if ( mInfoType == "mp3khzwindow" )
	{
		prop = item.property("samplerate");
		prop.truncate(2); // we just want 44 instead of 44100
	}
	else if ( mInfoType == "mp3kbpswindow" )
	{
		prop = item.property("bitrate");
	}
	else	// for safety: no infoType we know of
		return;

	if (prop.isNull())
		prop="";
	prepareString( prop.latin1() );
}

void KJFileInfo::prepareString(const QCString &str)
{
	if ( str == mLastTime )
		return;
	mLastTime = str;
	mTime = textFont().draw(str, rect().width());
	repaint();
}

QString KJFileInfo::tip()
{
	if ( mInfoType == "mp3khzwindow" )
		return i18n("Sample rate in kHz");
	else if ( mInfoType == "mp3kbpswindow" )
		return i18n("Bitrate in kbps");

	return QString();
}

#include "kjtextdisplay.moc"
