/***************************************************************************
 * copyright            : (C) 2006 Chris Muehlhaeuser <chris@chris.de>     *
 *                      : (C) 2006 Seb Ruiz <me@sebruiz.net>               *
 *                      : (C) 2006 Ian Monroe <ian@monroe.nu>              *
 *                      : (C) 2006 Mark Kretschmann <markey@web.de>        *
 **************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#define DEBUG_PREFIX "LastFm"

#include "amarok.h"         //APP_VERSION, actioncollection
#include "amarokconfig.h"   //last.fm username and passwd
#include "collectiondb.h"
#include "debug.h"
#include "enginecontroller.h"
#include "lastfm.h"
#include "statusbar.h"      //showError()

#include <tqdeepcopy.h>
#include <tqdom.h>
#include <tqhttp.h>
#include <tqlabel.h>
#include <tqregexp.h>

#include <tdeaction.h>
#include <klineedit.h>
#include <kmdcodec.h>       //md5sum
#include <tdemessagebox.h>
#include <tdeio/job.h>
#include <tdeio/jobclasses.h>
#include <tdeprotocolmanager.h>
#include <tdeshortcut.h>
#include <kurl.h>

#include <time.h>
#include <unistd.h>

using namespace LastFm;

///////////////////////////////////////////////////////////////////////////////
// CLASS AmarokHttp
// AmarokHttp is a hack written so that lastfm code could easily use something proxy aware.
// DO NOT use this class for anything else, use TDEIO directly instead.
////////////////////////////////////////////////////////////////////////////////
AmarokHttp::AmarokHttp ( const TQString& hostname, TQ_UINT16 port,
                         TQObject* parent )
    : TQObject( parent ),
      m_hostname( hostname ),
      m_port( port )
{}

int
AmarokHttp::get ( const TQString & path )
{
    TQString uri = TQString( "http://%1:%2/%3" )
                  .arg( m_hostname )
                  .arg( m_port )
                  .arg( path );

    m_done = false;
    m_error = TQHttp::NoError;
    m_state = TQHttp::Connecting;
    TDEIO::TransferJob *job = TDEIO::get(uri, true, false);
    connect(job,  TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
            this, TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
    connect(job,  TQ_SIGNAL(result(TDEIO::Job*)),
            this, TQ_SLOT(slotResult(TDEIO::Job*)));

    return 0;
}

TQHttp::State
AmarokHttp::state() const
{
    return m_state;
}

TQByteArray
AmarokHttp::readAll ()
{
    return m_result;
}

TQHttp::Error
AmarokHttp::error()
{
    return m_error;
}

void
AmarokHttp::slotData(TDEIO::Job*, const TQByteArray& data)
{
    if( data.size() == 0 ) {
        return;
    }
    else if ( m_result.size() == 0 ) {
        m_result = data;
    }
    else if ( m_result.resize( m_result.size() + data.size() ) ) {
        memcpy( m_result.end(), data.data(),  data.size() );
    }
}

void
AmarokHttp::slotResult(TDEIO::Job* job)
{
    bool err = job->error();
    if( err || m_error != TQHttp::NoError ) {
        m_error = TQHttp::UnknownError;
    }
    else {
        m_error = TQHttp::NoError;
    }
    m_done = true;
    m_state = TQHttp::Unconnected;
    emit( requestFinished( 0, err ) );
}



///////////////////////////////////////////////////////////////////////////////
// CLASS Controller
////////////////////////////////////////////////////////////////////////////////

Controller *Controller::s_instance = 0;

Controller::Controller()
    : TQObject( EngineController::instance(), "lastfmController" )
    , m_service( 0 )
{
    TDEActionCollection* ac = Amarok::actionCollection();
    m_actionList.append( new TDEAction( i18n( "Ban" ), Amarok::icon( "remove" ),
                         KKey( TQt::CTRL | TQt::Key_B ), this, TQ_SLOT( ban() ), ac, "ban" ) );

    m_actionList.append( new TDEAction( i18n( "Love" ), Amarok::icon( "love" ),
                         KKey( TQt::CTRL | TQt::Key_L ), this, TQ_SLOT( love() ), ac, "love" ) );

    m_actionList.append( new TDEAction( i18n( "Skip" ), Amarok::icon( "next" ),
                         KKey( TQt::CTRL | TQt::Key_K ), this, TQ_SLOT( skip() ), ac, "skip" ) );
    setActionsEnabled( false );
}


Controller*
Controller::instance()
{
    if( !s_instance ) s_instance = new Controller();
    return s_instance;
}


KURL
Controller::getNewProxy( TQString genreUrl, bool useProxy )
{
    DEBUG_BLOCK

    m_genreUrl = genreUrl;

    if ( m_service ) playbackStopped();

    WebService* service; 
    // m_service might have already been reset until changeStation() and/or handshare()
    // calls return
    service = m_service = new WebService( this, useProxy );

    if( checkCredentials() )
    {
        TQString user = AmarokConfig::scrobblerUsername();
        TQString pass = AmarokConfig::scrobblerPassword();
        
        if( !user.isEmpty() && !pass.isEmpty() &&
            service->handshake( user, pass ) )
        {
            bool ok = service->changeStation( m_genreUrl );
            if( ok ) // else playbackStopped()
            {
                if( !AmarokConfig::submitPlayedSongs() )
                    m_service->enableScrobbling( false );
                setActionsEnabled( true );
                return KURL( m_service->proxyUrl() );
            }
        }
        if (service->wasCanceled()) {
            // It was canceled before (during kapp->processEvents() loop)
            delete service;
            return KURL("lastfm://"); // construct invalid url
        }
    }

    // Some kind of failure happened, so crap out
    playbackStopped();
    return KURL();
}

int
Controller::changeStation( TQString url )
{
    if (isPlaying()) {
        WebService* service = getService();
        if (service->changeStation( url )) {
            return 1; // success
        } else if (service->wasCanceled()) {
            delete service;
            return -1; // canceled
        } else {
            return 0; // failed
        }
    } else {
        return 0; // impossible, failed
    }
}

void
Controller::playbackStopped() //SLOT
{
    setActionsEnabled( false );

    if (m_service) {
        if (m_service->cancel()) 
            delete m_service;;
        m_service = 0;
    }
}


bool
Controller::checkCredentials() //static
{
    if( AmarokConfig::scrobblerUsername().isEmpty() || AmarokConfig::scrobblerPassword().isEmpty() )
    {
        LoginDialog dialog( 0 );
        dialog.setCaption( "last.fm" );
        return dialog.exec() == TQDialog::Accepted;
    }
    return true;
}


TQString
Controller::createCustomStation() //static
{
    TQString token;
    CustomStationDialog dialog( 0 );

    if( dialog.exec() == TQDialog::Accepted )
    {
        token =  dialog.text();
    }

    return token;
}


void
Controller::ban()
{
    if( m_service )
        m_service->ban();
}


void
Controller::love()
{
    if( m_service )
        m_service->love();
}


void
Controller::skip()
{
    if( m_service )
        m_service->skip();
}


void
Controller::setActionsEnabled( bool enable )
{   //pausing last.fm streams doesn't do anything good
    Amarok::actionCollection()->action( "play_pause" )->setEnabled( !enable );
    Amarok::actionCollection()->action( "pause" )->setEnabled( !enable );

    TDEAction* action;
    for( action = m_actionList.first(); action; action = m_actionList.next() )
        action->setEnabled( enable );
}

/// return a translatable description of the station we are connected to
TQString
Controller::stationDescription( TQString url )
{
    if( url.isEmpty() && instance() && instance()->isPlaying() )
        url = instance()->getService()->currentStation();

    if( url.isEmpty() ) return TQString();

    TQStringList elements = TQStringList::split( "/", url );

    /// TAG RADIOS
    // eg: lastfm://globaltag/rock
    if ( elements[1] == "globaltags" )
        return i18n( "Global Tag Radio: %1" ).arg( elements[2] );

    /// ARTIST RADIOS
    if ( elements[1] == "artist" )
    {
        // eg: lastfm://artist/Queen/similarartists
        if ( elements[3] == "similarartists" )
            return i18n( "Similar Artists to %1" ).arg( elements[2] );

        if ( elements[3] == "fans" )
            return i18n( "Artist Fan Radio: %1" ).arg( elements[2] );
    }

    /// CUSTOM STATION
    if ( elements[1] == "artistnames" )
    {
        // eg: lastfm://artistnames/genesis,pink floyd,queen

        // turn "genesis,pink floyd,queen" into "Genesis, Pink Floyd, Queen"
        TQString artists = elements[2];
        artists.replace( ",", ", " );
        const TQStringList words = TQStringList::split( " ", TQString( artists ).remove( "," ) );
        foreach( words ) {
            TQString capitalized = *it;
            capitalized.replace( 0, 1, (*it)[0].upper() );
            artists.replace( *it, capitalized );
        }

        return i18n( "Custom Station: %1" ).arg( artists );
    }

    /// USER RADIOS
    else if ( elements[1] == "user" )
    {
        // eg: lastfm://user/sebr/neighbours
        if ( elements[3] == "neighbours" )
            return i18n( "%1's Neighbor Radio" ).arg( elements[2] );

        // eg: lastfm://user/sebr/personal
        if ( elements[3] == "personal" )
            return i18n( "%1's Personal Radio" ).arg( elements[2] );

        // eg: lastfm://user/sebr/loved
        if ( elements[3] == "loved" )
            return i18n( "%1's Loved Radio" ).arg( elements[2] );

        // eg: lastfm://user/sebr/recommended/100 : 100 is number for how obscure the music should be
        if ( elements[3] == "recommended" )
            return i18n( "%1's Recommended Radio" ).arg( elements[2] );
    }

    /// GROUP RADIOS
    //eg: lastfm://group/Amarok%20users
    else if ( elements[1] == "group" )
        return i18n( "Group Radio: %1" ).arg( elements[2] );

    /// TRACK RADIOS
    else if ( elements[1] == "play" )
    {
        if ( elements[2] == "tracks" )
            return i18n( "Track Radio" );
        else if ( elements[2] == "artists" )
            return i18n( "Artist Radio" );
    }
    //kaput!
    return url;
}



////////////////////////////////////////////////////////////////////////////////
// CLASS WebService
////////////////////////////////////////////////////////////////////////////////

WebService::WebService( TQObject* parent, bool useProxy )
    : TQObject( parent, "lastfmParent" )
    , m_useProxy( useProxy )
    , m_deletionUnsafe( false )
    , m_wasCanceled( false )
{
    debug() << "Initialising Web Service" << endl;
}


WebService::~WebService()
{
    DEBUG_BLOCK
}

bool
WebService::cancel() {
    m_wasCanceled = true;
    return !m_deletionUnsafe;
}

void
WebService::readProxy() //SLOT
{
    TQString line;

    while( m_server->readln( line ) != -1 ) {
        debug() << line << endl;

        if( line == "AMAROK_PROXY: SYNC" )
            requestMetaData();
    }
}


bool
WebService::handshake( const TQString& username, const TQString& password )
{
    DEBUG_BLOCK

    m_username = username;
    m_password = password;

    AmarokHttp http( "ws.audioscrobbler.com", 80 );

    const TQString path =
            TQString( "/radio/handshake.php?version=%1&platform=%2&username=%3&passwordmd5=%4&debug=%5" )
            .arg( APP_VERSION )             //Muesli-approved: Amarok version, and Amarok-as-platform
            .arg( TQString("Amarok") )
            .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) )
            .arg( KMD5( m_password.utf8() ).hexDigest().data() )
            .arg( "0" );

    http.get( path );

    // We don't know what might happen within processEvents() loop.
    // Therefore this service instance must be protected from deletion.
    m_deletionUnsafe = true;
    do
        kapp->processEvents();
    while( http.state() != TQHttp::Unconnected );
    m_deletionUnsafe = false;
    if (this->wasCanceled())
        return false;

    if ( http.error() != TQHttp::NoError )
        return false;

    const TQString result( TQDeepCopy<TQString>( http.readAll() ) );

    debug() << "result: " << result << endl;

    m_session = parameter( "session", result );
    m_baseHost = parameter( "base_url", result );
    m_basePath = parameter( "base_path", result );
    m_subscriber = parameter( "subscriber", result ) == "1";
    m_streamUrl = TQUrl( parameter( "stream_url", result ) );
//     bool banned = parameter( "banned", result ) == "1";

    if ( m_session.lower() == "failed" ) {
        Amarok::StatusBar::instance()->longMessage( i18n(
        "Amarok failed to establish a session with last.fm. <br>"
        "Check if your last.fm user and password are correctly set."
        ) );
        return false;
    }

    Amarok::config( "Scrobbler" )->writeEntry( "Subscriber", m_subscriber );
    if( m_useProxy )
    {
        // Find free port
        MyServerSocket* socket = new MyServerSocket();
        const int port = socket->port();
        debug() << "Proxy server using port: " << port << endl;
        delete socket;
    
        m_proxyUrl = TQString( "http://localhost:%1/lastfm.mp3" ).arg( port );
    
        m_server = new Amarok::ProcIO();
        m_server->setComm( TDEProcess::Communication( TDEProcess::AllOutput ) );
        *m_server << "amarok_proxy.rb";
        *m_server << "--lastfm";
        *m_server << TQString::number( port );
        *m_server << m_streamUrl.toString();
        *m_server << AmarokConfig::soundSystem();
        *m_server << Amarok::proxyForUrl( m_streamUrl.toString() );
    
        if( !m_server->start( KProcIO::NotifyOnExit, true ) ) {
            error() << "Failed to start amarok_proxy.rb" << endl;
            return false;
        }
    
        TQString line;
        m_deletionUnsafe = true;
        while( true ) {
            kapp->processEvents();
            m_server->readln( line );
            if( line == "AMAROK_PROXY: startup" ) break;
        }
        m_deletionUnsafe = false;
        if (this->wasCanceled())
            return false;
    
        connect( m_server, TQ_SIGNAL( readReady( KProcIO* ) ), this, TQ_SLOT( readProxy() ) );
        connect( m_server, TQ_SIGNAL( processExited( TDEProcess* ) ), Controller::instance(), TQ_SLOT( playbackStopped() ) );
    }
    else
        m_proxyUrl = m_streamUrl.toString();

    return true;
}


bool
WebService::changeStation( TQString url )
{
    debug() << "Changing station:" << url << endl;

    AmarokHttp http( m_baseHost, 80 );

    http.get( TQString( m_basePath + "/adjust.php?session=%1&url=%2&debug=0" )
             .arg( m_session )
             .arg( url ) );

    m_deletionUnsafe = true;
    do
        kapp->processEvents();
    while( http.state() != TQHttp::Unconnected );
    m_deletionUnsafe = false;
    if (this->wasCanceled())
        return false;

    if ( http.error() != TQHttp::NoError )
    {
        showError( E_OTHER ); // default error
        return false;
    }

    const TQString result( TQDeepCopy<TQString>( http.readAll() ) );
    const int errCode = parameter( "error", result ).toInt();

    if ( errCode )
    {
        showError( errCode );
        return false;
    }

    const TQString _url = parameter( "url", result );
    if ( _url.startsWith( "lastfm://" ) )
    {
        m_station = _url; // parse it in stationDescription
        emit stationChanged( _url, m_station );
    }
    else
        emit stationChanged( _url, TQString() );

    return true;
}

void
WebService::requestMetaData() //SLOT
{
    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( int, bool ) ), this, TQ_SLOT( metaDataFinished( int, bool ) ) );

    http->get( TQString( m_basePath + "/np.php?session=%1&debug=%2" )
                  .arg( m_session )
                  .arg( "0" ) );
}


void
WebService::metaDataFinished( int /*id*/, bool error ) //SLOT
{
    DEBUG_BLOCK

    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    const TQString result( http->readAll() );
    debug() << result << endl;

    int errCode = parameter( "error", result ).toInt();
    if ( errCode > 0 ) {
        debug() << "Metadata failed with error code: " << errCode << endl;
        showError( errCode );
        return;
    }

    m_metaBundle.setArtist( parameter( "artist", result ) );
    m_metaBundle.setAlbum ( parameter( "album", result )  );
    m_metaBundle.setTitle ( parameter( "track", result )  );
    m_metaBundle.setUrl   ( KURL( Controller::instance()->getGenreUrl() ) );
    m_metaBundle.setLength( parameter( "trackduration", result ).toInt()  );

    Bundle lastFmStuff;
    TQString imageUrl = parameter( "albumcover_medium", result );

    if( imageUrl == "http://static.last.fm/coverart/" ||
        imageUrl == "http://static.last.fm/depth/catalogue/no_album_large.gif" )
        imageUrl = TQString();

    lastFmStuff.setImageUrl ( CollectionDB::instance()->notAvailCover( true ) );
    lastFmStuff.setArtistUrl( parameter( "artist_url", result ) );
    lastFmStuff.setAlbumUrl ( parameter( "album_url", result ) );
    lastFmStuff.setTitleUrl ( parameter( "track_url", result ) );
//     bool discovery = parameter( "discovery", result ) != "-1";

    m_metaBundle.setLastFmBundle( lastFmStuff );

    const KURL u( imageUrl );
    if( !u.isValid() ) {
        debug() << "imageUrl empty or invalid." << endl;
        emit metaDataResult( m_metaBundle );
        return;
    }

    TDEIO::Job* job = TDEIO::storedGet( u, true, false );
    connect( job, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( fetchImageFinished( TDEIO::Job* ) ) );
}


void
WebService::fetchImageFinished( TDEIO::Job* job ) //SLOT
{
    DEBUG_BLOCK

    if( job->error() == 0 ) {
        const TQString path = Amarok::saveLocation() + "lastfm_image.png";
        const int size = AmarokConfig::coverPreviewSize();

        TQImage img( static_cast<TDEIO::StoredTransferJob*>( job )->data() );
        img.smoothScale( size, size ).save( path, "PNG" );

        m_metaBundle.lastFmBundle()->setImageUrl( CollectionDB::makeShadowedImage( path, false ) );
    }
    emit metaDataResult( m_metaBundle );
}


void
WebService::enableScrobbling( bool enabled ) //SLOT
{
    if ( enabled )
        debug() << "Enabling Scrobbling!" << endl;
    else
        debug() << "Disabling Scrobbling!" << endl;

    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( int, bool ) ), this, TQ_SLOT( enableScrobblingFinished( int, bool ) ) );

    http->get( TQString( m_basePath + "/control.php?session=%1&command=%2&debug=%3" )
                  .arg( m_session )
                  .arg( enabled ? TQString( "rtp" ) : TQString( "nortp" ) )
                  .arg( "0" ) );
}


void
WebService::enableScrobblingFinished( int /*id*/, bool error ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if ( error ) return;

    emit enableScrobblingDone();
}


void
WebService::love() //SLOT
{
    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( int, bool ) ), this, TQ_SLOT( loveFinished( int, bool ) ) );

    http->get( TQString( m_basePath + "/control.php?session=%1&command=love&debug=%2" )
                  .arg( m_session )
                  .arg( "0" ) );
    Amarok::StatusBar::instance()->shortMessage( i18n("love, as in affection", "Loving song...") );
}


void
WebService::skip() //SLOT
{
    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( int, bool ) ), this, TQ_SLOT( skipFinished( int, bool ) ) );

    http->get( TQString( m_basePath + "/control.php?session=%1&command=skip&debug=%2" )
                  .arg( m_session )
                  .arg( "0" ) );
    Amarok::StatusBar::instance()->shortMessage( i18n("Skipping song...") );
}


void
WebService::ban() //SLOT
{
    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( int, bool ) ), this, TQ_SLOT( banFinished( int, bool ) ) );

    http->get( TQString( m_basePath + "/control.php?session=%1&command=ban&debug=%2" )
                  .arg( m_session )
                  .arg( "0" ) );
    Amarok::StatusBar::instance()->shortMessage( i18n("Ban, as in dislike", "Banning song...") );
}


void
WebService::loveFinished( int /*id*/, bool error ) //SLOT
{
    DEBUG_BLOCK

    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    emit loveDone();
}


void
WebService::skipFinished( int /*id*/, bool error ) //SLOT
{
    DEBUG_BLOCK

    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    EngineController::engine()->flushBuffer();
    emit skipDone();
}


void
WebService::banFinished( int /*id*/, bool error ) //SLOT
{
    DEBUG_BLOCK

    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    EngineController::engine()->flushBuffer();
    emit banDone();
    emit skipDone();
}


void
WebService::friends( TQString username )
{
    if ( username.isEmpty() )
        username = m_username;

    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( bool ) ), this, TQ_SLOT( friendsFinished( bool ) ) );

    http->get( TQString( "/1.0/user/%1/friends.xml" )
                  .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) );
}


void
WebService::friendsFinished( int /*id*/, bool error ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    TQDomDocument document;
    document.setContent( http->readAll() );

    if ( document.elementsByTagName( "friends" ).length() == 0 )
    {
        emit friendsResult( TQString( "" ), TQStringList() );
        return;
    }

    TQStringList friends;
    TQString user = document.elementsByTagName( "friends" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
    TQDomNodeList values = document.elementsByTagName( "user" );
    for ( uint i = 0; i < values.count(); i++ )
    {
        friends << values.item( i ).attributes().namedItem( "username" ).nodeValue();
    }

    emit friendsResult( user, friends );
}


void
WebService::neighbours( TQString username )
{
    if ( username.isEmpty() )
        username = m_username;

    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( bool ) ), this, TQ_SLOT( neighboursFinished( bool ) ) );

    http->get( TQString( "/1.0/user/%1/neighbours.xml" )
                  .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) );
}


void
WebService::neighboursFinished( int /*id*/, bool error ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error )  return;

    TQDomDocument document;
    document.setContent( http->readAll() );

    if ( document.elementsByTagName( "neighbours" ).length() == 0 )
    {
        emit friendsResult( TQString( "" ), TQStringList() );
        return;
    }

    TQStringList neighbours;
    TQString user = document.elementsByTagName( "neighbours" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
    TQDomNodeList values = document.elementsByTagName( "user" );
    for ( uint i = 0; i < values.count(); i++ )
    {
        neighbours << values.item( i ).attributes().namedItem( "username" ).nodeValue();
    }

    emit neighboursResult( user, neighbours );
}


void
WebService::userTags( TQString username )
{
    if ( username.isEmpty() )
        username = m_username;

    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( bool ) ), this, TQ_SLOT( userTagsFinished( bool ) ) );

    http->get( TQString( "/1.0/user/%1/tags.xml?debug=%2" )
                  .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) );
}


void
WebService::userTagsFinished( int /*id*/, bool error ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    TQDomDocument document;
    document.setContent( http->readAll() );

    if ( document.elementsByTagName( "toptags" ).length() == 0 )
    {
        emit userTagsResult( TQString(), TQStringList() );
        return;
    }

    TQStringList tags;
    TQDomNodeList values = document.elementsByTagName( "tag" );
    TQString user = document.elementsByTagName( "toptags" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
    for ( uint i = 0; i < values.count(); i++ )
    {
        TQDomNode item = values.item( i ).namedItem( "name" );
        tags << item.toElement().text();
    }
    emit userTagsResult( user, tags );
}


void
WebService::recentTracks( TQString username )
{
    if ( username.isEmpty() )
        username = m_username;

    AmarokHttp *http = new AmarokHttp( m_baseHost, 80, this );
    connect( http, TQ_SIGNAL( requestFinished( bool ) ), this, TQ_SLOT( recentTracksFinished( bool ) ) );

    http->get( TQString( "/1.0/user/%1/recenttracks.xml" )
                  .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) ) );
}


void
WebService::recentTracksFinished( int /*id*/, bool error ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();
    if( error ) return;

    TQValueList< TQPair<TQString, TQString> > songs;
    TQDomDocument document;
    document.setContent( http->readAll() );

    if ( document.elementsByTagName( "recenttracks" ).length() == 0 )
    {
        emit recentTracksResult( TQString(), songs );
        return;
    }

    TQDomNodeList values = document.elementsByTagName( "track" );
    TQString user = document.elementsByTagName( "recenttracks" ).item( 0 ).attributes().namedItem( "user" ).nodeValue();
    for ( uint i = 0; i < values.count(); i++ )
    {
        TQPair<TQString, TQString> song;
        song.first = values.item( i ).namedItem( "artist" ).toElement().text();
        song.second = values.item( i ).namedItem( "name" ).toElement().text();

        songs << song;
    }
    emit recentTracksResult( user, songs );
}


void
WebService::recommend( int type, TQString username, TQString artist, TQString token )
{
    TQString modeToken = "";
    switch ( type )
    {
        case 0:
            modeToken = TQString( "artist_name=%1" ).arg( TQString( TQUrl( artist ).encodedPathAndQuery() ) );
            break;

        case 1:
            modeToken = TQString( "album_artist=%1&album_name=%2" )
                           .arg( TQString( TQUrl( artist ).encodedPathAndQuery() ) )
                           .arg( TQString( TQUrl( token ).encodedPathAndQuery() ) );
            break;

        case 2:
            modeToken = TQString( "track_artist=%1&track_name=%2" )
                           .arg( TQString( TQUrl( artist ).encodedPathAndQuery() ) )
                           .arg( TQString( TQUrl( token ).encodedPathAndQuery() ) );
            break;
    }

    TQHttp *http = new TQHttp( "wsdev.audioscrobbler.com", 80, this );
    connect( http, TQ_SIGNAL( requestFinished( bool ) ), this, TQ_SLOT( recommendFinished( bool ) ) );

    uint currentTime = TQDateTime::currentDateTime( TQt::UTC ).toTime_t();
    TQString challenge = TQString::number( currentTime );

    TQCString md5pass = KMD5( KMD5( m_password.utf8() ).hexDigest() + currentTime ).hexDigest();

    token = TQString( "user=%1&auth=%2&nonce=%3recipient=%4" )
                .arg( TQString( TQUrl( currentUsername() ).encodedPathAndQuery() ) )
                .arg( TQString( TQUrl( md5pass ).encodedPathAndQuery() ) )
                .arg( TQString( TQUrl( challenge ).encodedPathAndQuery() ) )
                .arg( TQString( TQUrl( username ).encodedPathAndQuery() ) );

    TQHttpRequestHeader header( "POST", "/1.0/rw/recommend.php?" + token.utf8() );
    header.setValue( "Host", "wsdev.audioscrobbler.com" );
    header.setContentType( "application/x-www-form-urlencoded" );
    http->request( header, modeToken.utf8() );
}


void
WebService::recommendFinished( int /*id*/, bool /*error*/ ) //SLOT
{
    AmarokHttp* http = (AmarokHttp*) sender();
    http->deleteLater();

    debug() << "Recommendation:" << http->readAll() << endl;
}


TQString
WebService::parameter( const TQString keyName, const TQString data ) const
{
    TQStringList list = TQStringList::split( '\n', data );

    for ( uint i = 0; i < list.size(); i++ )
    {
        TQStringList values = TQStringList::split( '=', list[i] );
        if ( values[0] == keyName )
        {
            values.remove( values.at(0) );
            return TQString::fromUtf8( values.join( "=" ).ascii() );
        }
    }

    return TQString( "" );
}


TQStringList
WebService::parameterArray( const TQString keyName, const TQString data ) const
{
    TQStringList result;
    TQStringList list = TQStringList::split( '\n', data );

    for ( uint i = 0; i < list.size(); i++ )
    {
        TQStringList values = TQStringList::split( '=', list[i] );
        if ( values[0].startsWith( keyName ) )
        {
            values.remove( values.at(0) );
            result.append( TQString::fromUtf8( values.join( "=" ).ascii() ) );
        }
    }

    return result;
}


TQStringList
WebService::parameterKeys( const TQString keyName, const TQString data ) const
{
    TQStringList result;
    TQStringList list = TQStringList::split( '\n', data );

    for ( uint i = 0; i < list.size(); i++ )
    {
        TQStringList values = TQStringList::split( '=', list[i] );
        if ( values[0].startsWith( keyName ) )
        {
            values = TQStringList::split( '[', values[0] );
            values = TQStringList::split( ']', values[1] );
            result.append( values[0] );
        }
    }


    return result;
}

void
WebService::showError( int code, TQString message )
{
    switch ( code )
    {
        case E_NOCONTENT:
            message = i18n( "There is not enough content to play this station." );
            break;
        case E_NOMEMBERS:
            message = i18n( "This group does not have enough members for radio." );
            break;
        case E_NOFANS:
            message = i18n( "This artist does not have enough fans for radio." );
            break;
        case E_NOAVAIL:
            message = i18n( "This item is not available for streaming." );
            break;
        case E_NOSUBSCRIBER:
            message = i18n( "This feature is only available to last.fm subscribers." );
            break;
        case E_NONEIGHBOURS:
            message = i18n( "There are not enough neighbors for this radio." );
            break;
        case E_NOSTOPPED:
            message = i18n( "This stream has stopped. Please try another station." );
            break;
        default:
            if( message.isEmpty() )
                message = i18n( "Failed to play this last.fm stream." );
    }

    Amarok::StatusBar::instance()->longMessage( message, KDE::StatusBar::Sorry );
}

////////////////////////////////////////////////////////////////////////////////
// CLASS LastFm::Bundle
////////////////////////////////////////////////////////////////////////////////

Bundle::Bundle( const Bundle& lhs )
    : m_imageUrl( lhs.m_imageUrl )
    , m_albumUrl( lhs.m_albumUrl )
    , m_artistUrl( lhs.m_artistUrl )
    , m_titleUrl( lhs.m_titleUrl )
{}

void Bundle::detach() {
    m_imageUrl = TQDeepCopy<TQString>(m_imageUrl);
    m_albumUrl = TQDeepCopy<TQString>(m_albumUrl);
    m_artistUrl = TQDeepCopy<TQString>(m_artistUrl);
    m_titleUrl = TQDeepCopy<TQString>(m_titleUrl);
}

////////////////////////////////////////////////////////////////////////////////
// CLASS LastFm::LoginDialog
////////////////////////////////////////////////////////////////////////////////
LoginDialog::LoginDialog( TQWidget *parent )
    : KDialogBase( parent, "LastfmLogin", true, TQString(), Ok|Cancel)
{
    makeGridMainWidget( 1, TQt::Horizontal );
    new TQLabel( i18n( "To use last.fm with Amarok, you need a last.fm profile." ), mainWidget() );

    makeGridMainWidget( 2, TQt::Horizontal );
    TQLabel *nameLabel = new TQLabel( i18n("&Username:"), mainWidget() );
    m_userLineEdit = new KLineEdit( mainWidget() );
    nameLabel->setBuddy( m_userLineEdit );

    TQLabel *passLabel = new TQLabel( i18n("&Password:"), mainWidget() );
    m_passLineEdit = new KLineEdit( mainWidget() );
    m_passLineEdit->setEchoMode( TQLineEdit::Password );
    passLabel->setBuddy( m_passLineEdit );

    m_userLineEdit->setFocus();
}


void LoginDialog::slotOk()
{
    AmarokConfig::setScrobblerUsername( m_userLineEdit->text() );
    AmarokConfig::setScrobblerPassword( m_passLineEdit->text() );

    KDialogBase::slotOk();
}


////////////////////////////////////////////////////////////////////////////////
// CLASS LastFm::CustomStationDialog
////////////////////////////////////////////////////////////////////////////////
CustomStationDialog::CustomStationDialog( TQWidget *parent )
    : KDialogBase( parent, "LastfmCustomStation", true, i18n( "Create Custom Station" ) , Ok|Cancel)
{
    makeVBoxMainWidget();

    new TQLabel( i18n( "Enter the name of a band or artist you like:" ), mainWidget() );

    m_edit = new KLineEdit( mainWidget(), "CustomStationEdit" );
    m_edit->setFocus();
}


TQString
CustomStationDialog::text() const
{
    return m_edit->text();
}


#include "lastfm.moc"
