// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
// (c) 2004 Sami Nieminen <sami.nieminen@iki.fi>
// (c) 2006 Shane King <kde@dontletsstart.com>
// (c) 2006 Iain Benson <iain@arctos.me.uk>
// See COPYING file for licensing information

#ifndef AMAROK_SCROBBLER_H
#define AMAROK_SCROBBLER_H

#include "engineobserver.h"
#include <tqdom.h>
#include <tqobject.h>
#include <tqptrdict.h>
#include <tqptrlist.h>
#include <tqtimer.h>

//some setups require this
#undef PROTOCOL_VERSION

namespace TDEIO { class Job; }

class TQStringList;
class SubmitItem;
class SubmitQueue;
class ScrobblerSubmitter;

class Scrobbler : public TQObject, public EngineObserver
{
    friend class MediaDevice;

    Q_OBJECT
  

    public:
        static Scrobbler *instance();

        void applySettings();

    signals:
        void similarArtistsFetched( const TQString& artist, const TQStringList& suggestions );

    public slots:
        void subTrack( long currentPos, long startPos, long endPos ); // cuefiles can update length without track change
        void similarArtists( const TQString & /*artist*/ );

    protected:
        Scrobbler();
        ~Scrobbler();

        void engineNewMetaData( const MetaBundle& /*bundle*/, bool /*state*/ );
        void engineTrackPositionChanged( long /*position*/ , bool /*userSeek*/ );

    private slots:
        void audioScrobblerSimilarArtistsResult( TDEIO::Job* /*job*/ );
        void audioScrobblerSimilarArtistsData(
            TDEIO::Job* /*job*/, const TQByteArray& /*data*/ );

    private:
        TQTimer m_timer; //works around xine bug
                        //http://sourceforge.net/tracker/index.php?func=detail&aid=1401026&group_id=9655&atid=109655
        TQByteArray m_similarArtistsBuffer;
        TDEIO::Job* m_similarArtistsJob;
        TQString m_artist;
        bool m_validForSending;
        long m_startPos;
        ScrobblerSubmitter* m_submitter;
        SubmitItem* m_item;
};


class SubmitItem
{
    friend class ScrobblerSubmitter;

    public:
        SubmitItem(
            const TQString& /*artist*/,
            const TQString& /*album*/,
            const TQString& /*title*/,
            int /*length*/,
            bool now = true );
        SubmitItem( const TQDomElement& /* domElement */ );
        SubmitItem();

        bool operator==( const SubmitItem& item );

        void setArtist( const TQString& artist ) { m_artist = artist; }
        void setAlbum( const TQString& album ) { m_album = album; }
        void setTitle( const TQString& title ) { m_title = title; }
        const TQString artist() const { return m_artist; }
        const TQString album() const { return m_album; }
        const TQString title() const { return m_title; }
        int length() const { return m_length; }
        uint playStartTime() const { return m_playStartTime; }

        TQDomElement toDomElement( TQDomDocument& /* document */ ) const;

        bool valid() const { return !m_artist.isEmpty() && !m_title.isEmpty() && m_length >= 30; }

    private:
        TQString m_artist;
        TQString m_album;
        TQString m_title;
        int m_length;
        uint m_playStartTime;
};


class SubmitQueue : public TQPtrList<SubmitItem>
{
    protected:
        int compareItems( TQPtrCollection::Item item1, TQPtrCollection::Item item2 );
};


class ScrobblerSubmitter : public TQObject
{
    Q_OBJECT
  

    public:
        static TQString PROTOCOL_VERSION;
        static TQString CLIENT_ID;
        static TQString CLIENT_VERSION;
        static TQString HANDSHAKE_URL;

        ScrobblerSubmitter();
        ~ScrobblerSubmitter();

        void submitItem( SubmitItem* /* item */ );

        void configure( const TQString& /*username*/, const TQString& /* password*/, bool /*enabled*/ );

        void syncComplete();

    private slots:
        void scheduledTimeReached();
        void audioScrobblerHandshakeResult( TDEIO::Job* /*job*/ );
        void audioScrobblerSubmitResult( TDEIO::Job* /*job*/ );
        void audioScrobblerSubmitData(
            TDEIO::Job* /*job*/, const TQByteArray& /*data*/ );

    private:
        bool canSubmit() const;
        void enqueueItem( SubmitItem* /* item */ );
        SubmitItem* dequeueItem();
        void enqueueJob( TDEIO::Job* /* job */ );
        void finishJob( TDEIO::Job* /* job */ );
        void announceSubmit(
            SubmitItem* /* item */, int /* tracks */, bool /* success */ ) const;
        void saveSubmitQueue();
        void readSubmitQueue();
        bool schedule( bool failure );
        void performHandshake();
        void performSubmit();

        // on failure, start at MIN_BACKOFF, and double on subsequent failures
        // until MAX_BACKOFF is reached
        static const int MIN_BACKOFF = 60;
        static const int MAX_BACKOFF = 60 * 60;

        TQString m_submitResultBuffer;
        TQString m_username;
        TQString m_password;
        TQString m_submitUrl;
        TQString m_challenge;
        TQString m_savePath;
        bool m_scrobblerEnabled;
        bool m_holdFakeQueue;
        bool m_inProgress;
        bool m_needHandshake;
        uint m_prevSubmitTime;
        uint m_interval;
        uint m_backoff;
        uint m_lastSubmissionFinishTime;
        uint m_fakeQueueLength;

        TQPtrDict<SubmitItem> m_ongoingSubmits;
        SubmitQueue m_submitQueue; // songs played by Amarok
        SubmitQueue m_fakeQueue; // songs for which play times have to be faked (e.g. when submitting from media device)

        TQTimer m_timer;
};


#endif /* AMAROK_SCROBBLER_H */
