/*
 * Cantata
 *
 * Copyright (c) 2011-2014 Craig Drummond <craig.p.drummond@gmail.com>
 *
 */
/*
 * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and
 *                    Roeland Douma (roeland AT rullzer DOT com)
 *
 * This file is part of QtMPC.
 *
 * QtMPC 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.
 *
 * QtMPC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with QtMPC.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "musiclibraryitemalbum.h"
#include "musiclibraryitemartist.h"
#include "musiclibraryitemsong.h"
#include "musiclibraryitemroot.h"
#include "musiclibrarymodel.h"
#include "albumsmodel.h"
#include "playqueuemodel.h"
#include "settings.h"
#include "config.h"
#include "covers.h"
#include "itemview.h"
#include "mpdparseutils.h"
#include "mpdconnection.h"
#include "localize.h"
#include "utils.h"
#include "icons.h"
#include "stdactions.h"
#include "qtiocompressor/qtiocompressor.h"
#include <QCommonStyle>
#include <QFile>
#include <QTimer>
#include <QStringRef>
#include <QDateTime>
#include <QDir>
#include <QMimeData>
#include <QStringList>
#ifdef ENABLE_KDE_SUPPORT
#include <KDE/KGlobal>
#endif

#if defined ENABLE_MODEL_TEST
#include "modeltest.h"
#endif

#ifdef ENABLE_KDE_SUPPORT
K_GLOBAL_STATIC(MusicLibraryModel, instance)
#endif

MusicLibraryModel * MusicLibraryModel::self()
{
    #ifdef ENABLE_KDE_SUPPORT
    return instance;
    #else
    static MusicLibraryModel *instance=0;
    if(!instance) {
        instance=new MusicLibraryModel;
        #if defined ENABLE_MODEL_TEST
        new ModelTest(instance, instance);
        #endif
    }
    return instance;
    #endif
}

const QLatin1String MusicLibraryModel::constLibraryCache("library/");
const QLatin1String MusicLibraryModel::constLibraryExt(".xml");
const QLatin1String MusicLibraryModel::constLibraryCompressedExt(".xml.gz");

static QString cacheFileName(const MPDConnectionDetails &details, bool withPort=true)
{
    QString fileName=(withPort && !details.isLocal() ? details.hostname+'_'+QString::number(details.port) : details.hostname)
                     +MusicLibraryModel::constLibraryCompressedExt;
    fileName.replace('/', '_');
    fileName.replace('~', '_');
    return Utils::cacheDir(MusicLibraryModel::constLibraryCache)+fileName;
}

static QString cacheFileName(bool withPort=true)
{
    return cacheFileName(MPDConnection::self()->getDetails(), withPort);
}

void MusicLibraryModel::convertCache(const QString &compressedName)
{
    QString prev=compressedName;
    prev.replace(constLibraryCompressedExt, constLibraryExt);

    if (QFile::exists(prev) && !QFile::exists(compressedName)) {
        QFile old(prev);
        if (old.open(QIODevice::ReadOnly)) {
            QByteArray a=old.readAll();
            old.close();

            QFile newCache(compressedName);
            QtIOCompressor compressor(&newCache);
            compressor.setStreamFormat(QtIOCompressor::GzipFormat);
            if (compressor.open(QIODevice::WriteOnly)) {
                compressor.write(a);
                compressor.close();
                QFile::remove(prev);
            }
        }
    }
}

//void MusicLibraryModel::cleanCache()
//{
//    QSet<QString> existing;
//    QList<MPDConnectionDetails> connections=Settings::self()->allConnections();
//    foreach (const MPDConnectionDetails &conn, connections) {
//        QString withPort=cacheFileName(conn);
//        QString withoutPort=cacheFileName(conn, false);
//        if (withPort!=withoutPort) {
//            existing.insert(withoutPort);
//        }
//        existing.insert(withPort);
//    }
//    QDir dir(Utils::cacheDir(constLibraryCache));
//    QFileInfoList files=dir.entryInfoList(QStringList() << "*"+constLibraryExt << "*"+constLibraryCompressedExt, QDir::Files);
//    foreach (const QFileInfo &file, files) {
//        if (!existing.contains(file.fileName())) {
//            QFile::remove(file.absoluteFilePath());
//        }
//    }
//}

MusicLibraryModel::MusicLibraryModel(QObject *parent, bool isMpdModel, bool isCheckable)
    : MusicModel(parent)
    , mpdModel(isMpdModel)
    , checkable(isCheckable)
    , rootItem(new MusicLibraryItemRoot)
    , databaseTimeUnreliable(false)
{
    if (mpdModel)
    {
        connect(Covers::self(), SIGNAL(artistImage(const Song &, const QImage &, const QString &)),
                this, SLOT(setArtistImage(const Song &, const QImage &)));
        connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)),
                this, SLOT(setCover(const Song &, const QImage &, const QString &)));
        connect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)),
                this, SLOT(updateCover(const Song &, const QImage &, const QString &)));
        connect(MPDConnection::self(), SIGNAL(updatingDatabase()), this, SLOT(updatingMpd()));
        connect(MPDConnection::self(), SIGNAL(musicLibraryUpdated(MusicLibraryItemRoot *, QDateTime)),
                this, SLOT(updateMusicLibrary(MusicLibraryItemRoot *, QDateTime)));
    }
    rootItem->setModel(this);
}

MusicLibraryModel::~MusicLibraryModel()
{
    delete rootItem;
}

QModelIndex MusicLibraryModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent)) {
        return QModelIndex();
    }

    const MusicLibraryItem * parentItem;

    if (!parent.isValid()) {
        parentItem = rootItem;
    } else {
        parentItem = static_cast<MusicLibraryItem *>(parent.internalPointer());
    }

    MusicLibraryItem * const childItem = parentItem->childItem(row);
    if (childItem) {
        return createIndex(row, column, childItem);
    }

    return QModelIndex();
}

QModelIndex MusicLibraryModel::parent(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QModelIndex();
    }

    const MusicLibraryItem * const childItem = static_cast<MusicLibraryItem *>(index.internalPointer());
    MusicLibraryItem * const parentItem = childItem->parentItem();

    if (parentItem == rootItem) {
        return QModelIndex();
    }

    return createIndex(parentItem->row(), 0, parentItem);
}

int MusicLibraryModel::rowCount(const QModelIndex &parent) const
{
    if (parent.column() > 0) {
        return 0;
    }

    const MusicLibraryItem *parentItem;

    if (!parent.isValid()) {
        parentItem = rootItem;
    } else {
        parentItem = static_cast<MusicLibraryItem *>(parent.internalPointer());
    }

    return parentItem->childCount();
}

QVariant MusicLibraryModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    if (checkable && Qt::CheckStateRole==role) {
        return static_cast<MusicLibraryItem *>(index.internalPointer())->checkState();
    }
    return MusicModel::data(index, role);
}

bool MusicLibraryModel::setData(const QModelIndex &idx, const QVariant &value, int role)
{
    if (checkable && Qt::CheckStateRole==role) {
        if (!idx.isValid()) {
            return false;
        }

        MusicLibraryItem *item = static_cast<MusicLibraryItem *>(idx.internalPointer());
        Qt::CheckState check=value.toBool() ? Qt::Checked : Qt::Unchecked;

        if (item->checkState()==check) {
            return false;
        }

        switch (item->itemType()) {
        case MusicLibraryItem::Type_Podcast:
            break;
        case MusicLibraryItem::Type_Artist: {
            MusicLibraryItemArtist *artistItem=static_cast<MusicLibraryItemArtist *>(item);
            QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex());
            item->setCheckState(check);
            foreach (MusicLibraryItem *album, artistItem->childItems()) {
                if (check!=album->checkState()) {
                    MusicLibraryItemAlbum *albumItem=static_cast<MusicLibraryItemAlbum *>(album);
                    QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex);
                    album->setCheckState(check);
                    foreach (MusicLibraryItem *song, albumItem->childItems()) {
                        song->setCheckState(check);
                    }
                    emit dataChanged(index(0, 0, albumIndex), index(0, albumItem->childCount(), albumIndex));
                }
                emit dataChanged(index(0, 0, artistIndex), index(0, artistItem->childCount(), artistIndex));
            }
            emit dataChanged(idx, idx);
            break;
        }
        case MusicLibraryItem::Type_Album: {
            MusicLibraryItemArtist *artistItem=static_cast<MusicLibraryItemArtist *>(item->parentItem());
            MusicLibraryItemAlbum *albumItem=static_cast<MusicLibraryItemAlbum *>(item);
            QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex());
            item->setCheckState(check);
            foreach (MusicLibraryItem *song, albumItem->childItems()) {
                song->setCheckState(check);
            }
            setParentState(artistIndex);
            emit dataChanged(idx, idx);
            break;
        }
        case MusicLibraryItem::Type_Song: {
            item->setCheckState(check);
            MusicLibraryItemAlbum *albumItem=static_cast<MusicLibraryItemAlbum *>(item->parentItem());
            MusicLibraryItemArtist *artistItem=static_cast<MusicLibraryItemArtist *>(albumItem->parentItem());
            QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex());
            QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex);
            setParentState(albumIndex);
            setParentState(artistIndex);
            emit dataChanged(idx, idx);
            break;
        }
        case MusicLibraryItem::Type_Root:
            return false;
        }

        return true;
    }
    return ActionModel::setData(idx, value, role);
}

void MusicLibraryModel::setParentState(const QModelIndex &parent)
{
    MusicLibraryItemContainer *parentItem=static_cast<MusicLibraryItemContainer *>(parent.internalPointer());
    Qt::CheckState parentCheck=parentItem->checkState();
    bool haveCheckedChildren=false;
    bool haveUncheckedChildren=false;
    bool stop=false;

    foreach (MusicLibraryItem *child, parentItem->childItems()) {
        switch (child->checkState()) {
        case Qt::PartiallyChecked:
            parentCheck=Qt::PartiallyChecked;
            stop=true;
            break;
        case Qt::Unchecked:
            haveUncheckedChildren=true;
            parentCheck=haveCheckedChildren ? Qt::PartiallyChecked : Qt::Unchecked;
            stop=haveCheckedChildren && haveUncheckedChildren;
            break;
        case Qt::Checked:
            haveCheckedChildren=true;
            parentCheck=haveUncheckedChildren ? Qt::PartiallyChecked : Qt::Checked;
            stop=haveCheckedChildren && haveUncheckedChildren;
            break;
        }
        if (stop) {
            break;
        }
    }

    if (parentItem->checkState()!=parentCheck) {
        parentItem->setCheckState(parentCheck);
        emit dataChanged(parent, parent);
    }
}

void MusicLibraryModel::clear()
{
    const MusicLibraryItemRoot *oldRoot = rootItem;
    beginResetModel();
    databaseTime = QDateTime();
    rootItem = new MusicLibraryItemRoot;
    rootItem->setModel(this);
    rootItem->setLargeImages(oldRoot->useLargeImages());
    rootItem->setUseAlbumImages(oldRoot->useAlbumImages());
    rootItem->setUseArtistImages(oldRoot->useArtistImages());
    delete oldRoot;
    endResetModel();

    if (mpdModel) {
        AlbumsModel::self()->update(rootItem);
    }
}

QModelIndex MusicLibraryModel::findSongIndex(const Song &s) const
{
    MusicLibraryItemArtist *artistItem = rootItem->artist(s, false);
    if (artistItem) {
        MusicLibraryItemAlbum *albumItem = artistItem->album(s, false);
        if (albumItem) {
            foreach (MusicLibraryItem *songItem, albumItem->childItems()) {
                if (songItem->data()==s.displayTitle()) {
                    return createIndex(songItem->row(), 0, songItem);
                }
            }
        }
    }

    return QModelIndex();
}

QModelIndex MusicLibraryModel::findArtistIndex(const QString &artist) const
{
    Song s;
    s.artist=artist;
    MusicLibraryItemArtist *artistItem = rootItem->artist(s, false);
    return artistItem ? index(artistItem->row(), 0, QModelIndex()) : QModelIndex();
}

QModelIndex MusicLibraryModel::findAlbumIndex(const QString &artist, const QString &album) const
{
    Song s;
    s.artist=artist;
    s.album=album;
    MusicLibraryItemArtist *artistItem = rootItem->artist(s, false);
    MusicLibraryItemAlbum *albumItem = artistItem ? artistItem->album(s, false) : 0;
    return albumItem ? index(albumItem->row(), 0, index(artistItem->row(), 0, QModelIndex())) : QModelIndex();
}

void MusicLibraryModel::removeCache()
{
    QString cacheFile(cacheFileName());
    if (QFile::exists(cacheFile)) {
        QFile::remove(cacheFile);
    }

    // Remove old (non-compressed) cache file as well...
    QString cacheFileWithoutPort(cacheFileName(false));
    if (cacheFileWithoutPort!=cacheFile && QFile::exists(cacheFileWithoutPort)) {
        QFile::remove(cacheFileWithoutPort);
    }

    // Remove old (non-compressed) cache file as well...
    QString oldCache=cacheFile;
    oldCache.replace(constLibraryCompressedExt, constLibraryExt);
    if (oldCache!=cacheFile && QFile::exists(oldCache)) {
        QFile::remove(oldCache);
    }

    databaseTime = QDateTime();
}

QSet<QString> MusicLibraryModel::getAlbumArtists()
{
    QSet<QString> a;
    foreach (MusicLibraryItem *i, rootItem->childItems()) {
        a.insert(i->data());
    }
    return a;
}

void MusicLibraryModel::updateMusicLibrary(MusicLibraryItemRoot *newroot, QDateTime dbUpdate, bool fromFile)
{
    if (!mpdModel || (databaseTime.isValid() && databaseTime >= dbUpdate)) {
        delete newroot;
        return;
    }

    newroot->setUseAlbumImages(rootItem->useAlbumImages());
    newroot->setUseArtistImages(rootItem->useArtistImages());
    bool updatedSongs=false;
    bool needToSave=dbUpdate>databaseTime;
    bool incremental=rootItem->childCount() && newroot->childCount();
    bool needToUpdate=!databaseTime.isValid();

    if (incremental && !QFile::exists(cacheFileName())) {
        incremental=false;
    }

    databaseTime = dbUpdate;
    if (incremental) {
        updatedSongs=update(newroot->allSongs());
        delete newroot;
    } else {
        const MusicLibraryItemRoot *oldRoot = rootItem;
        beginResetModel();
        rootItem = newroot;
        rootItem->setModel(this);
        rootItem->setLargeImages(oldRoot->useLargeImages());
        rootItem->setUseAlbumImages(oldRoot->useAlbumImages());
        rootItem->setUseArtistImages(oldRoot->useArtistImages());
        delete oldRoot;
        endResetModel();
        updatedSongs=true;
    }

    // MPD proxy DB plugin (MPD < 0.18.5) does not provide a datetime for the DB. Also, Mopidy
    // returns 0 for the database time (which equates to 1am Jan 1st 1970!). Therefore, in these
    // cases we just use current datetime so that we dont keep requesting DB listing each time
    // Cantata starts...
    //
    // Mopidy users, and users of the proxy DB plugin, will have to force Cantata to refresh :-(
    if (!fromFile) {
        databaseTimeUnreliable=!dbUpdate.isValid() || dbUpdate.date().year()<2000; // See note in updatingMpd()
    }
    if ((!databaseTime.isValid() && !dbUpdate.isValid()) || (databaseTime.date().year()<2000 && dbUpdate.date().year()<2000)) {
        databaseTime=QDateTime::currentDateTime();
    }

    if ((updatedSongs || needToUpdate) && (!fromFile && (needToSave || needToUpdate))) {
        rootItem->toXML(cacheFileName(), databaseTime, databaseTimeUnreliable);
    }

    AlbumsModel::self()->update(rootItem);
    emit updateGenres(rootItem->genres());
}

void MusicLibraryModel::updatingMpd()
{
    // MPD/Mopidy is being updated. If MPD's database-time is not reliable (as is the case for older proxy DBs, and Mopidy)
    // then we set the databaseTime to NOW when updated. This means we will miss any updates. So, for these scenarios, when
    // a users presses 'Refresh Database' in Cantata's main window, we need to reset our view of the databaseTime to null, s
    // that we update. This does mean that we will ALWAYS fetch the whole listing - but we have n oway of knowing if it changed
    // or not :-(
    if (databaseTimeUnreliable) {
        removeCache();
    }
}

bool MusicLibraryModel::update(const QSet<Song> &songs)
{
    bool updatedSongs=rootItem->update(songs);

    if (updatedSongs && checkable) {
        QSet<Song> chkdSongs;
        foreach (MusicLibraryItem *artist, rootItem->childItems()) {
            MusicLibraryItemArtist *artistItem=static_cast<MusicLibraryItemArtist *>(artist);
            QModelIndex artistIndex;
            int numCheckedAlbums=0;
            int numUnCheckedAlbums=0;
            int numPartialAlbums=0;

            foreach (MusicLibraryItem *album, artistItem->childItems()) {
                MusicLibraryItemAlbum *albumItem=static_cast<MusicLibraryItemAlbum *>(album);
                int numCheckedSongs=0;
                int numUnCheckedSongs=0;
                foreach (MusicLibraryItem *song, albumItem->childItems()) {
                    if (Qt::Unchecked==song->checkState()) {
                        numUnCheckedSongs++;
                    } else {
                        chkdSongs.insert(static_cast<MusicLibraryItemSong *>(song)->song());
                        numCheckedSongs++;
                    }
                }
                Qt::CheckState albumState=numCheckedAlbums && numUnCheckedAlbums ? Qt::PartiallyChecked : numCheckedAlbums ? Qt::Checked : Qt::Unchecked;
                if (albumState!=albumItem->checkState()) {
                    albumItem->setCheckState(albumState);
                    if (!artistIndex.isValid()) {
                        artistIndex=index(artistItem->row(), 0, QModelIndex());
                    }
                    QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex);
                    emit dataChanged(albumIndex, albumIndex);
                }

                switch (albumState) {
                case Qt::PartiallyChecked: numPartialAlbums++;   break;
                case Qt::Checked:          numCheckedAlbums++;   break;
                case Qt::Unchecked:        numUnCheckedAlbums++; break;
                }
            }

            Qt::CheckState artistState=numPartialAlbums ? Qt::PartiallyChecked : numCheckedAlbums ? Qt::Checked : Qt::Unchecked;
            if (artistState!=artistItem->checkState()) {
                artistItem->setCheckState(artistState);
                if (!artistIndex.isValid()) {
                    artistIndex=index(artistItem->row(), 0, QModelIndex());
                }
                emit dataChanged(artistIndex, artistIndex);
            }
        }
        emit checkedSongs(chkdSongs);
    }

    return updatedSongs;
}

//void MusicLibraryModel::uncheckAll()
//{
//    if (!checkable) {
//        return;
//    }

//    foreach (MusicLibraryItem *artist, rootItem->childItems()) {
//        MusicLibraryItemArtist *artistItem=static_cast<MusicLibraryItemArtist *>(artist);
//        QModelIndex artistIndex=index(artistItem->row(), 0, QModelIndex());

//        foreach (MusicLibraryItem *album, artistItem->childItems()) {
//            MusicLibraryItemAlbum *albumItem=static_cast<MusicLibraryItemAlbum *>(album);
//            QModelIndex albumIndex=index(albumItem->row(), 0, artistIndex);

//            foreach (MusicLibraryItem *song, albumItem->childItems()) {
//                if (Qt::Unchecked!=song->checkState()) {
//                    song->setCheckState(Qt::Unchecked);
//                    QModelIndex songIndex=index(song->row(), 0, albumIndex);
//                    emit dataChanged(songIndex, songIndex);
//                }
//            }
//            if (Qt::Unchecked!=albumItem->checkState()) {
//                albumItem->setCheckState(Qt::Unchecked);
//                emit dataChanged(albumIndex, albumIndex);
//            }
//        }
//        if (Qt::Unchecked!=artistItem->checkState()) {
//            artistItem->setCheckState(Qt::Unchecked);
//            emit dataChanged(artistIndex, artistIndex);
//        }
//    }
//    emit checkedSongs(QSet<Song>());
//}

void MusicLibraryModel::toggleGrouping()
{
    beginResetModel();
    rootItem->toggleGrouping();
    rootItem->toXML(cacheFileName(), databaseTime, databaseTimeUnreliable);
    endResetModel();
    if (mpdModel) {
        AlbumsModel::self()->update(rootItem);
    }
}

QList<Song> MusicLibraryModel::getAlbumTracks(const Song &s) const
{
    QList<Song> songs;
    MusicLibraryItemArtist *artistItem = rootItem->artist(s, false);
    if (artistItem) {
        MusicLibraryItemAlbum *albumItem = artistItem->album(s, false);
        if (albumItem) {
            foreach (MusicLibraryItem *songItem, albumItem->childItems()) {
                songs.append(static_cast<MusicLibraryItemSong *>(songItem)->song());
            }
            qSort(songs);
        }
    }
    return songs;
}

QList<Song> MusicLibraryModel::getArtistAlbumsFirstTracks(const Song &song) const
{
    //QString artist=song.isVariousArtists() ? song.artist : song.albumArtist();
    QString basicArtist=song.basicArtist();
    QList<Song> tracks;
    bool foundCurrent=false;
    bool checkAll=!song.isVariousArtists() && song.albumArtist()!=basicArtist;

    foreach (MusicLibraryItem *ar, rootItem->childItems()) {
        if (static_cast<MusicLibraryItemArtist *>(ar)->isVarious() || checkAll) {
            foreach (MusicLibraryItem *al, static_cast<MusicLibraryItemContainer *>(ar)->childItems()) {
                MusicLibraryItemAlbum *a=static_cast<MusicLibraryItemAlbum *>(al);
                if (a->containsArtist(basicArtist)) {
                    Song first=static_cast<MusicLibraryItemSong *>(a->childItems().first())->song();
                    tracks.append(first);
                    if (!foundCurrent && first.album==song.album && first.albumArtist()==song.albumArtist()) {
                        foundCurrent=true;
                    }
                }
            }
        } else if (ar->data()==basicArtist) { // i.e. album-artist == basic artist (most cases!)
            foreach (MusicLibraryItem *al, static_cast<MusicLibraryItemContainer *>(ar)->childItems()) {
                MusicLibraryItemContainer *a=static_cast<MusicLibraryItemContainer *>(al);
                if (!a->childItems().isEmpty()) {
                    Song first=static_cast<MusicLibraryItemSong *>(a->childItems().first())->song();
                    tracks.append(first);
                    if (!foundCurrent && first.album==song.album && first.albumArtist()==song.albumArtist()) {
                        foundCurrent=true;
                    }
                }
            }
        }
    }
    if (!foundCurrent) {
        tracks.append(song);
    }
    return tracks;
}

void MusicLibraryModel::setArtistImage(const Song &song, const QImage &img, bool update)
{
    if (!rootItem->useArtistImages() || img.isNull() || MusicLibraryItemAlbum::CoverNone==MusicLibraryItemAlbum::currentCoverSize() ||
        song.file.startsWith("http://") || song.name.startsWith("http://")) {
        return;
    }

    MusicLibraryItemArtist *artistItem = rootItem->artist(song, false);
    if (artistItem && artistItem->setCover(img, update)) {
        QModelIndex idx=index(artistItem->row(), 0, QModelIndex());
        emit dataChanged(idx, idx);
    }
}

void MusicLibraryModel::setCover(const Song &song, const QImage &img, const QString &file)
{
    setCover(song, img, file, false);
}

void MusicLibraryModel::updateCover(const Song &song, const QImage &img, const QString &file)
{
    if (song.isArtistImageRequest()) {
        setArtistImage(song, img, true);
    } else {
        setCover(song, img, file, true);
    }
}

void MusicLibraryModel::setCover(const Song &song, const QImage &img, const QString &file, bool update)
{
    Q_UNUSED(file)
    if (!rootItem->useAlbumImages() || img.isNull() || MusicLibraryItemAlbum::CoverNone==MusicLibraryItemAlbum::currentCoverSize() ||
        song.isCdda() || song.file.startsWith("http:/") || song.name.startsWith("http:/")) {
        return;
    }

    MusicLibraryItemArtist *artistItem = rootItem->artist(song, false);
    if (artistItem) {
        MusicLibraryItemAlbum *albumItem = artistItem->album(song, false);
        if (albumItem && albumItem->setCover(img, update)) {
            QModelIndex idx=index(albumItem->row(), 0, index(artistItem->row(), 0, QModelIndex()));
            emit dataChanged(idx, idx);
        }
    }
}

/**
 * Read an xml file from disk.
 *
 * @param filename The name of the xmlfile to read the db from
 *
 * @return true on succesfull parsing, false otherwise
 * TODO: check for hostname
 * TODO: check for database version
 */
bool MusicLibraryModel::fromXML()
{
    // If socket connection used, then check if cache file has port number...
    QString withPort=cacheFileName();
    QString withoutPort=cacheFileName(false);
    if (withPort!=withoutPort && QFile::exists(withoutPort) && !QFile::exists(withPort)) {
        QFile::rename(withoutPort, withPort);
    }

    convertCache(cacheFileName());
    MusicLibraryItemRoot *root=new MusicLibraryItemRoot;
    quint32 date=root->fromXML(cacheFileName(), MPDStats::self()->dbUpdate(), &databaseTimeUnreliable);
    if (!date) {
        delete root;
        return false;
    }
    QDateTime dt;
    dt.setTime_t(date);
    updateMusicLibrary(root, dt, true);
    return true;
}

Qt::ItemFlags MusicLibraryModel::flags(const QModelIndex &index) const
{
    if (index.isValid()) {
        return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | (checkable ? Qt::ItemIsUserCheckable : Qt::NoItemFlags);
    }
    return Qt::ItemIsDropEnabled;
}

QStringList MusicLibraryModel::filenames(const QModelIndexList &indexes, bool allowPlaylists) const
{
    QList<Song> songList=songs(indexes, allowPlaylists);
    QStringList fnames;
    foreach (const Song &s, songList) {
        fnames.append(s.file);
    }
    return fnames;
}

static inline void addSong(const MusicLibraryItem *song, QList<Song> &insertInto, QList<Song> &checkAgainst, bool allowPlaylists)
{
    if (MusicLibraryItem::Type_Song==song->itemType() &&
        (allowPlaylists || Song::Playlist!=static_cast<const MusicLibraryItemSong*>(song)->song().type) &&
        !checkAgainst.contains(static_cast<const MusicLibraryItemSong*>(song)->song())) {
        insertInto << static_cast<const MusicLibraryItemSong*>(song)->song();
    }
}

QList<Song> MusicLibraryModel::songs(const QModelIndexList &indexes, bool allowPlaylists) const
{
    QList<Song> songs;

    foreach(QModelIndex index, indexes) {
        const MusicLibraryItem *item = static_cast<MusicLibraryItem *>(index.internalPointer());

        switch (item->itemType()) {
        case MusicLibraryItem::Type_Artist: {
            // First, sort all albums as they would appear in UI...
            QList<MusicLibraryItem *> artistAlbums=static_cast<const MusicLibraryItemContainer *>(item)->childItems();
            qSort(artistAlbums.begin(), artistAlbums.end(), MusicLibraryItemAlbum::lessThan);

            foreach (MusicLibraryItem *i, artistAlbums) {
                QList<Song> albumSongs;
                const MusicLibraryItemSong *cue=allowPlaylists ? static_cast<const MusicLibraryItemAlbum *>(i)->getCueFile() : 0;
                if (cue) {
                    addSong(cue, albumSongs, songs, true);
                } else {
                    foreach (const MusicLibraryItem *song, static_cast<const MusicLibraryItemContainer *>(i)->childItems()) {
                        addSong(song, albumSongs, songs, false);
                    }
                }
                qSort(albumSongs);
                songs << albumSongs;
            }
            break;
        }
        case MusicLibraryItem::Type_Album: {
            QList<Song> albumSongs;
            const MusicLibraryItemSong *cue=allowPlaylists ? static_cast<const MusicLibraryItemAlbum *>(item)->getCueFile() : 0;
            if (cue) {
                addSong(cue, albumSongs, songs, true);
            } else {
                foreach (const MusicLibraryItem *song, static_cast<const MusicLibraryItemContainer *>(item)->childItems()) {
                    addSong(song, albumSongs, songs, false);
                }
            }
            qSort(albumSongs);
            songs << albumSongs;
            break;
        }
        case MusicLibraryItem::Type_Song:
            addSong(item, songs, songs, allowPlaylists);
            break;
        default:
            break;
        }
    }

    return songs;
}

QList<Song> MusicLibraryModel::songs(const QStringList &filenames, bool insertNotFound) const
{
    QList<Song> songs;

    if (filenames.isEmpty()) {
        return songs;
    }

    QSet<QString> files=filenames.toSet();

    foreach (const MusicLibraryItem *artist, rootItem->childItems()) {
        foreach (const MusicLibraryItem *album, static_cast<const MusicLibraryItemContainer *>(artist)->childItems()) {
            foreach (const MusicLibraryItem *song, static_cast<const MusicLibraryItemContainer *>(album)->childItems()) {
                QSet<QString>::Iterator it=files.find(static_cast<const MusicLibraryItemSong*>(song)->file());

                if (it!=files.end()) {
                    files.erase(it);
                    songs.append(static_cast<const MusicLibraryItemSong*>(song)->song());
                }
            }
        }
    }

    if (insertNotFound && files.size()) {
        foreach (const QString &file, files) {
            Song s;
            s.file=file;
            songs.append(s);
        }
    }
    return songs;
}

/**
* Convert the data at indexes into mimedata ready for transport
*
* @param indexes The indexes to pack into mimedata
* @return The mimedata
*/
QMimeData *MusicLibraryModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData *mimeData = new QMimeData();
    QStringList files=filenames(indexes, true);
    PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files);
    if (!MPDConnection::self()->getDetails().dir.isEmpty()) {
        QStringList paths;
        foreach (const QString &f, files) {
            paths << MPDConnection::self()->getDetails().dir+f;
        }
        PlayQueueModel::encode(*mimeData, PlayQueueModel::constUriMimeType, paths);
    }
    return mimeData;
}
