//
// AmarokSystray
//
// Contributors: Stanislav Karchebny <berkus@users.sf.net>, (C) 2003
//               berkus, mxcl, eros, eean
//               Timothy Pearson <kb9vqf@pearsoncomputing.net> (c) 2010
//
// Copyright: like rest of Amarok
//

#include "amarok.h"
#include "amarokconfig.h"
#include "enginecontroller.h"
#include "systray.h"

#include <tqevent.h>
#include <tqimage.h>
#include <tdeaction.h>
#include <tdeapplication.h>
#include <tdepopupmenu.h>
#include <kiconeffect.h>
#include <tdestandarddirs.h>

namespace Amarok
{
    static TQPixmap
    loadOverlay( const char *iconName, int iconWidth )
    {
        return TQImage( locate( "data", TQString( "amarok/images/b_%1.png" ).arg( iconName ) ), "PNG" ).smoothScale( ((iconWidth/2)-(iconWidth/20)), ((iconWidth/2)-(iconWidth/20)) );
    }
}


Amarok::TrayIcon::TrayIcon( TQWidget *playerWidget )
        : KSystemTray( playerWidget )
        , EngineObserver( EngineController::instance() )
        , trackLength( 0 )
        , mergeLevel( -1 )
        , overlay( 0 )
        , blinkTimerID( 0 )
        , overlayVisible( false )
        , m_lastFmMode( false )
{
    TDEActionCollection* const ac = Amarok::actionCollection();

    setAcceptDrops( true );

    ac->action( "prev"        )->plug( contextMenu() );
    ac->action( "play_pause"  )->plug( contextMenu() );
    ac->action( "stop"        )->plug( contextMenu() );
    ac->action( "next"        )->plug( contextMenu() );

    //seems to be necessary
    TDEAction *quit = actionCollection()->action( "file_quit" );
    quit->disconnect();
    connect( quit, TQ_SIGNAL(activated()), tdeApp, TQ_SLOT(quit()) );

    baseIcon     = KSystemTray::loadIcon( "amarok" );
    playOverlay  = Amarok::loadOverlay( "play", baseIcon.width() );
    pauseOverlay = Amarok::loadOverlay( "pause", baseIcon.width() );
    overlayVisible = false;

    //paintIcon();
    setPixmap( baseIcon );
}

bool
Amarok::TrayIcon::event( TQEvent *e )
{
    switch( e->type() )
    {
    case TQEvent::Drop:
    case TQEvent::Wheel:
    case TQEvent::DragEnter:
        return Amarok::genericEventHandler( this, e );

    case TQEvent::Timer:
        if( static_cast<TQTimerEvent*>(e)->timerId() != blinkTimerID )
            return KSystemTray::event( e );

        // if we're playing, blink icon
        if ( overlay == &playOverlay )
        {
            overlayVisible = !overlayVisible;
            paintIcon( mergeLevel, true );
        }

        return true;

    case TQEvent::MouseButtonPress:
        if( static_cast<TQMouseEvent*>(e)->button() == TQt::MidButton )
        {
            EngineController::instance()->playPause();

            return true;
        }

        //else FALL THROUGH

    default:
        return KSystemTray::event( e );
    }
}

void Amarok::TrayIcon::resizeTrayIcon ()
{
	// Honor Free Desktop specifications that allow for arbitrary system tray icon sizes
	baseIcon = KSystemTray::loadSizedIcon( "amarok", width() );
	if (overlay == &pauseOverlay) {
		pauseOverlay = Amarok::loadOverlay( "pause", width() );
		overlay = &pauseOverlay;
	}
	if (overlay == &playOverlay) {
		playOverlay = Amarok::loadOverlay( "play", width() );
		overlay = &playOverlay;
	}
	playOverlay  = Amarok::loadOverlay( "play", width() );
	pauseOverlay = Amarok::loadOverlay( "pause", width() );
	grayedIcon = TQPixmap();
	alternateIcon = TQPixmap();
	paintIcon( -1, true );
}

void Amarok::TrayIcon::resizeEvent ( TQResizeEvent * )
{
	resizeTrayIcon();
}

void Amarok::TrayIcon::showEvent ( TQShowEvent *se )
{
	resizeTrayIcon();
	KSystemTray::showEvent(se);
}

void
Amarok::TrayIcon::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
{
    // stop timer
    if ( blinkTimerID )
    {
        killTimer( blinkTimerID );
        blinkTimerID = 0;
    }
    // draw overlay
    overlayVisible = true;

    // draw the right overlay for each state
    switch( state )
    {
    case Engine::Paused:
        overlay = &pauseOverlay;
        paintIcon( mergeLevel, true );
        break;

    case Engine::Playing:
        overlay = &playOverlay;
        if( AmarokConfig::animateTrayIcon() )
           blinkTimerID = startTimer( 1500 );  // start 'blink' timer

        paintIcon( mergeLevel, true ); // repaint the icon
        break;

    case Engine::Empty:
        overlayVisible = false;
        paintIcon( -1, true ); // repaint the icon
                               // fall through to default:
    default:
        setLastFm( false );
    }
}

void
Amarok::TrayIcon::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ )
{
    trackLength = bundle.length() * 1000;
    setLastFm( bundle.url().protocol() == "lastfm" );
}

void
Amarok::TrayIcon::engineTrackPositionChanged( long position, bool /*userSeek*/ )
{
    mergeLevel = trackLength ? ((baseIcon.height() + 1) * position) / trackLength : -1;
    paintIcon( mergeLevel );
}

void
Amarok::TrayIcon::paletteChange( const TQPalette & op )
{
    if ( palette().active().highlight() == op.active().highlight() || alternateIcon.isNull() )
        return;

    alternateIcon.resize( 0, 0 );
    paintIcon( mergeLevel, true );
}

void
Amarok::TrayIcon::paintIcon( int mergePixels, bool force )
{
    // skip redrawing the same pixmap
    static int mergePixelsCache = 0;
    if ( mergePixels == mergePixelsCache && !force ) {
        return;
    }
    mergePixelsCache = mergePixels;

    if ( mergePixels < 0 ) {
        return blendOverlay( baseIcon );
    }

    // make up the grayed icon
    if ( grayedIcon.isNull() )
    {
        TQImage tmpTrayIcon = baseIcon.convertToImage();
        TDEIconEffect::semiTransparent( tmpTrayIcon );
        grayedIcon = tmpTrayIcon;
    }

    // make up the alternate icon (use highlight color but more saturated)
    if ( alternateIcon.isNull() )
    {
        TQImage tmpTrayIcon = baseIcon.convertToImage();
        // eros: this looks cool with dark red blue or green but sucks with
        // other colors (such as kde default's pale pink..). maybe the effect
        // or the blended color has to be changed..
        TQColor saturatedColor = palette().active().highlight();
        int hue, sat, value;
        saturatedColor.getHsv( &hue, &sat, &value );
        saturatedColor.setHsv( hue, sat > 200 ? 200 : sat, value < 100 ? 100 : value );
        TDEIconEffect::colorize( tmpTrayIcon, saturatedColor/* TQt::blue */, 0.9 );
        alternateIcon = tmpTrayIcon;
    }

    if ( mergePixels >= alternateIcon.height() ) {
        return blendOverlay( grayedIcon );
    }
    if ( mergePixels == 0 ) {
        return blendOverlay( alternateIcon );
    }

    // mix [ grayed <-> colored ] icons
    TQPixmap tmpTrayPixmap = alternateIcon;
    copyBlt( &tmpTrayPixmap, 0,0, &grayedIcon, 0,0,
            alternateIcon.width(), mergePixels>0 ? mergePixels-1 : 0 );
    blendOverlay( tmpTrayPixmap );
}

void
Amarok::TrayIcon::blendOverlay( TQPixmap &sourcePixmap )
{
    if ( !overlayVisible || !overlay || overlay->isNull() ) {
        return setPixmap( sourcePixmap ); // @since 3.2
    }

    // here comes the tricky part.. no tdefx functions are helping here.. :-(
    // we have to blend pixmaps with different sizes (blending will be done in
    // the bottom-left corner of source pixmap with a smaller overlay pixmap)
    int opW = overlay->width(),
        opH = overlay->height(),
        opX = 1,
        opY = sourcePixmap.height() - opH;

    // get the rectangle where blending will take place
    TQPixmap sourceCropped( opW, opH, sourcePixmap.depth() );
    copyBlt( &sourceCropped, 0,0, &sourcePixmap, opX,opY, opW,opH );

    //speculative fix for a bactrace we received
    //crash was in convertToImage() somewhere in this function
    if( sourceCropped.isNull() ) {
        return setPixmap( sourcePixmap );
    }

    // blend the overlay image over the cropped rectangle
    TQImage blendedImage = sourceCropped.convertToImage();
    TQImage overlayImage = overlay->convertToImage();
    TDEIconEffect::overlay( blendedImage, overlayImage );
    sourceCropped.convertFromImage( blendedImage );

    // put back the blended rectangle to the original image
    TQPixmap sourcePixmapCopy = sourcePixmap;
    copyBlt( &sourcePixmapCopy, opX,opY, &sourceCropped, 0,0, opW,opH );

    setPixmap( sourcePixmapCopy ); // @since 3.2
}

void
Amarok::TrayIcon::setLastFm( bool lastFmActive )
{
    if( lastFmActive == m_lastFmMode ) return;

    static int separatorId = 0;

    TDEActionCollection* const ac = Amarok::actionCollection();
    if( ac->action( "ban" ) == 0 ) return; //if the LastFm::Controller doesn't exist yet

    if( lastFmActive )
    {
        ac->action( "play_pause" )->unplug( contextMenu() );
        // items are inserted in reverse order!
        ac->action( "ban" ) ->plug( contextMenu(), 4 );
        ac->action( "love" )->plug( contextMenu(), 4 );
        ac->action( "skip" )->plug( contextMenu(), 4 );
        separatorId = contextMenu()->insertSeparator( 4 );

        m_lastFmMode = true;
    }
    else
    {
        ac->action( "play_pause" )->plug( contextMenu(), 2 );
        ac->action( "ban" ) ->unplug( contextMenu() );
        ac->action( "love" )->unplug( contextMenu() );
        ac->action( "skip" )->unplug( contextMenu() );

        if( separatorId != 0 )
            contextMenu()->removeItem( separatorId ); // kill separator
        m_lastFmMode = false;
   }
}
