/****************************************************************************************
 * Copyright (c) 2007-2008 Leo Franchi <lfranchi@gmail.com>                             *
 * Copyright (c) 2008 William Viana Soares <vianasw@gmail.com>                          *
 *                                                                                      *
 * 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, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/

/*
  Significant parts of this code is inspired and/or copied from KDE plasma sources,
  available at kdebase/workspace/plasma
*/

#define DEBUG_PREFIX "ContextView"

#include "ContextView.h"

#include "core/support/Amarok.h"
#include "Context.h"
#include "ContextScene.h"
#include "core/support/Debug.h"
#include "Svg.h"
#include "Theme.h"
#include "amarokconfig.h"
#include "amarokurls/AmarokUrlHandler.h"
#include "amarokurls/ContextUrlRunner.h"

#include <plasma/dataenginemanager.h>

#include <QParallelAnimationGroup>
#include <QWheelEvent>

namespace Context
{

ContextView* ContextView::s_self = 0;


ContextView::ContextView( Plasma::Containment *cont, Plasma::Corona *corona, QWidget* parent )
    : Plasma::View( cont, parent )
    , m_curState( Home )
{
    Q_UNUSED( corona )
    DEBUG_BLOCK

    s_self = this;

    scene()->setItemIndexMethod( QGraphicsScene::BspTreeIndex );
    //TODO: Figure out a way to use rubberband and ScrollHandDrag
    //setDragMode( QGraphicsView::RubberBandDrag );
    setTransformationAnchor( QGraphicsView::NoAnchor );
    setCacheMode( QGraphicsView::CacheBackground );
    setInteractive( true );
    setAcceptDrops( true );
    setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
    // setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

    //make background transparent
    QPalette p = palette();
    QColor c = p.color( QPalette::Base );
    c.setAlpha( 0 );
    p.setColor( QPalette::Base, c );
    setPalette( p );

    PERF_LOG( "Accessing Plasma::Theme" );
    // here we initialize all the Plasma paths to Amarok paths
    Plasma::Theme::defaultTheme()->setUseGlobalSettings( false );
    Theme::defaultTheme()->setThemeName( "Amarok-Mockup" );
    PERF_LOG( "Access to Plasma::Theme complete" )
    contextScene()->setAppletMimeType( "text/x-amarokappletservicename" );

    cont->setPos( 0, 0 );
    cont->updateConstraints();
    Containment* amarokContainment = qobject_cast<Containment* >( cont );
    if( amarokContainment )
        amarokContainment->setView( this );

    m_urlRunner = new ContextUrlRunner();
    The::amarokUrlHandler()->registerRunner( m_urlRunner, "context" );

    m_collapseAnimations = new QParallelAnimationGroup( this );
    connect( m_collapseAnimations.data(), SIGNAL(finished()),
             this, SLOT(slotCollapseAnimationsFinished()) );

    m_collapseGroupTimer = new QTimer( this );
    m_collapseGroupTimer.data()->setSingleShot( true );
    connect( m_collapseGroupTimer.data(), SIGNAL(timeout()), SLOT(slotStartCollapseAnimations()) );

    EngineController* const engine = The::engineController();

    connect( engine, SIGNAL( trackChanged( Meta::TrackPtr ) ),
             this, SLOT( slotTrackChanged( Meta::TrackPtr ) ) );
    connect( engine, SIGNAL( trackMetadataChanged( Meta::TrackPtr ) ),
             this, SLOT( slotMetadataChanged( Meta::TrackPtr ) ) );
}

ContextView::~ContextView()
{
    DEBUG_BLOCK

    // Unload and destroy all Amarok plasma-engines
    const QStringList engines = Plasma::DataEngineManager::self()->listAllEngines( "Amarok" );

    // Assert added for tracing crash on exit, see BUG 187384
    Q_ASSERT_X( !engines.isEmpty(), "Listing loaded Plasma engines", "List is empty (no engines loaded!?)" );

    foreach( const QString &engine, engines )
    {
        debug() << "Unloading plasma engine: " << engine;

        // PlasmaDataEngineManager uses refcounting for the engines, so we need to unload until the refcount reaches 0
        while( Plasma::DataEngineManager::self()->engine( engine )->isValid() )
            Plasma::DataEngineManager::self()->unloadEngine( engine );
    }

    clear( m_curState );
    //this should be done to prevent a crash on exit
    clearFocus();

    delete m_urlRunner;
}


void
ContextView::clear( const ContextState& state )
{
    Q_UNUSED( state )
    DEBUG_BLOCK

    const QString name = "amarok_homerc";
    // now we save the state, remembering the column info etc
    KConfig appletConfig( name );
    // erase previous config
    foreach( const QString& group, appletConfig.groupList() )
        appletConfig.deleteGroup( group );

    const int numContainments = contextScene()->containments().size();
    for( int i = 0; i < numContainments; i++ )
    {
        DEBUG_LINE_INFO
        Containment* containment = qobject_cast< Containment* >( contextScene()->containments()[i] );
        KConfigGroup cg( &appletConfig, QString( "Containment %1" ).arg( i ) );
        if( containment )
            containment->saveToConfig( cg );
    }
    contextScene()->clearContainments();
}

void ContextView::clearNoSave()
{
    contextScene()->clearContainments();
}


void ContextView::slotTrackChanged( Meta::TrackPtr track )
{
    DEBUG_BLOCK;

    if( track )
        messageNotify( Current );
    else
        messageNotify( Home );
}


void
ContextView::slotMetadataChanged( Meta::TrackPtr track )
{
    DEBUG_BLOCK

    // if we are listening to a stream, take the new metadata as a "new track"
    if( track && The::engineController()->isStream() )
        messageNotify( Current );
}

void ContextView::showHome()
{
    DEBUG_BLOCK

    m_curState = Home;
    loadConfig();
    messageNotify( m_curState );
}


// loads applets onto the ContextScene from saved data, using m_curState
void
ContextView::loadConfig()
{
    contextScene()->clearContainments();

    PERF_LOG( "Start to load config" );
    int numContainments = contextScene()->containments().size();
    KConfig conf( "amarok_homerc", KConfig::FullConfig );
    for( int i = 0; i < numContainments; i++ )
    {
        Containment* containment = qobject_cast< Containment* >( contextScene()->containments()[i] );
        if( containment )
        {
            KConfigGroup cg( &conf, QString( "Containment %1" ).arg( i ) );
            containment->loadConfig( cg );
        }
    }
    PERF_LOG( "Done loading config" );
}

Plasma::Applet*
ContextView::addApplet( const QString& name, const QStringList& args )
{
    QVariantList argList;
    QStringListIterator i(args);
    while( i.hasNext() )
        argList << QVariant( i.next() );

    if( !containment() )
        contextScene()->addContainment( "amarok_containment_vertical" );

    return containment()->addApplet( name, argList );
}

void
ContextView::addCollapseAnimation( QAbstractAnimation *anim )
{
    if( !anim )
    {
        debug() << "failed to add collapsing animation";
        return;
    }

    QParallelAnimationGroup *group = m_collapseAnimations.data();
    if( group && group->state() == QAbstractAnimation::Running )
    {
        m_queuedAnimations.enqueue( anim );
        return;
    }

    QTimer *timer( 0 );
    if( m_collapseGroupTimer )
        timer = m_collapseGroupTimer.data();

    if( group && timer && !timer->isActive() )
    {
        group->addAnimation( anim );
        timer->start( 0 );
    }
    else
    {
        m_queuedAnimations.enqueue( anim );
    }
}

void
ContextView::slotCollapseAnimationsFinished()
{
    QTimer *timer = m_collapseGroupTimer.data();
    if( timer )
        timer->stop();

    QParallelAnimationGroup *group = m_collapseAnimations.data();
    if( group )
        group->clear();

    while( !m_queuedAnimations.isEmpty() )
    {
        if( QWeakPointer<QAbstractAnimation> anim = m_queuedAnimations.dequeue() )
        {
            if( group )
                group->addAnimation( anim.data() );
        }
    }

    if( group && (group->animationCount() > 0) )
        timer->start( 0 );
}

void
ContextView::slotStartCollapseAnimations()
{
    QParallelAnimationGroup *group = m_collapseAnimations.data();
    if( group && (group->animationCount() > 0) )
        group->start( QAbstractAnimation::KeepWhenStopped );
}

void
ContextView::hideAppletExplorer()
{
    if( m_appletExplorer )
        m_appletExplorer.data()->hide();
}

void
ContextView::showAppletExplorer()
{
    if( !m_appletExplorer )
    {
        Context::Containment *cont = qobject_cast<Context::Containment*>( containment() );
        m_appletExplorer = new AppletExplorer( cont );
        m_appletExplorer.data()->setContainment( cont );
        m_appletExplorer.data()->setZValue( m_appletExplorer.data()->zValue() + 1000 );
        m_appletExplorer.data()->setFlag( QGraphicsItem::ItemIsSelectable );

        connect( m_appletExplorer.data(), SIGNAL(addAppletToContainment(QString, const int)),
                 cont, SLOT(addApplet(QString, const int)) );
        connect( m_appletExplorer.data(), SIGNAL(appletExplorerHid()), SIGNAL(appletExplorerHid()) );
        connect( m_appletExplorer.data(), SIGNAL(geometryChanged()), SLOT(slotPositionAppletExplorer()) );

        qreal height = m_appletExplorer.data()->effectiveSizeHint( Qt::PreferredSize ).height();
        m_appletExplorer.data()->resize( rect().width() - 2, height );
        m_appletExplorer.data()->setPos( 0, rect().height() - height - 2 );
    }
    m_appletExplorer.data()->show();
}

void
ContextView::slotPositionAppletExplorer()
{
    if( !m_appletExplorer )
        return;
    qreal height = m_appletExplorer.data()->effectiveSizeHint( Qt::PreferredSize ).height();
    m_appletExplorer.data()->setPos( 0, rect().height() - height - 2 );
}


ContextScene*
ContextView::contextScene()
{
    return static_cast<ContextScene*>( scene() );
}

void
ContextView::resizeEvent( QResizeEvent* event )
{
    Plasma::View::resizeEvent( event );
    if( testAttribute( Qt::WA_PendingResizeEvent ) )
        return; // lets not do this more than necessary, shall we?

    QRectF rect( QRectF(pos(), maximumViewportSize()) );
    containment()->setGeometry( rect );
    scene()->setSceneRect( rect );
    scene()->update( rect );

    if( m_appletExplorer )
    {
        qreal height = m_appletExplorer.data()->effectiveSizeHint( Qt::PreferredSize ).height();
        m_appletExplorer.data()->resize( rect.width() - 2, height );
        m_appletExplorer.data()->setPos( 0, rect.height() - height - 2 );
    }
}

void
ContextView::wheelEvent( QWheelEvent* event )
{
    if( event->orientation() != Qt::Horizontal )
        Plasma::View::wheelEvent( event );
}

QStringList
ContextView::currentApplets()
{
    DEBUG_BLOCK
    QStringList appletNames;
    
    Applet::List applets = containment()->applets();
    foreach( Plasma::Applet * applet, applets )
    {
        appletNames << applet->pluginName();
    }

    debug() << "current applets: " << appletNames;

    return appletNames;
}

QStringList ContextView::currentAppletNames()
{
    DEBUG_BLOCK
    QStringList appletNames;

    Applet::List applets = containment()->applets();
    foreach( Plasma::Applet * applet, applets )
    {
        appletNames << applet->name();
    }

    debug() << "current applets: " << appletNames;

    return appletNames; 
}

} // Context namespace

#include "ContextView.moc"

