// (c) 2004 Mark Kretschmann <markey@web.de>
// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
// (c) 2004 Sami Nieminen <sami.nieminen@iki.fi>
// (c) 2005 Ian Monroe <ian@monroe.nu>
// (c) 2005 Jeff Mitchell <kde-dev@emailgoeshere.com>
// (c) 2005 Isaiah Damron <xepo@trifault.net>
// (c) 2005 Alexandre Pereira de Oliveira <aleprj@gmail.com>
// (c) 2006 Jonas Hurrelmann <j@outpo.st>
// (c) 2006 Shane King <kde@dontletsstart.com>
// (c) 2006 Peter C. Ndikuwera <pndiku@gmail.com>
// See COPYING file for licensing information.

#ifndef AMAROK_COLLECTIONDB_H
#define AMAROK_COLLECTIONDB_H

#include "engineobserver.h"
#include "threadmanager.h" //baseclass
#include "amarok_export.h"

#include <kurl.h>
#include <tqdir.h>            //stack allocated
#include <tqdatetime.h>
#include <tqimage.h>
#include <tqmutex.h>
#include <tqobject.h>         //baseclass
#include <tqptrqueue.h>       //baseclass
#include <tqsemaphore.h>      //stack allocated
#include <tqstringlist.h>     //stack allocated
#include <tqptrvector.h>
#include <tqthread.h>
#include <tqvaluestack.h>

namespace TDEIO { class Job; }

class DbConnection;
class CoverFetcher;
class MetaBundle;
class OrganizeCollectionDialog;
class PodcastChannelBundle;
class PodcastEpisodeBundle;
class TQListViewItem;
class Scrobbler;

class DbConfig
{};


class SqliteConfig : public DbConfig
{
    public:
        SqliteConfig( const TQString& /* dbfile */ );

        TQString dbFile() const { return m_dbfile; }

    private:
        TQString m_dbfile;
};


class MySqlConfig : public DbConfig
{
    public:
        MySqlConfig(
            const TQString& /* host */,
            const int /* port */,
            const TQString& /* database */,
            const TQString& /* username */,
            const TQString& /* password */);

        TQString host() const { return m_host; }
        int port() const { return m_port; }
        TQString database() const { return m_database; }
        TQString username() const { return m_username; }
        TQString password() const { return m_password; }

    private:
        TQString m_host;
        int m_port;
        TQString m_database;
        TQString m_username;
        TQString m_password;
};


class PostgresqlConfig : public DbConfig
{
    public:
        PostgresqlConfig(
            const TQString& /* host */,
            const int /* port */,
            const TQString& /* database */,
            const TQString& /* username */,
            const TQString& /* password */);

        TQString host() const { return m_host; }
        int port() const { return m_port; }
        TQString database() const { return m_database; }
        TQString username() const { return m_username; }
        TQString password() const { return m_password; }

    private:
        TQString m_host;
        int m_port;
        TQString m_database;
        TQString m_username;
        TQString m_password;
};


class DbConnection
{
    public:
        enum DbConnectionType { sqlite = 0, mysql = 1, postgresql = 2 };

        DbConnection();
        virtual ~DbConnection() {}

        virtual TQStringList query( const TQString& /* statement */, bool suppressDebug ) = 0;
        virtual int insert( const TQString& /* statement */, const TQString& /* table */ ) = 0;
        bool isInitialized() const { return m_initialized; }
        virtual bool isConnected() const = 0;
        virtual TQString lastError() const { return "None"; }

    protected:
        bool m_initialized;
};


typedef struct sqlite3 sqlite3;
typedef struct sqlite3_context sqlite3_context;
typedef struct Mem sqlite3_value;

class SqliteConnection : public DbConnection
{
    public:
        SqliteConnection( const SqliteConfig* /* config */ );
       ~SqliteConnection();

        TQStringList query( const TQString& /* statement */, bool suppressDebug = false );
        int insert( const TQString& /* statement */, const TQString& /* table */ );
        bool isConnected()const { return true; }
    private:
        static void sqlite_rand( sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/ );
        static void sqlite_power( sqlite3_context *context, int argc, sqlite3_value **argv );
        static void sqlite_like_new( sqlite3_context *context, int argc, sqlite3_value **argv );

        sqlite3* m_db;
};


#ifdef USE_MYSQL
typedef struct MYSQL_STRUCT MYSQL;

class MySqlConnection : public DbConnection
{
    public:
        MySqlConnection( const MySqlConfig* /* config */ );
       ~MySqlConnection();

        TQStringList query( const TQString& /* statement */, bool suppressDebug = false );
        int insert( const TQString& /* statement */, const TQString& /* table */ );
        bool isConnected()const { return m_connected; }
        TQString lastError() const { return m_error; }
    private:
        void setMysqlError();
        MYSQL* m_db;
        bool m_connected;
        TQString m_error;
};
#endif


#ifdef USE_POSTGRESQL
typedef struct pg_conn PGconn;

class PostgresqlConnection : public DbConnection
{
    public:
        PostgresqlConnection( const PostgresqlConfig* /* config */ );
       ~PostgresqlConnection();

        TQStringList query( const TQString& /* statement */, bool suppressDebug = false );
        int insert( const TQString& /* statement */, const TQString& /* table */ );
        bool isConnected()const { return m_connected; }
        TQString lastError() const { return m_error; }
    private:
        void setPostgresqlError();
        PGconn* m_db;
        bool m_connected;
        TQString m_error;
};
#endif


class LIBAMAROK_EXPORT CollectionDB : public TQObject, public EngineObserver
{
    TQ_OBJECT
    

    friend class SimilarArtistsInsertionJob;

    signals:
        void scanStarted();
        void scanDone( bool changed );
        void databaseEngineChanged();

        void databaseUpdateDone();

        void scoreChanged( const TQString &url, float score );
        void ratingChanged( const TQString &url, int rating );
        void labelsChanged( const TQString &url );
        void fileMoved( const TQString &srcUrl, const TQString &dstUrl );
        void fileMoved( const TQString &srcUrl, const TQString &dstUrl, const TQString &uniqueid );
        void fileDeleted( const TQString &absPath );
        void fileDeleted( const TQString &absPath, const TQString &uniqueid );
        void fileAdded( const TQString &absPath );
        void fileAdded( const TQString &absPath, const TQString &uniqueid );
        void filesAdded( const TQMap<TQString,TQString> &map );
        void uniqueIdChanged( const TQString &url, const TQString &originalid, const TQString &newid );
        void coverChanged( const TQString &artist, const TQString &album ); //whenever a cover changes
        void coverFetched( const TQString &artist, const TQString &album ); //only when fetching from amazon
        void coverRemoved( const TQString &artist, const TQString &album );
        void coverFetcherError( const TQString &error );

        void similarArtistsFetched( const TQString &artist );
        void tagsChanged( const MetaBundle &bundle );
        void tagsChanged( const TQString &oldArtist, const TQString &oldAlbum );
        void imageFetched( const TQString &remoteURL ); //for fetching remote podcast images

        // Cross-thread communication
        void startScrobblerSimilarArtistsFetch(const TQString &);

    public:
        CollectionDB();
        ~CollectionDB();

        static CollectionDB *instance();

        /**
         * performs all initializations which require directory or URL data stored in the
         * database.
         */
        void initDirOperations();

        enum labelTypes { typeUser = 1 };           //add new types add the end!

        TQString escapeString(TQString string ) const
        {
            return
            #ifdef USE_MYSQL
                // We have to escape "\" for mysql, but can't do so for sqlite
                ( m_dbConnType == DbConnection::mysql )
                ? string.replace("\\", "\\\\").replace( '\'', "''" ) :
            #endif
                  string.replace( '\'', "''" );
        }

        TQString boolT() const { if (getDbConnectionType() == DbConnection::postgresql) return "true"; else return "1"; }
        TQString boolF() const { if (getDbConnectionType() == DbConnection::postgresql) return "false"; else return "0"; }
        inline bool boolFromSql( const TQString &b ) { return ( b == boolT() || b == "t" ); }
        //textColumnType should be used for normal strings, which need to be compared
        //either case-sensitively or -insensitively
        TQString textColumnType( int length=255 ) const { if ( getDbConnectionType() == DbConnection::postgresql ) return "TEXT"; else return TQString("VARCHAR(%1)").arg(length); }
        //exactTextColumnType should be used for strings that must be stored exactly, such
        //as URLs (necessary for holding control chars etc. if present in URL), except for
        //trailing spaces. Comparisions should always be done case-sensitively.
        //As we create indices on these columns, we have to restrict them to
        //<= 255 chars for mysql < 5.0.3
        TQString exactTextColumnType( int length=1024 ) const { if ( getDbConnectionType() == DbConnection::mysql ) return TQString( "VARBINARY(%1)" ).arg( length>255 ? 255 : length ); else return textColumnType( length ); }
        // We might consider using LONGTEXT type, as some lyrics could be VERY long..???
        TQString longTextColumnType() const { if ( getDbConnectionType() == DbConnection::postgresql ) return "TEXT"; else return "TEXT"; }
        TQString randomFunc() const { if ( getDbConnectionType() == DbConnection::postgresql ) return "random()"; else return "RAND()"; }

        inline static TQString exactCondition( const TQString &right );
        static TQString likeCondition( const TQString &right, bool anyBegin=false, bool anyEnd=false );
        int getType() { return getDbConnectionType(); }

        //sql helper methods
        TQStringList query( const TQString& statement, bool suppressDebug = false );
        int insert( const TQString& statement, const TQString& table );

        /**
         * TODO: write doc
         * @param showAll
         * @return a string which can be appended to an existing sql where statement
         */
        TQString deviceidSelection( const bool showAll = false );

        /**
         * converts the result of a query which contains a deviceid and a relative path
         * to a list of absolute paths. the order of entries in each result row must be
         * deviceid first, relative path second.
         * @param result the result of the sql query, deviceid first, relative path second
         * @return a list of urls
         */
        TQStringList URLsFromQuery( const TQStringList &result ) const;

        /**
         * converts the result list of a amarok-sql query to a list of urls
         */
        KURL::List URLsFromSqlDrag( const TQStringList &values ) const;

        //table management methods
        bool isEmpty();
        bool isValid();
        TQString adminValue( TQString noption );
        void setAdminValue( TQString noption, TQString value );
        void createTables( const bool temporary = false );
        void createIndices(  );
        void createPermanentIndices();
        void dropTables( const bool temporary = false);
        void clearTables( const bool temporary = false);
        void copyTempTables(  );
        void prepareTempTables();

        uint artistID( TQString value, bool autocreate = true, const bool temporary = false, bool exact = true );
        uint composerID( TQString value, bool autocreate = true, const bool temporary = false, bool exact = true );
        uint albumID( TQString value, bool autocreate = true, const bool temporary = false, bool exact = true );
        uint genreID( TQString value, bool autocreate = true, const bool temporary = false, bool exact = true );
        uint yearID( TQString value, bool autocreate = true, const bool temporary = false, bool exact = true );

        bool isDirInCollection( TQString path );
        bool isFileInCollection( const TQString &url );
        TQString getURL( const MetaBundle &bundle );
        void removeDirFromCollection( TQString path );
        void removeSongsInDir( TQString path, TQMap<TQString,TQString> *tagsRemoved = 0 );
        void removeSongs( const KURL::List& urls );
        void updateDirStats( TQString path, const long datetime, const bool temporary = false );

        //song methods
        bool addSong( MetaBundle* bundle, const bool incremental = false );
        void aftCheckPermanentTables( const TQString &currdeviceid, const TQString &currid, const TQString &currurl );
        void doAFTStuff( MetaBundle *bundle, const bool tempTables = true );
        void emitFileAdded( const TQString &absPath,
                            const TQString &uniqueid = TQString() );
        void emitFilesAdded( const TQMap<TQString,TQString> &map ) { emit filesAdded( map ); }
        void emitFileDeleted( const TQString &absPath,
                              const TQString &uniqueid = TQString() );
        bool newUniqueIdForFile( const TQString &path );
        bool removeUniqueIdFromFile( const TQString &path );
        TQString urlFromUniqueId( const TQString &id );
        TQString uniqueIdFromUrl( const KURL &url );

        //podcast methods
        /// Insert a podcast channel into the database.  If @param replace is true, replace the row
        /// use updatePodcastChannel() always in preference
        bool addPodcastChannel( const PodcastChannelBundle &pcb, const bool &replace=false );
        /// Insert a podcast episode into the database.  If @param idToUpdate is provided, replace the row
        /// use updatePodcastEpisode() always in preference
        int  addPodcastEpisode( const PodcastEpisodeBundle &episode, const int idToUpdate=0 );
        int  addPodcastFolder( const TQString &name, const int parent_id=0, const bool isOpen=false );
        TQValueList<PodcastChannelBundle> getPodcastChannels();
        PodcastEpisodeBundle getPodcastEpisodeById( int id );
        TQValueList<PodcastEpisodeBundle> getPodcastEpisodes( const KURL &parent, bool newOnly=false, int limit=-1 );
        void removePodcastChannel( const KURL &url ); // will remove all episodes too
        void removePodcastEpisode( const int id );
        void removePodcastFolder( const int id );
        void updatePodcastChannel( const PodcastChannelBundle &b );
        void updatePodcastEpisode( const int id, const PodcastEpisodeBundle &b );
        void updatePodcastFolder( const int folder_id, const TQString &name, const int parent_id=0, const bool isOpen=false );
        // these return false when no bundle was available
        bool getPodcastChannelBundle( const KURL &url, PodcastChannelBundle *channel );
        bool getPodcastEpisodeBundle( const KURL &url, PodcastEpisodeBundle *channel );

        MetaBundle bundleFromQuery( TQStringList::const_iterator *iter );
        /**
         * The @p bundle parameter's url() will be looked up in the Collection
         * @param bundle this will be filled in with tags for you
         * @return true if in the collection
         */
        bool bundleForUrl( MetaBundle* bundle );
        TQValueList<MetaBundle> bundlesByUrls( const KURL::List& urls );
        void addAudioproperties( const MetaBundle& bundle );

        //Helper function for updateTags
        void deleteRedundantName( const TQString &table, const TQString &id );

        void deleteAllRedundant( const TQString &table );

        void updateTags( const TQString &url, const MetaBundle &bundle, const bool updateView = true);
        void updateURL( const TQString &url, const bool updateView = true );
        TQString getUniqueId( const TQString &url );

        //statistics methods
        void addSongPercentage( const TQString &url, float percentage,
                const TQString &reason, const TQDateTime *playtime = 0 );
        float getSongPercentage( const TQString &url );
        int getSongRating( const TQString &url );
        void setSongPercentage( const TQString &url, float percentage );
        void setSongRating( const TQString &url, int percentage, bool toggleHalf = false );
        int getPlayCount( const TQString &url );
        TQDateTime getFirstPlay( const TQString &url );
        TQDateTime getLastPlay( const TQString &url );
        void migrateFile( const TQString &oldURL, const TQString &newURL );
        bool moveFile( const TQString &src, const TQString &dest, bool overwrite, bool copy = false );
        bool organizeFile( const KURL &src, OrganizeCollectionDialog &dialog, bool copy );

        //artist methods
        TQStringList similarArtists( const TQString &artist, uint count );

        //album methods
        void checkCompilations( const TQString &path, const bool temporary = false );
        void setCompilation( const KURL::List &urls, bool enabled, bool updateView );
        TQString albumSongCount( const TQString &artist_id, const TQString &album_id );
        bool albumIsCompilation( const TQString &album_id );
        void sanitizeCompilations();

        //label methods
        TQStringList getLabels( const TQString &url, const uint type );
        void removeLabels( const TQString &url, const TQStringList &labels, const uint type );
        bool addLabel( const TQString &url, const TQString &label, const TQString &uid, const uint type );
        void setLabels( const TQString &url, const TQStringList &labels, const TQString &uid, const uint type );

        void cleanLabels();

        TQStringList favoriteLabels( int type = CollectionDB::typeUser, int count = 10 );

        //list methods
        TQStringList artistList( bool withUnknowns = true, bool withCompilations = true );
        TQStringList composerList( bool withUnknowns = true, bool withCompilations = true );
        TQStringList albumList( bool withUnknowns = true, bool withCompilations = true );
        TQStringList genreList( bool withUnknowns = true, bool withCompilations = true );
        TQStringList yearList( bool withUnknowns = true, bool withCompilations = true );
        TQStringList labelList();

        TQStringList albumListOfArtist( const TQString &artist, bool withUnknown = true, bool withCompilations = true );
        TQStringList artistAlbumList( bool withUnknown = true, bool withCompilations = true );

        TQStringList albumTracks( const TQString &artist_id, const TQString &album_id );
        TQStringList albumDiscTracks( const TQString &artist_id, const TQString &album_id, const TQString &discNumber );
        TQStringList artistTracks( const TQString &artist_id );

        //cover management methods
        /** Returns the image from a given URL, network-transparently.
         * You must run TDEIO::NetAccess::removeTempFile( tmpFile ) when you are finished using the image;
         **/
        static TQImage fetchImage( const KURL& url, TQString &tmpFile );
        /** Saves images located on the user's filesystem */
        bool setAlbumImage( const TQString& artist, const TQString& album, const KURL& url );
        /** Saves images obtained from CoverFetcher */
        bool setAlbumImage( const TQString& artist, const TQString& album, TQImage img, const TQString& amazonUrl = TQString(), const TQString& asin = TQString() );

        TQString findAmazonImage( const TQString &artist, const TQString &album, const uint width = 1 );
        TQString findDirectoryImage( const TQString& artist, const TQString& album, uint width = 0 );
        TQString findEmbeddedImage( const TQString& artist, const TQString& album, uint width = 1 );
        TQString findMetaBundleImage( const MetaBundle &trackInformation, const uint = 1 );

        /// ensure the sql only return urls to tracks for efficiency
        static TQPixmap createDragPixmapFromSQL( const TQString &sql, TQString textOverRide=TQString() );
        static TQPixmap createDragPixmap( const KURL::List &urls, TQString textOverRide=TQString() );
        static const int DRAGPIXMAP_OFFSET_X = -12;
        static const int DRAGPIXMAP_OFFSET_Y = -28;

        /*
         * Retrieves the path to the local copy of the image pointed to by url,
         * initiates fetching of the remote image if necessary.
         * @param width the size of the image. 0 == full size, 1 == preview size
         */
        TQString podcastImage( const MetaBundle &bundle, const bool withShadow = false, uint width = 1 );
        TQString podcastImage( const TQString &remoteURL, const bool withShadow = false, uint width = 1 );

        /**
         * Retrieves the path to the image for the album of the requested item
         * @param width the size of the image. 0 == full size, 1 == preview size
         * @param embedded if not NULL, sets a bool indicating whether the path is an embedded image
         */
        TQString albumImage( const MetaBundle &trackInformation, const bool withShadow = false, uint width = 1, bool* embedded = 0 );
        TQString albumImage( const uint artist_id, const uint album_id, const bool withShadow = false, uint width = 1, bool* embedded = 0 );
        TQString albumImage( const TQString &artist, const TQString &album, const bool withShadow = false, uint width = 1, bool* embedded = 0 );
        TQMap<TQListViewItem*, CoverFetcher*> * getItemCoverMap() { return itemCoverMap; }
        TQMutex * getItemCoverMapMutex() { return itemCoverMapMutex; }

        bool removeAlbumImage( const uint artist_id, const uint album_id );
        bool removeAlbumImage( const TQString &artist, const TQString &album );

        static TQString makeShadowedImage( const TQString& albumImage, bool cache = true );

        //local cover methods
        void addImageToAlbum( const TQString& image, TQValueList< TQPair<TQString, TQString> > info, const bool temporary );
        TQString notAvailCover( const bool withShadow = false, int width = 1 );

        //embedded cover methods
        void addEmbeddedImage( const TQString& path, const TQString& hash, const TQString& description );
        void removeOrphanedEmbeddedImages();

        void applySettings();

        void setLyrics( const TQString& url, const TQString& lyrics, const TQString &uniqueid = TQString() );
        TQString getLyrics( const TQString& url );

        /** Remove from the amazon table the item with the specified md5sum **/
        void removeInvalidAmazonInfo( const TQString& md5sum );
        void newAmazonReloadDate( const TQString& asin, const TQString& locale, const TQString& md5sum );
        TQStringList staleImages();

        DbConnection::DbConnectionType getDbConnectionType() const { return m_dbConnType; }
        bool isConnected();
        void releasePreviousConnection(TQThread *currThread);

        void invalidateArtistAlbumCache() { m_validArtistCache=false; m_validComposerCache=false; m_validAlbumCache=false; };

        void vacuum();

        /**
        * Cancel the underlying move/copy file action
        */
        void cancelMovingFileJob();

    protected:
        TQCString md5sum( const TQString& artist, const TQString& album, const TQString& file = TQString() );
        void engineTrackEnded( int finalPosition, int trackLength, const TQString &reason );
        /** Manages regular folder monitoring scan */
        void timerEvent( TQTimerEvent* e );

    public slots:
        void fetchCover( TQWidget* parent, const TQString& artist, const TQString& album, bool noedit, TQListViewItem* item = 0 );
        void scanMonitor();
        void startScan();
        void stopScan();
        void scanModifiedDirs();
        void disableAutoScoring( bool disable = true ) { m_autoScoring = !disable; }

        void checkDatabase();

    private slots:
        void dirDirty( const TQString& path );
        void coverFetcherResult( CoverFetcher* );
        void similarArtistsFetched( const TQString& artist, const TQStringList& suggestions );
        void fileOperationResult( TDEIO::Job *job ); // moveFile depends on it
        void podcastImageResult( TDEIO::Job *job ); //for fetching remote podcast images
        void aftMigratePermanentTablesUrl( const TQString& oldUrl, const TQString& newUrl, const TQString& uniqueid ); //AFT-enable stats
        void aftMigratePermanentTablesUniqueId( const TQString& url, const TQString& oldid, const TQString& newid );

    private:
        //bump DATABASE_VERSION whenever changes to the table structure are made.
        // This erases tags, album, artist, composer, genre, year, images, embed, directory and related_artists tables.
        static const int DATABASE_VERSION = 35;
        // Persistent Tables hold data that is somehow valuable to the user, and can't be erased when rescaning.
        // When bumping this, write code to convert the data!
        static const int DATABASE_PERSISTENT_TABLES_VERSION = 19;
        // Bumping this erases stats table. If you ever need to, write code to convert the data!
        static const int DATABASE_STATS_VERSION = 12;
        // When bumping this, you should provide code to convert the data.
        static const int DATABASE_PODCAST_TABLES_VERSION = 2;
        static const int DATABASE_AFT_VERSION = 2;
        // persistent table. you should provide code to convert the data when bumping this
        static const int DATABASE_DEVICES_VERSION = 1;

        static const int MONITOR_INTERVAL = 60; //sec

        static TQDir largeCoverDir();
        static TQDir tagCoverDir();
        static TQDir cacheCoverDir();

        void initialize();
        void destroy();
        DbConnection* getMyConnection();

        //helper methods which perform updates of amarok's database
        void updateStatsTables();
        void updatePersistentTables();
        void updatePodcastTables();

        //A dirty hack to preserve Group By settings in Collection Browser after addition
        //of Composer table
        void updateGroupBy();

        void customEvent( TQCustomEvent * );

        // helpers for embedded images
        TQString loadHashFile( const TQCString& hash, uint width );
        bool extractEmbeddedImage( const MetaBundle &trackInformation, TQCString& hash );

        //general management methods
        void createStatsTable();
        void dropStatsTable();
        void createPersistentTables();
        void dropPersistentTables();
        void createPodcastTables();
        void dropPodcastTables();
        void createDevicesTable();
        void dropDevicesTable();

        //Archived forms of the above. useful for providing a linear upgrade routine that
        //stays the same
        void createStatsTableV8();
        void createStatsTableV10( bool temp );
        void dropStatsTableV1();
        void createPersistentTablesV12();
        void createPersistentTablesV14( bool temp );
        void dropPersistentTablesV14();
        void createPodcastTablesV2( bool temp );
        void dropPodcastTablesV2();


        TQCString makeWidthKey( uint width );
        TQString artistValue( uint id );
        TQString composerValue( uint id );
        TQString albumValue( uint id );
        TQString genreValue( uint id );
        TQString yearValue( uint id );

        //These should be avoided as they will be slow and potentially unsafe.
        //Use the Exact version where possible (faster and safer).
        //To convert output from Exact version from TQString to uint, use .toUInt()
        uint IDFromValue( TQString name, TQString value, bool autocreate = true, const bool temporary = false );
        TQString IDFromExactValue(  TQString table, TQString value, bool autocreate = true, bool temporary = false );
        TQString valueFromID( TQString table, uint id );

        //member variables
        TQString m_amazonLicense;
        bool    m_validArtistCache;
        bool    m_validComposerCache;
        bool    m_validAlbumCache;
        TQString m_cacheArtist[2];
        uint    m_cacheArtistID[2];
        TQString m_cacheComposer[2];
        uint    m_cacheComposerID[2];
        TQString m_cacheAlbum[2];
        uint    m_cacheAlbumID[2];

        bool m_monitor;
        bool m_autoScoring;

        static TQMap<TQListViewItem*, CoverFetcher*> *itemCoverMap;
        static TQMutex *itemCoverMapMutex;
        TQImage m_noCover, m_shadowImage;

        static TQMap<TQThread *, DbConnection *> *threadConnections;
        static TQMutex *connectionMutex;
        DbConnection::DbConnectionType m_dbConnType;
        DbConfig *m_dbConfig;

        //organize files stuff
        bool m_waitForFileOperation;
        bool m_fileOperationFailed;
        bool m_scanInProgress;
        bool m_rescanRequired;

        TQStringList m_aftEnabledPersistentTables;

        // Cancel move/copy job
        bool m_moveFileJobCancelled;

        // for handling podcast image url redirects
        TQMap<TDEIO::Job *, TQString> m_podcastImageJobs;

        // protect against multiple simultaneous queries/inserts
        TQMutex m_mutex;
};

#ifdef Q_MOC_RUN
// MOC_SKIP_BEGIN
class INotify : public JobBase
// MOC_SKIP_END
#else // Q_MOC_RUN
class INotify : public ThreadManager::DependentJob
#endif // Q_MOC_RUN
{
    TQ_OBJECT
    

    public:
        INotify( CollectionDB *parent, int fd );
        ~INotify();

        static INotify *instance() { return s_instance; }

        bool watchDir( const TQString directory );
        int fd() { return m_fd; }

    private:
        virtual bool doJob();

        CollectionDB* m_parent;
        int m_fd;

        static INotify* s_instance;
};


class QueryBuilder
{
    public:
        //attributes:
        enum qBuilderTables  { tabAlbum = 1, tabArtist = 2, tabComposer = 4, tabGenre = 8, tabYear = 16, tabSong = 64,
                               tabStats = 128, tabLyrics = 256, tabPodcastChannels = 512,
                               tabPodcastEpisodes = 1024, tabPodcastFolders = 2048,
                               tabDevices = 4096, tabLabels = 8192
                               /* dummy table for filtering */, tabDummy = 0 };
        enum qBuilderOptions { optNoCompilations = 1, optOnlyCompilations = 2, optRemoveDuplicates = 4,
                               optRandomize = 8,
                               optShowAll = 16 /* get all songs, not just mounted ones */ };
        /* This has been an enum in the past, but 32 bits wasn't enough anymore :-( */
        static const TQ_INT64 valDummy         = 0;
        static const TQ_INT64 valID            = 1LL << 0;
        static const TQ_INT64 valName          = 1LL << 1;
        static const TQ_INT64 valURL           = 1LL << 2;
        static const TQ_INT64 valTitle         = 1LL << 3;
        static const TQ_INT64 valTrack         = 1LL << 4;
        static const TQ_INT64 valScore         = 1LL << 5;
        static const TQ_INT64 valComment       = 1LL << 6;
        static const TQ_INT64 valBitrate       = 1LL << 7;
        static const TQ_INT64 valLength        = 1LL << 8;
        static const TQ_INT64 valSamplerate    = 1LL << 9;
        static const TQ_INT64 valPlayCounter   = 1LL << 10;
        static const TQ_INT64 valCreateDate    = 1LL << 11;
        static const TQ_INT64 valAccessDate    = 1LL << 12;
        //static const TQ_INT64 valPercentage    = 1LL << 13; // same as valScore
        static const TQ_INT64 valArtistID      = 1LL << 14;
        static const TQ_INT64 valAlbumID       = 1LL << 15;
        static const TQ_INT64 valYearID        = 1LL << 16;
        static const TQ_INT64 valGenreID       = 1LL << 17;
        static const TQ_INT64 valDirectory     = 1LL << 18;
        static const TQ_INT64 valLyrics        = 1LL << 19;
        static const TQ_INT64 valRating        = 1LL << 20;
        static const TQ_INT64 valComposerID    = 1LL << 21;
        static const TQ_INT64 valDiscNumber    = 1LL << 22;
        static const TQ_INT64 valFilesize      = 1LL << 23;
        static const TQ_INT64 valFileType      = 1LL << 24;
        static const TQ_INT64 valIsCompilation = 1LL << 25;
        static const TQ_INT64 valBPM           = 1LL << 26;
        // podcast relevant:
        static const TQ_INT64 valCopyright     = 1LL << 27;
        static const TQ_INT64 valParent        = 1LL << 28;
        static const TQ_INT64 valWeblink       = 1LL << 29;
        static const TQ_INT64 valAutoscan      = 1LL << 30;
        static const TQ_INT64 valFetchtype     = 1LL << 31;
        static const TQ_INT64 valAutotransfer  = 1LL << 32;
        static const TQ_INT64 valPurge         = 1LL << 33;
        static const TQ_INT64 valPurgeCount    = 1LL << 34;
        static const TQ_INT64 valIsNew         = 1LL << 35;
        // dynamic collection relevant:
        static const TQ_INT64 valDeviceId      = 1LL << 36;
        static const TQ_INT64 valRelativePath  = 1LL << 37;
        static const TQ_INT64 valDeviceLabel   = 1LL << 38;
        static const TQ_INT64 valMountPoint    = 1LL << 39;
        //label relevant
        static const TQ_INT64 valType         = 1LL << 40;

        static TQ_INT64 valForFavoriteSorting();
        void sortByFavorite();

        // sortByFavoriteAvg() add the average rating, if enabled, the average score, if enabled,
        // and the average playcounter as return values!
        void sortByFavoriteAvg();

        enum qBuilderFunctions  { funcNone = 0, funcCount = 1, funcMax = 2, funcMin = 4, funcAvg = 8, funcSum = 16 };

        // Note: modes beginMatch, endMatch are only supported for string filters
        // Likewise, modes between and notBetween are only supported for numeric filters
        enum qBuilderFilter  { modeNormal = 0, modeLess = 1, modeGreater = 2, modeEndMatch = 3, modeBeginMatch = 4, modeBetween = 5, modeNotBetween = 6};

        LIBAMAROK_EXPORT QueryBuilder();

        LIBAMAROK_EXPORT void addReturnValue( int table, TQ_INT64 value, bool caseSensitive = false /* unless value refers to a string */ );
        LIBAMAROK_EXPORT void addReturnFunctionValue( int function, int table, TQ_INT64 value);
        uint countReturnValues();

        // Note: the filter chain begins in AND mode
        void beginOR(); //filters will be ORed instead of ANDed
        void endOR();   //don't forget to end it!
        void beginAND(); // These do the opposite; for recursive and/or
        void endAND();

        void setGoogleFilter( int defaultTables, TQString query );

        void addURLFilters( const TQStringList& filter );

        void addFilter( int tables, const TQString& filter);
        void addFilter( int tables, TQ_INT64 value, const TQString& filter, int mode = modeNormal, bool exact = false );
        void addFilters( int tables, const TQStringList& filter );
        void excludeFilter( int tables, const TQString& filter );
        void excludeFilter( int tables, TQ_INT64 value, const TQString& filter, int mode = modeNormal, bool exact = false );

        void addMatch( int tables, const TQString& match, bool interpretUnknown = true, bool caseSensitive = true );
        LIBAMAROK_EXPORT void addMatch( int tables, TQ_INT64 value, const TQString& match, bool interpretUnknown = true, bool caseSensitive = true );
        void addMatches( int tables, const TQStringList& match, bool interpretUnknown = true, bool caseSensitive = true );
        void excludeMatch( int tables, const TQString& match );
        void having( int table, TQ_INT64 value, int function, int mode, const TQString& match );

        void exclusiveFilter( int tableMatching, int tableNotMatching, TQ_INT64 value );

        // For numeric filters:
        // modeNormal means strict equality; modeBeginMatch and modeEndMatch are not
        // allowed; modeBetween needs a second value endRange
        void addNumericFilter(int tables, TQ_INT64 value, const TQString &n,
                              int mode = modeNormal,
                              const TQString &endRange = TQString());

        void setOptions( int options );
        void sortBy( int table, TQ_INT64 value, bool descending = false );
        void sortByFunction( int function, int table, TQ_INT64 value, bool descending = false );
        void groupBy( int table, TQ_INT64 value );
        void setLimit( int startPos, int length );

        // Returns the results in random order.
        // If a \p table and \p value are specified, uses weighted random order on
        // that field.
        // The shuffle is cumulative with other sorts, but any sorts after this are
        // pointless because of the precision of the random function.
        void shuffle( int table = 0, TQ_INT64 value = 0 );

        static const int dragFieldCount;
        static TQString dragSQLFields();
        void initSQLDrag();

        void buildQuery( bool withDeviceidPlaceholder = false );
        TQString getQuery();
        //use withDeviceidPlaceholder = false if the query isn't run immediately (*CurrentTimeT*)
        //and replace (*MountedDeviceSelection*) with CollectionDB::instance()->deviceIdSelection()
        TQString query( bool withDeviceidPlaceholder = false ) { buildQuery( withDeviceidPlaceholder ); return m_query; };
        void clear();

        LIBAMAROK_EXPORT TQStringList run();

        // Transform a string table.value "field" into enum values
        // @return true if we succeeded
        bool getField(const TQString &tableValue, int *table, TQ_INT64 *value);

    private:
        TQString tableName( int table );
        const TQString &valueName( TQ_INT64 value );
        TQString functionName( int functions );
        bool coalesceField( int table, TQ_INT64 value );

        int getTableByName(const TQString &name);
        TQ_INT64 getValueByName(const TQString &field);

        TQStringList cleanURL( TQStringList result );

        void linkTables( int tables );

        TQValueStack<bool> m_OR;
        bool m_showAll;
        uint m_deviceidPos;

        TQString ANDslashOR() const;

        TQString m_query;
        TQString m_values;
        TQString m_tables;
        TQString m_join;
        TQString m_where;
        TQString m_sort;
        TQString m_group;
        TQString m_limit;
        TQString m_having;

        TQString m_url;      //url is used as primary key and linkTables needs to do some special stuff with it

        int m_linkTables;
        uint m_returnValues;
};

inline void QueryBuilder::beginOR()
{
    m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
    m_OR.push(true);
}
inline void QueryBuilder::endOR()
{
    m_where += " ) ";
    m_OR.pop();
}
inline void QueryBuilder::beginAND()
{
    m_where += ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' ';
    m_OR.push(false);
}
inline void QueryBuilder::endAND()
{
    m_where += " ) ";
    m_OR.pop();
}
inline TQString QueryBuilder::ANDslashOR() const { return m_OR.top() ? "OR" : "AND"; }


#endif /* AMAROK_COLLECTIONDB_H */
