/***************************************************************************
 *   Copyright (C) 2004-2006 by Mark Kretschmann <markey@web.de>           *
 *                      2005 by Seb Ruiz <me@sebruiz.net>                  *
 *                      2006 by Alexandre Oliveira <aleprj@gmail.com>      *
 *                      2006 by Martin Ellis <martin.ellis@kdemail.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.                                   *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02110-1301, USA.          *
 ***************************************************************************/

#define DEBUG_PREFIX "ScriptManager"

#include "amarok.h"
#include "amarokconfig.h"
#include "contextbrowser.h"
#include "debug.h"
#include "enginecontroller.h"
#include "metabundle.h"
#include "scriptmanager.h"
#include "scriptmanagerbase.h"
#include "statusbar.h"

#include <sys/stat.h>
#include <sys/types.h>

#include <tqcheckbox.h>
#include <tqdir.h>
#include <tqfileinfo.h>
#include <tqfont.h>
#include <tqlabel.h>
#include <tqtextcodec.h>
#include <tqtimer.h>

#include <tdeaboutdialog.h>
#include <tdeapplication.h>
#include <tdefiledialog.h>
#include <kiconloader.h>
#include <tdeio/netaccess.h>
#include <tdelistview.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdepopupmenu.h>
#include <kprocio.h>
#include <tdeprotocolmanager.h>
#include <kpushbutton.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <ktar.h>
#include <ktextedit.h>
#include <twin.h>

#include <tdenewstuff/downloaddialog.h> // tdenewstuff script fetching
#include <tdenewstuff/engine.h>         // "
#include <tdenewstuff/knewstuff.h>      // "
#include <tdenewstuff/provider.h>       // "


namespace Amarok {
    void closeOpenFiles(int out, int in, int err) {
        for(int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--)
            if(i!=out && i!=in && i!=err)
                close(i);
    }

     /**
    * This constructor is needed so that the correct codec is used. KProcIO defaults
    * to latin1, while the scanner uses UTF-8.
    */
    ProcIO::ProcIO() : KProcIO( TQTextCodec::codecForName( "UTF-8" ) ) {}

    TQString
    proxyForUrl(const TQString& url)
    {
        KURL kurl( url );

        TQString proxy;

        if ( KProtocolManager::proxyForURL( kurl ) != 
                TQString::fromLatin1( "DIRECT" ) ) {
            KProtocolManager::slaveProtocol ( kurl, proxy );
        }

        return proxy;
    }

    TQString
    proxyForProtocol(const TQString& protocol)
    {
        return KProtocolManager::proxyFor( protocol );
    }


}

////////////////////////////////////////////////////////////////////////////////
// class AmarokScriptNewStuff
////////////////////////////////////////////////////////////////////////////////

/**
 * GHNS Customised Download implementation.
 */
class AmarokScriptNewStuff : public TDENewStuff
{
    public:
    AmarokScriptNewStuff(const TQString &type, TQWidget *parentWidget=0)
             : TDENewStuff( type, parentWidget )
    {}

    bool install( const TQString& fileName )
    {
        return ScriptManager::instance()->slotInstallScript( fileName );
    }

    virtual bool createUploadFile( const TQString& ) { return false; } //make compile on kde 3.5
};


////////////////////////////////////////////////////////////////////////////////
// class ScriptManager
////////////////////////////////////////////////////////////////////////////////

ScriptManager* ScriptManager::s_instance = 0;


ScriptManager::ScriptManager( TQWidget *parent, const char *name )
        : KDialogBase( parent, name, false, TQString(), Close, Close, true )
        , EngineObserver( EngineController::instance() )
        , m_gui( new ScriptManagerBase( this ) )
{
    DEBUG_BLOCK

    s_instance = this;

    kapp->setTopWidget( this );
    setCaption( kapp->makeStdCaption( i18n( "Script Manager" ) ) );

    // Gives the window a small title bar, and skips a taskbar entry
    KWin::setType( winId(), NET::Utility );
    KWin::setState( winId(), NET::SkipTaskbar );

    setMainWidget( m_gui );
    m_gui->listView->setRootIsDecorated( true );
    m_gui->listView->setFullWidth( true );
    m_gui->listView->setShowSortIndicator( true );


    /// Category items
    m_generalCategory    = new TDEListViewItem( m_gui->listView, i18n( "General" ) );
    m_lyricsCategory     = new TDEListViewItem( m_gui->listView, i18n( "Lyrics" ) );
    m_scoreCategory      = new TDEListViewItem( m_gui->listView, i18n( "Score" ) );
    m_transcodeCategory  = new TDEListViewItem( m_gui->listView, i18n( "Transcoding" ) );

    m_generalCategory  ->setSelectable( false );
    m_lyricsCategory   ->setSelectable( false );
    m_scoreCategory    ->setSelectable( false );
    m_transcodeCategory->setSelectable( false );

    m_generalCategory  ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) );
    m_lyricsCategory   ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) );
    m_scoreCategory    ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) );
    m_transcodeCategory->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) );

    // Restore the open/closed state of the category items
    TDEConfig* const config = Amarok::config( "ScriptManager" );
    m_generalCategory  ->setOpen( config->readBoolEntry( "General category open" ) );
    m_lyricsCategory   ->setOpen( config->readBoolEntry( "Lyrics category open" ) );
    m_scoreCategory    ->setOpen( config->readBoolEntry( "Score category State" ) );
    m_transcodeCategory->setOpen( config->readBoolEntry( "Transcode category open" ) );

    connect( m_gui->listView, TQ_SIGNAL( currentChanged( TQListViewItem* ) ), TQ_SLOT( slotCurrentChanged( TQListViewItem* ) ) );
    connect( m_gui->listView, TQ_SIGNAL( doubleClicked ( TQListViewItem*, const TQPoint&, int ) ), TQ_SLOT( slotRunScript() ) );
    connect( m_gui->listView, TQ_SIGNAL( contextMenuRequested ( TQListViewItem*, const TQPoint&, int ) ), TQ_SLOT( slotShowContextMenu( TQListViewItem*, const TQPoint& ) ) );

    connect( m_gui->installButton,   TQ_SIGNAL( clicked() ), TQ_SLOT( slotInstallScript() ) );
    connect( m_gui->retrieveButton,  TQ_SIGNAL( clicked() ), TQ_SLOT( slotRetrieveScript() ) );
    connect( m_gui->uninstallButton, TQ_SIGNAL( clicked() ), TQ_SLOT( slotUninstallScript() ) );
    connect( m_gui->runButton,       TQ_SIGNAL( clicked() ), TQ_SLOT( slotRunScript() ) );
    connect( m_gui->stopButton,      TQ_SIGNAL( clicked() ), TQ_SLOT( slotStopScript() ) );
    connect( m_gui->configureButton, TQ_SIGNAL( clicked() ), TQ_SLOT( slotConfigureScript() ) );
    connect( m_gui->aboutButton,     TQ_SIGNAL( clicked() ), TQ_SLOT( slotAboutScript() ) );

    m_gui->installButton  ->setIconSet( SmallIconSet( Amarok::icon( "files" ) ) );
    m_gui->retrieveButton ->setIconSet( SmallIconSet( Amarok::icon( "download" ) ) );
    m_gui->uninstallButton->setIconSet( SmallIconSet( Amarok::icon( "remove" ) ) );
    m_gui->runButton      ->setIconSet( SmallIconSet( Amarok::icon( "play" ) ) );
    m_gui->stopButton     ->setIconSet( SmallIconSet( Amarok::icon( "stop" ) ) );
    m_gui->configureButton->setIconSet( SmallIconSet( Amarok::icon( "configure" ) ) );
    m_gui->aboutButton    ->setIconSet( SmallIconSet( Amarok::icon( "info" ) ) );

    TQSize sz = sizeHint();
    setMinimumSize( kMax( 350, sz.width() ), kMax( 250, sz.height() ) );
    resize( sizeHint() );

    connect( this, TQ_SIGNAL(lyricsScriptChanged()), ContextBrowser::instance(), TQ_SLOT( lyricsScriptChanged() ) );

    // Delay this call via eventloop, because it's a bit slow and would block
    TQTimer::singleShot( 0, this, TQ_SLOT( findScripts() ) );
}


ScriptManager::~ScriptManager()
{
    DEBUG_BLOCK

    TQStringList runningScripts;
    ScriptMap::Iterator it;
    ScriptMap::Iterator end( m_scripts.end() );
    for( it = m_scripts.begin(); it != end; ++it ) {
        if( it.data().process ) {
            terminateProcess( &it.data().process );
            runningScripts << it.key();
        }
    }

    // Save config
    TDEConfig* const config = Amarok::config( "ScriptManager" );
    config->writeEntry( "Running Scripts", runningScripts );

    // Save the open/closed state of the category items
    config->writeEntry( "General category open", m_generalCategory->isOpen() );
    config->writeEntry( "Lyrics category open", m_lyricsCategory->isOpen() );
    config->writeEntry( "Score category open", m_scoreCategory->isOpen() );
    config->writeEntry( "Transcode category open", m_transcodeCategory->isOpen() );

    s_instance = 0;
}


////////////////////////////////////////////////////////////////////////////////
// public
////////////////////////////////////////////////////////////////////////////////

bool
ScriptManager::runScript( const TQString& name, bool silent )
{
    if( !m_scripts.contains( name ) )
        return false;

    m_gui->listView->setCurrentItem( m_scripts[name].li );
    return slotRunScript( silent );
}


bool
ScriptManager::stopScript( const TQString& name )
{
    if( !m_scripts.contains( name ) )
        return false;

    m_gui->listView->setCurrentItem( m_scripts[name].li );
    slotStopScript();

    return true;
}


TQStringList
ScriptManager::listRunningScripts()
{
    TQStringList runningScripts;
    foreachType( ScriptMap, m_scripts )
        if( it.data().process )
            runningScripts << it.key();

    return runningScripts;
}


void
ScriptManager::customMenuClicked( const TQString& message )
{
    notifyScripts( "customMenuClicked: " + message );
}


TQString
ScriptManager::specForScript( const TQString& name )
{
    if( !m_scripts.contains( name ) )
        return TQString();
    TQFileInfo info( m_scripts[name].url.path() );
    const TQString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec";

    return specPath;
}


void
ScriptManager::notifyFetchLyrics( const TQString& artist, const TQString& title )
{
    const TQString args = KURL::encode_string( artist ) + ' ' + KURL::encode_string( title );
    notifyScripts( "fetchLyrics " + args );
}


void
ScriptManager::notifyFetchLyricsByUrl( const TQString& url )
{
    notifyScripts( "fetchLyricsByUrl " + url );
}


void ScriptManager::notifyTranscode( const TQString& srcUrl, const TQString& filetype )
{
    notifyScripts( "transcode " + srcUrl + ' ' + filetype );
}


void
ScriptManager::notifyPlaylistChange( const TQString& change)
{
   notifyScripts( "playlistChange: " + change );
}


void
ScriptManager::requestNewScore( const TQString &url, double prevscore, int playcount, int length, float percentage, const TQString &reason )
{
    const TQString script = ensureScoreScriptRunning();
    if( script.isNull() )
    {
        Amarok::StatusBar::instance()->longMessage(
            i18n( "No score scripts were found, or none of them worked. Automatic scoring will be disabled. Sorry." ),
            KDE::StatusBar::Sorry );
        return;
    }

    m_scripts[script].process->writeStdin(
        TQString( "requestNewScore %6 %1 %2 %3 %4 %5" )
        .arg( prevscore )
        .arg( playcount )
        .arg( length )
        .arg( percentage )
        .arg( reason )
        .arg( KURL::encode_string( url ) ) ); //last because it might have %s
}

////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

void
ScriptManager::findScripts() //SLOT
{
    const TQStringList allFiles = kapp->dirs()->findAllResources( "data", "amarok/scripts/*", true );

    // Add found scripts to listview:
    {
        foreach( allFiles )
            if( TQFileInfo( *it ).isExecutable() )
                loadScript( *it );
    }

    // Handle auto-run:

    TDEConfig* const config = Amarok::config( "ScriptManager" );
    const TQStringList runningScripts = config->readListEntry( "Running Scripts" );

    {
        foreach( runningScripts )
            if( m_scripts.contains( *it ) ) {
                debug() << "Auto-running script: " << *it << endl;
                m_gui->listView->setCurrentItem( m_scripts[*it].li );
                slotRunScript();
            }
    }

    m_gui->listView->setCurrentItem( m_gui->listView->firstChild() );
    slotCurrentChanged( m_gui->listView->currentItem() );
}


void
ScriptManager::slotCurrentChanged( TQListViewItem* item )
{
    const bool isCategory = item == m_generalCategory ||
                            item == m_lyricsCategory ||
                            item == m_scoreCategory ||
                            item == m_transcodeCategory;

    if( item && !isCategory ) {
        const TQString name = item->text( 0 );
        m_gui->uninstallButton->setEnabled( true );
        m_gui->runButton->setEnabled( !m_scripts[name].process );
        m_gui->stopButton->setEnabled( m_scripts[name].process );
        m_gui->configureButton->setEnabled( m_scripts[name].process );
        m_gui->aboutButton->setEnabled( true );
    }
    else {
        m_gui->uninstallButton->setEnabled( false );
        m_gui->runButton->setEnabled( false );
        m_gui->stopButton->setEnabled( false );
        m_gui->configureButton->setEnabled( false );
        m_gui->aboutButton->setEnabled( false );
    }
}


bool
ScriptManager::slotInstallScript( const TQString& path )
{
    TQString _path = path;

    if( path.isNull() ) {
        _path = KFileDialog::getOpenFileName( TQString(),
            "*.amarokscript.tar *.amarokscript.tar.bz2 *.amarokscript.tar.gz|"
            + i18n( "Script Packages (*.amarokscript.tar, *.amarokscript.tar.bz2, *.amarokscript.tar.gz)" )
            , this
            , i18n( "Select Script Package" ) );
        if( _path.isNull() ) return false;
    }

    KTar archive( _path );
    if( !archive.open( IO_ReadOnly ) ) {
        KMessageBox::sorry( 0, i18n( "Could not read this package." ) );
        return false;
    }

    TQString destination = Amarok::saveLocation( "scripts/" );
    const KArchiveDirectory* const archiveDir = archive.directory();

    // Prevent installing a script that's already installed
    const TQString scriptFolder = destination + archiveDir->entries().first();
    if( TQFile::exists( scriptFolder ) ) {
        KMessageBox::error( 0, i18n( "A script with the name '%1' is already installed. "
                                     "Please uninstall it first." ).arg( archiveDir->entries().first() ) );
        return false;
    }

    archiveDir->copyTo( destination );
    m_installSuccess = false;
    recurseInstall( archiveDir, destination );

    if( m_installSuccess ) {
        KMessageBox::information( 0, i18n( "Script successfully installed." ) );
        return true;
    }
    else {
        KMessageBox::sorry( 0, i18n( "<p>Script installation failed.</p>"
                                     "<p>The package did not contain an executable file. "
                                     "Please inform the package maintainer about this error.</p>" ) );

        // Delete directory recursively
        TDEIO::NetAccess::del( KURL::fromPathOrURL( scriptFolder ), 0 );
    }

    return false;
}


void
ScriptManager::recurseInstall( const KArchiveDirectory* archiveDir, const TQString& destination )
{
    const TQStringList entries = archiveDir->entries();

    foreach( entries ) {
        const TQString entry = *it;
        const KArchiveEntry* const archEntry = archiveDir->entry( entry );

        if( archEntry->isDirectory() ) {
            const KArchiveDirectory* const dir = static_cast<const KArchiveDirectory*>( archEntry );
            recurseInstall( dir, destination + entry + '/' );
        }
        else {
            ::chmod( TQFile::encodeName( destination + entry ), archEntry->permissions() );

            if( TQFileInfo( destination + entry ).isExecutable() ) {
                loadScript( destination + entry );
                m_installSuccess = true;
            }
        }
    }
}


void
ScriptManager::slotRetrieveScript()
{
    // Delete TDENewStuff's configuration entries. These entries reflect which scripts
    // are already installed. As we cannot yet keep them in sync after uninstalling
    // scripts, we deactivate the check marks entirely.
    Amarok::config()->deleteGroup( "TDENewStuffStatus" );

    // we need this because TDENewStuffGeneric's install function isn't clever enough
    AmarokScriptNewStuff *kns = new AmarokScriptNewStuff( "amarok/script", this );
    KNS::Engine *engine = new KNS::Engine( kns, "amarok/script", this );
    KNS::DownloadDialog *d = new KNS::DownloadDialog( engine, this );
    d->setType( "amarok/script" );
    // you have to do this by hand when providing your own Engine
    KNS::ProviderLoader *p = new KNS::ProviderLoader( this );
    TQObject::connect( p, TQ_SIGNAL( providersLoaded(Provider::List*) ), d, TQ_SLOT( slotProviders (Provider::List *) ) );
    p->load( "amarok script", "https://www.trinitydesktop.org/ocs/providers.xml" );

    d->exec();
}


void
ScriptManager::slotUninstallScript()
{
    const TQString name = m_gui->listView->currentItem()->text( 0 );

    if( KMessageBox::warningContinueCancel( 0, i18n( "Are you sure you want to uninstall the script '%1'?" ).arg( name ), i18n("Uninstall Script"), i18n("Uninstall") ) == KMessageBox::Cancel )
        return;

    if( m_scripts.find( name ) == m_scripts.end() )
        return;

    KURL scriptDirURL( m_scripts[name].url.upURL() );

    // find if the script is installed in the global or local scripts directory
    KURL scriptsDirURL;
    TQStringList dirs = TDEGlobal::dirs()->findDirs( "data", "amarok/scripts/" );
    for ( TQStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it ) {
        scriptsDirURL = KURL::fromPathOrURL( *it );
        if ( scriptsDirURL.isParentOf( scriptDirURL ) )
            break;
    }

    // find the begining of this script directory tree
    KURL scriptDirUpURL = scriptDirURL.upURL();
    while ( ! scriptsDirURL.equals( scriptDirUpURL, true ) && scriptsDirURL.isParentOf( scriptDirUpURL ) ) {
        scriptDirURL = scriptDirUpURL;
        scriptDirUpURL = scriptDirURL.upURL();
    }

    // Delete script directory recursively
    if( !TDEIO::NetAccess::del( scriptDirURL, 0 ) ) {
        KMessageBox::sorry( 0, i18n( "<p>Could not uninstall this script.</p><p>The ScriptManager can only uninstall scripts which have been installed as packages.</p>" ) ); // only true when not running as root (which is reasonable)
        return;
    }

    TQStringList keys;

    // Find all scripts that were in the uninstalled directory
    {
        foreachType( ScriptMap, m_scripts )
            if( scriptDirURL.isParentOf( it.data().url ) )
                keys << it.key();
    }

    // Terminate script processes, remove entries from script list
    {
        foreach( keys ) {
            delete m_scripts[*it].li;
            terminateProcess( &m_scripts[*it].process );
            m_scripts.erase( *it );
        }
    }
}


bool
ScriptManager::slotRunScript( bool silent )
{
    if( !m_gui->runButton->isEnabled() ) return false;

    TQListViewItem* const li = m_gui->listView->currentItem();
    const TQString name = li->text( 0 );

    if( m_scripts[name].type == "lyrics" && !lyricsScriptRunning().isEmpty() ) {
        if( !silent )
            KMessageBox::sorry( 0, i18n( "Another lyrics script is already running. "
                                         "You may only run one lyrics script at a time." ) );
        return false;
    }

    if( m_scripts[name].type == "transcode" && !transcodeScriptRunning().isEmpty() ) {
        if( !silent )
            KMessageBox::sorry( 0, i18n( "Another transcode script is already running. "
                                         "You may only run one transcode script at a time." ) );
        return false;
    }

    // Don't start a script twice
    if( m_scripts[name].process ) return false;

    Amarok::ProcIO* script = new Amarok::ProcIO();
    script->setComm( static_cast<TDEProcess::Communication>( TDEProcess::All ) );
    const KURL url = m_scripts[name].url;
    *script << url.path();
    script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) );

    connect( script, TQ_SIGNAL( receivedStderr( TDEProcess*, char*, int ) ), TQ_SLOT( slotReceivedStderr( TDEProcess*, char*, int ) ) );
    connect( script, TQ_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ), TQ_SLOT( slotReceivedStdout( TDEProcess*, char*, int ) ) );
    connect( script, TQ_SIGNAL( processExited( TDEProcess* ) ), TQ_SLOT( scriptFinished( TDEProcess* ) ) );

    if( script->start( TDEProcess::NotifyOnExit ) )
    {
        if( m_scripts[name].type == "score" && !scoreScriptRunning().isNull() )
        {
            stopScript( scoreScriptRunning() );
            m_gui->listView->setCurrentItem( li );
        }
        AmarokConfig::setLastScoreScript( name );
    }
    else
    {
        if( !silent )
            KMessageBox::sorry( 0, i18n( "<p>Could not start the script <i>%1</i>.</p>"
                                         "<p>Please make sure that the file has execute (+x) permissions.</p>" ).arg( name ) );
        delete script;
        return false;
    }

    li->setPixmap( 0, SmallIcon( Amarok::icon( "play" ) ) );
    debug() << "Running script: " << url.path() << endl;

    m_scripts[name].process = script;
    slotCurrentChanged( m_gui->listView->currentItem() );
    if( m_scripts[name].type == "lyrics" )
        emit lyricsScriptChanged();

    return true;
}


void
ScriptManager::slotStopScript()
{
    TQListViewItem* const li = m_gui->listView->currentItem();
    const TQString name = li->text( 0 );

    // Just a sanity check
    if( m_scripts.find( name ) == m_scripts.end() )
        return;

    terminateProcess( &m_scripts[name].process );
    m_scripts[name].log = TQString();
    slotCurrentChanged( m_gui->listView->currentItem() );

    li->setPixmap( 0, TQPixmap() );
}


void
ScriptManager::slotConfigureScript()
{
    const TQString name = m_gui->listView->currentItem()->text( 0 );
    if( !m_scripts[name].process ) return;

    const KURL url = m_scripts[name].url;
    TQDir::setCurrent( url.directory() );

    m_scripts[name].process->writeStdin( TQString("configure") );
}


void
ScriptManager::slotAboutScript()
{
    const TQString name = m_gui->listView->currentItem()->text( 0 );
    TQFile readme( m_scripts[name].url.directory( false ) + "README" );
    TQFile license( m_scripts[name].url.directory( false ) + "COPYING" );

    if( !readme.open( IO_ReadOnly ) ) {
        KMessageBox::sorry( 0, i18n( "There is no information available for this script." ) );
        return;
    }

    TDEAboutDialog* about = new TDEAboutDialog( TDEAboutDialog::AbtTabbed|TDEAboutDialog::AbtProduct,
                                            TQString(),
                                            KDialogBase::Ok, KDialogBase::Ok, this );
    kapp->setTopWidget( about );
    about->setCaption( kapp->makeStdCaption( i18n( "About %1" ).arg( name ) ) );
    about->setProduct( "", "", "", "" );
    // Get rid of the confusing KDE version text
    TQLabel* product = static_cast<TQLabel*>( about->mainWidget()->child( "version" ) );
    if( product ) product->setText( i18n( "%1 Amarok Script" ).arg( name ) );

    about->addTextPage( i18n( "About" ), readme.readAll(), true );
    if( license.open( IO_ReadOnly ) )
        about->addLicensePage( i18n( "License" ), license.readAll() );

    about->setInitialSize( TQSize( 500, 350 ) );
    about->show();
}


void
ScriptManager::slotShowContextMenu( TQListViewItem* item, const TQPoint& pos )
{
    const bool isCategory = item == m_generalCategory ||
                            item == m_lyricsCategory ||
                            item == m_scoreCategory ||
                            item == m_transcodeCategory;

    if( !item || isCategory ) return;

    // Look up script entry in our map
    ScriptMap::Iterator it;
    ScriptMap::Iterator end( m_scripts.end() );
    for( it = m_scripts.begin(); it != end; ++it )
        if( it.data().li == item ) break;

    enum { SHOW_LOG, EDIT };
    TDEPopupMenu menu;
    menu.insertTitle( i18n( "Debugging" ) );
    menu.insertItem( SmallIconSet( Amarok::icon( "clock" ) ), i18n( "Show Output &Log" ), SHOW_LOG );
    menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "&Edit" ), EDIT );
    menu.setItemEnabled( SHOW_LOG, it.data().process );
    const int id = menu.exec( pos );

    switch( id )
    {
        case EDIT:
            KRun::runCommand( "kwrite " + TDEProcess::quote(it.data().url.path()) );
            break;

        case SHOW_LOG:
            TQString line;
            while( it.data().process->readln( line ) != -1 )
                it.data().log += line;

            KTextEdit* editor = new KTextEdit( it.data().log );
            kapp->setTopWidget( editor );
            editor->setCaption( kapp->makeStdCaption( i18n( "Output Log for %1" ).arg( it.key() ) ) );
            editor->setReadOnly( true );

            TQFont font( "fixed" );
            font.setFixedPitch( true );
            font.setStyleHint( TQFont::TypeWriter );
            editor->setFont( font );

            editor->setTextFormat( TQTextEdit::PlainText );
            editor->resize( 500, 380 );
            editor->show();
            break;
    }
}


/* This is just a workaround, some scripts crash for some people if stdout is not handled. */
void
ScriptManager::slotReceivedStdout( TDEProcess*, char* buf, int len )
{
    debug() << TQString::fromLatin1( buf, len ) << endl;
}


void
ScriptManager::slotReceivedStderr( TDEProcess* process, char* buf, int len )
{
    // Look up script entry in our map
    ScriptMap::Iterator it;
    ScriptMap::Iterator end( m_scripts.end() );
    for( it = m_scripts.begin(); it != end; ++it )
        if( it.data().process == process ) break;

    const TQString text = TQString::fromLatin1( buf, len );
    error() << it.key() << ":\n" << text << endl;

    if( it.data().log.length() > 20000 )
        it.data().log = "==== LOG TRUNCATED HERE ====\n";
    it.data().log += text;
}


void
ScriptManager::scriptFinished( TDEProcess* process ) //SLOT
{
    // Look up script entry in our map
    ScriptMap::Iterator it;
    ScriptMap::Iterator end( m_scripts.end() );
    for( it = m_scripts.begin(); it != end; ++it )
        if( it.data().process == process ) break;

    // Check if there was an error on exit
    if( process->normalExit() && process->exitStatus() != 0 )
        KMessageBox::detailedError( 0, i18n( "The script '%1' exited with error code: %2" )
                                           .arg( it.key() ).arg( process->exitStatus() )
                                           ,it.data().log );

    // Destroy script process
    delete it.data().process;
    it.data().process = 0;
    it.data().log = TQString();
    it.data().li->setPixmap( 0, TQPixmap() );
    slotCurrentChanged( m_gui->listView->currentItem() );
}


////////////////////////////////////////////////////////////////////////////////
// private
////////////////////////////////////////////////////////////////////////////////

TQStringList
ScriptManager::scriptsOfType( const TQString &type ) const
{
    TQStringList scripts;
    foreachType( ScriptMap, m_scripts )
        if( it.data().type == type )
            scripts += it.key();

    return scripts;
}


TQString
ScriptManager::scriptRunningOfType( const TQString &type ) const
{
    foreachType( ScriptMap, m_scripts )
        if( it.data().process )
            if( it.data().type == type )
                return it.key();

    return TQString();
}


TQString
ScriptManager::ensureScoreScriptRunning()
{
    TQString s = scoreScriptRunning();
    if( !s.isNull() )
        return s;

    if( runScript( AmarokConfig::lastScoreScript(), true /*silent*/ ) )
        return AmarokConfig::lastScoreScript();

    const TQString def = i18n( "Score" ) + ": " + "Default";
    if( runScript( def, true ) )
        return def;

    const TQStringList scripts = scoreScripts();
    for( TQStringList::const_iterator it = scripts.begin(), end = scripts.end(); it != end; ++it )
        if( runScript( *it, true ) )
            return *it;

    return TQString();
}


void
ScriptManager::terminateProcess( KProcIO** proc )
{
    if( *proc ) {
        (*proc)->kill(); // Sends SIGTERM
        (*proc)->detach();

        delete *proc;
        *proc = 0;
    }
}


void
ScriptManager::notifyScripts( const TQString& message )
{
    foreachType( ScriptMap, m_scripts ) {
        KProcIO* const proc = it.data().process;
        if( proc ) proc->writeStdin( message );
    }
}


void
ScriptManager::loadScript( const TQString& path )
{
    if( !path.isEmpty() ) {
        const KURL url = KURL::fromPathOrURL( path );
        TQString name = url.fileName();
        TQString type = "generic";

        // Read and parse .spec file, if exists
        TQFileInfo info( path );
        TDEListViewItem* li = 0;
        const TQString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec";
        if( TQFile::exists( specPath ) ) {
            TDEConfig spec( specPath, true, false );
            if( spec.hasKey( "name" ) )
                name = spec.readEntry( "name" );
            if( spec.hasKey( "type" ) ) {
                type = spec.readEntry( "type" );
                if( type == "lyrics" )
                    li = new TDEListViewItem( m_lyricsCategory, name );
                if( type == "transcode" )
                    li = new TDEListViewItem( m_transcodeCategory, name );
                if( type == "score" )
                    li = new TDEListViewItem( m_scoreCategory, name );
            }
        }

        if( !li )
            li = new TDEListViewItem( m_generalCategory, name );

        li->setPixmap( 0, TQPixmap() );

        ScriptItem item;
        item.url = url;
        item.type = type;
        item.process = 0;
        item.li = li;

        m_scripts[name] = item;
        debug() << "Loaded: " << name << endl;

        slotCurrentChanged( m_gui->listView->currentItem() );
    }
}


void
ScriptManager::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
{
    switch( state )
    {
        case Engine::Empty:
            notifyScripts( "engineStateChange: empty" );
            break;

        case Engine::Idle:
            notifyScripts( "engineStateChange: idle" );
            break;

        case Engine::Paused:
            notifyScripts( "engineStateChange: paused" );
            break;

        case Engine::Playing:
            notifyScripts( "engineStateChange: playing" );
            break;
    }
}


void
ScriptManager::engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ )
{
    notifyScripts( "trackChange" );
}


void
ScriptManager::engineVolumeChanged( int newVolume )
{
    notifyScripts( "volumeChange: " + TQString::number( newVolume ) );
}

#include "scriptmanager.moc"
