// (C) 2004 Mark Kretschmann <markey@web.de>
// (C) 2004 Stefan Bogner <bochi@online.ms>
// (C) 2004 Max Howell
// See COPYING file for licensing information.

#include "amarok.h"
#include "amarokconfig.h"
#include "collectiondb.h"
#include "config.h"        //for AMAZON_SUPPORT
#include "covermanager.h"
#include "coverfetcher.h"
#include "debug.h"
#include "statusbar.h"

#include <tqdom.h>
#include <tqhbox.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqregexp.h>

#include <kapplication.h>
#include <kcombobox.h>
#include <kcursor.h> //waiting cursor
#include <kdialog.h>
#include <kiconloader.h>
#include <tdefiledialog.h>
#include <tdeio/job.h>
#include <tdeio/jobclasses.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <tdepopupmenu.h>
#include <kpushbutton.h>
#include <twin.h>


void
Amarok::coverContextMenu( TQWidget *parent, TQPoint point, const TQString &artist, const TQString &album, bool showCoverManager )
{
        TDEPopupMenu menu;
        enum { SHOW, FETCH, CUSTOM, DELETE, MANAGER };

        menu.insertTitle( i18n( "Cover Image" ) );

        menu.insertItem( SmallIconSet( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), SHOW );
        menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch From amazon.%1" ).arg( CoverManager::amazonTld() ), FETCH );
        menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover" ), CUSTOM );
        bool disable = !album.isEmpty(); // disable setting covers for unknown albums
        menu.setItemEnabled( FETCH, disable );
        menu.setItemEnabled( CUSTOM, disable );
        menu.insertSeparator();

        menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Cover" ), DELETE );
        if ( showCoverManager ) {
            menu.insertSeparator();
            menu.insertItem( SmallIconSet( Amarok::icon( "covermanager" ) ), i18n( "Cover &Manager" ), MANAGER );
        }
        #ifndef AMAZON_SUPPORT
        menu.setItemEnabled( FETCH, false );
        #endif
        disable = !CollectionDB::instance()->albumImage( artist, album, 0 ).contains( "nocover" );
        menu.setItemEnabled( SHOW, disable );
        menu.setItemEnabled( DELETE, disable );

        switch( menu.exec( point ) )
        {
        case SHOW:
            CoverManager::viewCover( artist, album, parent );
            break;

        case DELETE:
        {
            const int button = KMessageBox::warningContinueCancel( parent,
                i18n( "Are you sure you want to remove this cover from the Collection?" ),
                TQString(),
                KStdGuiItem::del() );

            if ( button == KMessageBox::Continue )
                CollectionDB::instance()->removeAlbumImage( artist, album );
            break;
        }

        case FETCH:
        #ifdef AMAZON_SUPPORT
            CollectionDB::instance()->fetchCover( parent, artist, album, false );
            break;
        #endif

        case CUSTOM:
        {
            TQString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( artist ) );
            TQString album_id; album_id.setNum( CollectionDB::instance()->albumID( album ) );
            TQStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id );
            TQString startPath = ":homedir";

            if ( !values.isEmpty() ) {
                KURL url;
                url.setPath( values.first() );
                startPath = url.directory();
            }

            KURL file = KFileDialog::getImageOpenURL( startPath, parent, i18n("Select Cover Image File") );
            if ( !file.isEmpty() )
                CollectionDB::instance()->setAlbumImage( artist, album, file );
            break;
        }

        case MANAGER:
            CoverManager::showOnce( album );
            break;
        }
}



CoverLabel::CoverLabel ( TQWidget * parent, const char * name, WFlags f )
        : TQLabel( parent, name, f)
{}


void CoverLabel::mouseReleaseEvent(TQMouseEvent *pEvent) {
    if (pEvent->button() == Qt::LeftButton || pEvent->button() == Qt::RightButton)
    {
        Amarok::coverContextMenu( this, pEvent->globalPos(), m_artist, m_album, false );
    }
}


CoverFetcher::CoverFetcher( TQWidget *parent, const TQString &artist, TQString album )
        : TQObject( parent, "CoverFetcher" )
        , m_artist( artist )
        , m_album( album )
        , m_size( 2 )
        , m_success( true )
{
    DEBUG_FUNC_INFO

    TQStringList extensions;
    extensions << i18n("disc") << i18n("disk") << i18n("remaster") << i18n("cd") << i18n("single") << i18n("soundtrack") << i18n("part")
               << "disc" << "disk" << "remaster" << "cd" << "single" << "soundtrack" << "part" << "cds" /*cd single*/;

    //we do several queries, one raw ie, without the following modifications
    //the others have the above strings removed with the following regex, as this can increase hit-rate
    const TQString template1 = " ?-? ?[(^{]* ?%1 ?\\d*[)^}\\]]* *$"; //eg album - [disk 1] -> album
    foreach( extensions ) {
        TQRegExp regexp( template1.arg( *it ) );
        regexp.setCaseSensitive( false );
        album.remove( regexp );
    }

    //TODO try queries that remove anything in album after a " - " eg Les Mis. - Excerpts

    /**
     * We search for artist - album, and just album, using the exact album text and the
     * manipulated album text.
     */

    //search on our modified term, then the original
    if ( !m_artist.isEmpty() )
        m_userQuery = m_artist + " - ";
    m_userQuery += m_album;

    m_queries += m_artist + " - " + album;
    m_queries += m_userQuery;
    m_queries += album;
    m_queries += m_album;

    //don't do the same searches twice in a row
    if( m_album == album )  {
        m_queries.pop_front();
        m_queries.pop_back();
    }

    /**
     * Finally we do a search for just the artist, just in case as this often
     * turns up a cover, and it might just be the right one! Also it would be
     * the only valid search if m_album.isEmpty()
     */
    m_queries += m_artist;

    TQApplication::setOverrideCursor( KCursor::workingCursor() );
}

CoverFetcher::~CoverFetcher()
{
    DEBUG_FUNC_INFO

    TQApplication::restoreOverrideCursor();
}

void
CoverFetcher::startFetch()
{
    DEBUG_FUNC_INFO

    // Static license Key. Thanks hydrogen ;-)
    const TQString LICENSE( "11ZKJS8X1ETSTJ6MT802" );

    // reset all values
    m_coverAmazonUrls.clear();
    m_coverAsins.clear();
    m_coverUrls.clear();
    m_coverNames.clear();
    m_xml = TQString();
    m_size = 2;

    if ( m_queries.isEmpty() ) {
        debug() << "m_queries is empty" << endl;
        finishWithError( i18n("No cover found") );
        return;
    }
    TQString query = m_queries.front();
    m_queries.pop_front();

    // '&' breaks searching
    query.remove('&');
    
    TQString locale = AmarokConfig::amazonLocale();
    TQString tld;

    if( locale == "us" )
        tld = "com";
    else if( locale =="uk" )
        tld = "co.uk";
    else
        tld = locale;

    int mibenum = 106; // utf-8

    TQString url;
    url = "http://ecs.amazonaws." + tld
        + "/onca/xml?Service=AWSECommerceService&Version=2007-10-29&Operation=ItemSearch&AssociateTag=webservices-20&AWSAccessKeyId=" + LICENSE
        + "&Keywords=" + KURL::encode_string_no_slash( query, mibenum )
        + "&SearchIndex=Music&ResponseGroup=Small,Images";
    debug() << url << endl;

    TDEIO::TransferJob* job = TDEIO::storedGet( url, false, false );
    connect( job, TQT_SIGNAL(result( TDEIO::Job* )), TQT_SLOT(finishedXmlFetch( TDEIO::Job* )) );

    Amarok::StatusBar::instance()->newProgressOperation( job );
}


//////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
//////////////////////////////////////////////////////////////////////////////////////////

void
CoverFetcher::finishedXmlFetch( TDEIO::Job *job ) //SLOT
{
    DEBUG_BLOCK

    // NOTE: job can become 0 when this method is called from attemptAnotherFetch()

    if( job && job->error() ) {
        finishWithError( i18n("There was an error communicating with Amazon."), job );
        return;
    }
    if ( job ) {
        TDEIO::StoredTransferJob* const storedJob = static_cast<TDEIO::StoredTransferJob*>( job );
        m_xml = TQString::fromUtf8( storedJob->data().data(), storedJob->data().size() );
    }

    TQDomDocument doc;
    if( !doc.setContent( m_xml ) ) {
        m_errors += i18n("The XML obtained from Amazon is invalid.");
        startFetch();
        return;
    }

    m_coverAsins.clear();
    m_coverAmazonUrls.clear();
    m_coverUrls.clear();
    m_coverNames.clear();

    // the url for the Amazon product info page
    const TQDomNodeList list = doc.documentElement().namedItem( "Items" ).childNodes();

    for(int i = 0; i < list.count(); i++ )
    {
        TQDomNode n = list.item( i );
        if( n.isElement() && n.nodeName() == "IsValid" )
        {
            if( n.toElement().text() == "False" )
            {
                warning() << "The XML Is Invalid!";
                return;
            }
        }
        else if( list.item( i ).nodeName() == "Item" )
        {
            const TQDomNode node = list.item( i );
            parseItemNode( node );
        }
    }
    attemptAnotherFetch();
}

void CoverFetcher::parseItemNode( const TQDomNode &node )
{
    TQDomNode it = node.firstChild();

    TQString size;
    switch( m_size )
    {
        case 0:  size = "Small";  break;
        case 1:  size = "Medium"; break;
        default: size = "Large";  break;
    }
    size += "Image";

    while ( !it.isNull() ) {
        if ( it.isElement() ) {
            TQDomElement e = it.toElement();
            if(e.tagName()=="ASIN")
            {
                m_asin = e.text();
                m_coverAsins += m_asin;
            }
            else if(e.tagName() == "DetailPageURL" )
            {
                m_amazonURL = e.text();
                m_coverAmazonUrls += m_amazonURL;
            }
            else if( e.tagName() == size )
            {
                TQDomNode subIt = e.firstChild();
                while( !subIt.isNull() )
                {
                    if( subIt.isElement() )
                    {
                        TQDomElement subE = subIt.toElement();
                        if( subE.tagName() == "URL" )
                        {
                            const TQString coverUrl = subE.text();
                            m_coverUrls += coverUrl;
                            break;
                        }
                    }
                    subIt = subIt.nextSibling();
                }
            }
            else if( e.tagName() == "ItemAttributes" )
            {
                TQDomNodeList nodes = e.childNodes();
                TQDomNode iter;
                TQString artist;
                TQString album;
                for( int i = 0; i < nodes.count(); i++ )
                {
                    iter = nodes.item( i );

                    if( iter.isElement() )
                    {
                        if( iter.nodeName() == "Artist" )
                        {
                            artist = iter.toElement().text();
                        }
                        else if( iter.nodeName() == "Title" )
                        {
                            album = iter.toElement().text();
                        }
                    }
                }
                m_coverNames += TQString( artist + " - " + album );
            }
        }
        it = it.nextSibling();
    }
}

void
CoverFetcher::finishedImageFetch( TDEIO::Job *job ) //SLOT
{
    if( job->error() ) {
        debug() << "finishedImageFetch(): TDEIO::error(): " << job->error() << endl;

        m_errors += i18n("The cover could not be retrieved.");

        attemptAnotherFetch();
        return;
    }

    m_image.loadFromData( static_cast<TDEIO::StoredTransferJob*>( job )->data() );

    if( m_image.width() <= 1 ) {
        //Amazon seems to offer images of size 1x1 sometimes
        //Amazon has nothing to offer us for the requested image size
        m_errors += i18n("The cover-data produced an invalid image.");
        attemptAnotherFetch();
    }

    else if( m_userCanEditQuery )
        //yay! image found :)
        //lets see if the user wants it
        showCover();

    else
        //image loaded successfully yay!
        finish();
}


void
CoverFetcher::attemptAnotherFetch()
{
    DEBUG_BLOCK

    if( !m_coverUrls.isEmpty() ) {
        // Amazon suggested some more cover URLs to try before we
        // try a different query

        TDEIO::TransferJob* job = TDEIO::storedGet( KURL(m_coverUrls.front()), false, false );
        connect( job, TQT_SIGNAL(result( TDEIO::Job* )), TQT_SLOT(finishedImageFetch( TDEIO::Job* )) );

        Amarok::StatusBar::instance()->newProgressOperation( job );

        m_coverUrls.pop_front();

        m_currentCoverName = m_coverNames.front();
        m_coverNames.pop_front();

        m_amazonURL = m_coverAmazonUrls.front();
        m_coverAmazonUrls.pop_front();

        m_asin = m_coverAsins.front();
        m_coverAsins.pop_front();
    }

    else if( !m_xml.isEmpty() && m_size > 0 ) {
        // we need to try smaller sizes, this often is
        // fruitless, but does work out sometimes.
        m_size--;

        finishedXmlFetch( 0 );
    }

    else if( !m_queries.isEmpty() ) {
        // we have some queries left in the pot
        startFetch();
    }

    else if( m_userCanEditQuery ) {
        // we have exhausted all the predetermined queries
        // so lets let the user give it a try
        getUserQuery( i18n("You have seen all the covers Amazon returned using the query below. Perhaps you can refine it:") );
        m_coverAmazonUrls.clear();
        m_coverAsins.clear();
        m_coverUrls.clear();
        m_coverNames.clear();
    }
    else
        finishWithError( i18n("No cover found") );
}


// Moved outside the only function that uses it because
// gcc 2.95 doesn't like class declarations there.
    class EditSearchDialog : public KDialog
    {
    public:
        EditSearchDialog( TQWidget* parent, const TQString &text, const TQString &keyword, CoverFetcher *fetcher )
                : KDialog( parent )
        {
            setCaption( i18n( "Amazon Query Editor" ) );

            // amazon combo box
            KComboBox* amazonLocale = new KComboBox( this );
            amazonLocale->insertItem( i18n("International"), CoverFetcher::International );
            amazonLocale->insertItem( i18n("Canada"), CoverFetcher::Canada );
            amazonLocale->insertItem( i18n("France"), CoverFetcher::France );
            amazonLocale->insertItem( i18n("Germany"), CoverFetcher::Germany );
            amazonLocale->insertItem( i18n("Japan"), CoverFetcher::Japan);
            amazonLocale->insertItem( i18n("United Kingdom"), CoverFetcher::UK );
            if( CoverManager::instance() )
                connect( amazonLocale, TQT_SIGNAL( activated(int) ),
                        CoverManager::instance(), TQT_SLOT( changeLocale(int) ) );
            else
                connect( amazonLocale, TQT_SIGNAL( activated(int) ),
                        fetcher, TQT_SLOT( changeLocale(int) ) );
            TQHBoxLayout *hbox1 = new TQHBoxLayout( 8 );
            hbox1->addWidget( new TQLabel( i18n( "Amazon Locale: " ), this ) );
            hbox1->addWidget( amazonLocale );

            int currentLocale = CoverFetcher::localeStringToID( AmarokConfig::amazonLocale() );
            amazonLocale->setCurrentItem( currentLocale );

            KPushButton* cancelButton = new KPushButton( KStdGuiItem::cancel(), this );
            KPushButton* searchButton = new KPushButton( i18n("&Search"), this );

            TQHBoxLayout *hbox2 = new TQHBoxLayout( 8 );
            hbox2->addItem( new TQSpacerItem( 160, 8, TQSizePolicy::Expanding, TQSizePolicy::Minimum ) );
            hbox2->addWidget( searchButton );
            hbox2->addWidget( cancelButton );

            TQVBoxLayout *vbox = new TQVBoxLayout( this, 8, 8 );
            vbox->addLayout( hbox1 );
            vbox->addWidget( new TQLabel( "<qt>" + text, this ) );
            vbox->addWidget( new KLineEdit( keyword, this, "Query" ) );
            vbox->addLayout( hbox2 );

            searchButton->setDefault( true );

            adjustSize();
            setFixedHeight( height() );

            connect( searchButton, TQT_SIGNAL(clicked()), TQT_SLOT(accept()) );
            connect( cancelButton, TQT_SIGNAL(clicked()), TQT_SLOT(reject()) );
        }

        TQString query() { return static_cast<KLineEdit*>(TQT_TQWIDGET(child( "Query" )))->text(); }
    };

TQString
CoverFetcher::localeIDToString( int id )//static
{
    switch ( id )
    {
    case International:
        return "us";
    case Canada:
        return "ca";
    case France:
        return "fr";
    case Germany:
        return "de";
    case Japan:
        return "jp";
    case UK:
        return "uk";
    }

    return "us";
}

int
CoverFetcher::localeStringToID( const TQString &s )
{
    int id = International;
    if( s == "fr" ) id = France;
    else if( s == "de" ) id = Germany;
    else if( s == "jp" ) id = Japan;
    else if( s == "uk" ) id = UK;
    else if( s == "ca" ) id = Canada;

    return id;
}

void
CoverFetcher::changeLocale( int id )//SLOT
{
    TQString locale = localeIDToString( id );
    AmarokConfig::setAmazonLocale( locale );
}


void
CoverFetcher::getUserQuery( TQString explanation )
{
    if( explanation.isEmpty() )
        explanation = i18n("Ask Amazon for covers using this query:");

    EditSearchDialog dialog(
            TQT_TQWIDGET( parent() ),
            explanation,
            m_userQuery,
            this );

    switch( dialog.exec() )
    {
    case TQDialog::Accepted:
        m_userQuery = dialog.query();
        m_queries = m_userQuery;
        startFetch();
        break;
    default:
        finishWithError( i18n( "Aborted." ) );
        break;
    }
}

    class CoverFoundDialog : public KDialog
    {
    public:
        CoverFoundDialog( TQWidget *parent, const TQImage &cover, const TQString &productname )
                : KDialog( parent )
        {
            // Gives the window a small title bar, and skips a taskbar entry
            KWin::setType( winId(), NET::Utility );
            KWin::setState( winId(), NET::SkipTaskbar );

            (new TQVBoxLayout( this ))->setAutoAdd( true );

            TQLabel      *labelPix  = new TQLabel( this );
            TQLabel      *labelName = new TQLabel( this );
            TQHBox       *buttons   = new TQHBox( this );
            KPushButton *save      = new KPushButton( KStdGuiItem::save(), buttons );
            KPushButton *newsearch = new KPushButton( i18n( "Ne&w Search..." ), buttons, "NewSearch" );
            KPushButton *nextcover = new KPushButton( i18n( "&Next Cover" ), buttons, "NextCover" );
            KPushButton *cancel    = new KPushButton( KStdGuiItem::cancel(), buttons );

            labelPix ->setAlignment( TQt::AlignHCenter );
            labelName->setAlignment( TQt::AlignHCenter );
            labelPix ->setPixmap( cover );
            labelName->setText( productname );

            save->setDefault( true );
            this->setFixedSize( sizeHint() );
            this->setCaption( i18n("Cover Found") );

            connect( save,      TQT_SIGNAL(clicked()), TQT_SLOT(accept()) );
            connect( newsearch, TQT_SIGNAL(clicked()), TQT_SLOT(accept()) );
            connect( nextcover, TQT_SIGNAL(clicked()), TQT_SLOT(accept()) );
            connect( cancel,    TQT_SIGNAL(clicked()), TQT_SLOT(reject()) );
        }

        virtual void accept()
        {
            if( tqstrcmp( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name(), "NewSearch" ) == 0 )
                done( 1000 );
            else if( tqstrcmp( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name(), "NextCover" ) == 0 )
                done( 1001 );
            else
                KDialog::accept();
        }
    };


void
CoverFetcher::showCover()
{
    CoverFoundDialog dialog( TQT_TQWIDGET( parent() ), m_image, m_currentCoverName );

    switch( dialog.exec() )
    {
    case KDialog::Accepted:
        finish();
        break;
    case 1000: //showQueryEditor()
        getUserQuery();
        m_coverAmazonUrls.clear();
        m_coverAsins.clear();
        m_coverUrls.clear();
        m_coverNames.clear();
        break;
    case 1001: //nextCover()
        attemptAnotherFetch();
        break;
    default:
        finishWithError( i18n( "Aborted." ) );
        break;
    }
}


void
CoverFetcher::finish()
{
    emit result( this );

    deleteLater();
}

void
CoverFetcher::finishWithError( const TQString &message, TDEIO::Job *job )
{
    if( job )
        warning() << message << " TDEIO::error(): " << job->errorText() << endl;

    m_errors += message;
    m_success = false;

    emit result( this );

    deleteLater();
}

#include "coverfetcher.moc"
