/*  This file is part of the KDE project
    Copyright (C) 2009 Norbert Frese <nf2.email@gmail.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#include <QtCore/QMimeData>
#include <QtGui/QColor>
#include <QtGui/QAction>

#include "kfileplacesmodel-gnome.h"
#include "placesitemstandard.h"
#include "placesitemstorage.h"
#include "placesitembookmark.h"
#include "storage/kstorage.h"
#include "storage/kstoragemonitor.h"

#include <kuser.h>
#include <kgenericfactory.h>
#include <kpluginfactory.h>

K_PLUGIN_FACTORY(KFilePlacesModelGnomeModuleFactory, registerPlugin<KFilePlacesModelGnome>();)
K_EXPORT_PLUGIN(KFilePlacesModelGnomeModuleFactory("kfileplacesmodel-gnome"))

KFilePlacesModelGnome::KFilePlacesModelGnome(KFilePlacesModel *model)
    : KFilePlacesModelImpl(model)
{
}

KFilePlacesModelGnome::KFilePlacesModelGnome(QObject* parent, const QVariantList& params)
    : KFilePlacesModelImpl(qobject_cast<KFilePlacesModel *>(params.first().value<QObject *>()))
{
    QSharedPointer<PlacesItem> home(new PlacesItemStandard(KUser().loginName(), KUrl(KUser().homeDir()), "user-home"));
    items.standardItems << home;

    QSharedPointer<PlacesItem> desktop(new PlacesItemStandard("Desktop", KUrl(KUser().homeDir() + "/Desktop"), "user-desktop"));
    items.standardItems << desktop;

    QSharedPointer<PlacesItem> filesystem(new PlacesItemStandard("File System", KUrl("/"), "drive-harddisk"));
    items.standardItems << filesystem;

    QSharedPointer<PlacesItem> network(new PlacesItemStandard("Network", KUrl("network:///"), "network-workgroup"));
    items.standardItems << network;

    QSharedPointer<PlacesItem> trash(new PlacesItemStandard("Trash", KUrl("trash:///"), "user-trash"));
    items.standardItems2 << trash;

    // add devices

    QList<KStorage> storageList = KStorage::list();

    foreach(const KStorage &storage, storageList)
    {
        QSharedPointer<PlacesItem> item(new PlacesItemStorage(storage));
        items.deviceItems << item;
    }

    KStorageMonitor * storageMonitor = KStorageMonitor::instance();

    connect(storageMonitor, SIGNAL(added(const KStorage&)),
            this, SLOT(_k_storageAdded(const KStorage&)));
    connect(storageMonitor, SIGNAL(removed(const KStorage&)),
            this, SLOT(_k_storageRemoved(const KStorage&)));
    connect(storageMonitor, SIGNAL(changed(const KStorage&)),
            this, SLOT(_k_storageChanged(const KStorage&)));

    // add bookmarks

    QList<GtkBookmark> bookmarks = gtkBookmarks.allBookmarks();

    foreach(const GtkBookmark bookmark, bookmarks)
    {
        PlacesItem::Ptr item(new PlacesItemBookmark(bookmark));
        items.bookmarkItems << item;
    }

    connect(&gtkBookmarks, SIGNAL(added(const GtkBookmark&, int)),
            this, SLOT(_k_bookmarkAdded(const GtkBookmark&, int)));

    connect(&gtkBookmarks, SIGNAL(removed(const GtkBookmark&, int)),
            this, SLOT(_k_bookmarkRemoved(const GtkBookmark&, int)));

    connect(&gtkBookmarks, SIGNAL(modified(const GtkBookmark&, int)),
            this, SLOT(_k_bookmarkModified(const GtkBookmark&, int)));

}

static QString _k_internalMimetype(const KFilePlacesModel * const facade)
{
    return QString("application/x-kfileplacesmodel-")+QString::number((long)facade);
}

bool KFilePlacesModelGnome::isDevice(const QModelIndex & index) const
{
    return !storageForIndex(index).isNull();
}

Solid::Device KFilePlacesModelGnome::deviceForIndex(const QModelIndex & index) const
{
    return Solid::Device();
}

KBookmark KFilePlacesModelGnome::bookmarkForIndex(const QModelIndex & index) const
{
    if (!index.isValid())
        return KBookmark();

    PlacesItem::Ptr placesItem = items[index.row()];
    PlacesItemBookmark * placesItemBookmark = qobject_cast<PlacesItemBookmark*>(placesItem.data());
    if (placesItemBookmark)
    {
        return KBookmark::standaloneBookmark(placesItemBookmark->text(), placesItemBookmark->url());
    }

    return KBookmark();
}

KStorage KFilePlacesModelGnome::storageForIndex(const QModelIndex & index) const
{
    if (!index.isValid())
        return KStorage();

    PlacesItem::Ptr placesItem = items[index.row()];

    PlacesItemStorage * placesItemStorage = qobject_cast<PlacesItemStorage*>(placesItem.data());
    if (placesItemStorage)
    {
        return placesItemStorage->storage();
    }
    return KStorage();
}

QAction *KFilePlacesModelGnome::teardownActionForIndex(const QModelIndex & index) const
{
    KStorage storage = storageForIndex(index);

    if (!storage.isNull() && storage.isAccessible())
    {
        QString iconName;
        QString text;
        QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&");

        if (storage.isReleasable()) {
            text = i18n("&Release '%1'", label);
        } else if (storage.isRemovable() || storage.isHotpluggable()) {
            text = i18n("&Safely Remove '%1'", label);
            iconName = "media-eject";
        } else {
            text = i18n("&Unmount '%1'", label);
            iconName = "media-eject";
        }

        if (!iconName.isEmpty()) {
            return new QAction(KIcon(iconName), text, 0);
        } else {
            return new QAction(text, 0);
        }
    }

    return 0;

}

QAction *KFilePlacesModelGnome::ejectActionForIndex(const QModelIndex & index) const
{
    KStorage storage = storageForIndex(index);

    if (storage.isReleasable()) {

        QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&");
        QString text = i18n("&Eject '%1'", label);

        return new QAction(KIcon("media-eject"), text, 0);
    }

    return 0;
}

void KFilePlacesModelGnome::requestTeardown(const QModelIndex & index)
{
    KStorage storage = storageForIndex(index);
    storage.teardown(this, SLOT(_k_storageTeardownDone(const KStorage &, const KStorage::ErrorType, const QString &)));
}

void KFilePlacesModelGnome::requestEject(const QModelIndex & index)
{
    KStorage storage = storageForIndex(index);
    storage.eject(this, SLOT(_k_storageEjectDone(const KStorage &, const KStorage::ErrorType, const QString &)));
}

void KFilePlacesModelGnome::requestSetup(const QModelIndex & index)
{
    KStorage storage = storageForIndex(index);
    storageSetupInProgress[storage] = index;
    storage.setup(this, SLOT(_k_storageSetupDone(const KStorage &, const KStorage::ErrorType, const QString &)));
}

void KFilePlacesModelGnome::addPlace(const QString & text, const KUrl & url, const QString & iconName, const QString & appName)
{
    gtkBookmarks.createBookmark(url, text);
}

void KFilePlacesModelGnome::editPlace(const QModelIndex & modelIndex, const QString & text, const KUrl & url, const QString & iconName, const QString & appName)
{
    int row = modelIndex.row();
    int bookmarkIndex = items.bookMarkIndexFromGlobalIndex(row);
    if (bookmarkIndex >= 0)
    {
        gtkBookmarks.updateBookmark(bookmarkIndex, url, text);
    }
}

void KFilePlacesModelGnome::removePlace(const QModelIndex & index)
{
    int row = index.row();
    int bookmarkIndex = items.bookMarkIndexFromGlobalIndex(row);
    if (bookmarkIndex >= 0)
    {
        gtkBookmarks.removeBookmark(bookmarkIndex);
    }
}

void KFilePlacesModelGnome::setPlaceHidden(const QModelIndex & index, bool hidden)
{
}

int KFilePlacesModelGnome::hiddenCount() const
{
    return 0;
}

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

    PlacesItem * item = static_cast<PlacesItem*>(index.internalPointer());

    if (item)
        return item->data(role);
    else
        return QVariant();
}

QModelIndex KFilePlacesModelGnome::index(int row, int column, const QModelIndex & parent) const
{
    if (row<0 || column!=0 || row>=items.size())
        return QModelIndex();

    if (parent.isValid())
        return QModelIndex();

    return createIndex(row, column, items[row].data());

}

int KFilePlacesModelGnome::rowCount(const QModelIndex & parent) const
{
    return items.size();
}

int KFilePlacesModelGnome::columnCount(const QModelIndex & parent) const
{
    return 1;
}

QModelIndex KFilePlacesModelGnome::closestItem(const KUrl & url) const
{
    int foundRow = -1;
     int maxLength = 0;

     // Search the item which is equal to the URL or at least is a parent URL.
     // If there are more than one possible item URL candidates, choose the item
     // which covers the bigger range of the URL.
     for (int row = 0; row<items.size(); ++row) {
         PlacesItem *item = items[row].data();
         KUrl itemUrl = KUrl(item->data(KFilePlacesModel::UrlRole).toUrl());

         if (itemUrl.isParentOf(url)) {
             const int length = itemUrl.prettyUrl().length();
             if (length > maxLength) {
                 foundRow = row;
                 maxLength = length;
             }
         }
     }

     if (foundRow==-1)
         return QModelIndex();
     else
         return createIndex(foundRow, 0, items[foundRow].data());

    return QModelIndex();
}

Qt::DropActions KFilePlacesModelGnome::supportedDropActions() const
{
    return Qt::ActionMask;
}

Qt::ItemFlags KFilePlacesModelGnome::flags(const QModelIndex & index) const
{
    Qt::ItemFlags res = Qt::ItemIsSelectable|Qt::ItemIsEnabled;

    if (index.isValid())
        res|= Qt::ItemIsDragEnabled;

    if (!index.isValid())
        res|= Qt::ItemIsDropEnabled;

    return res;
}

QStringList KFilePlacesModelGnome::mimeTypes() const
{
    QStringList types;

    types << _k_internalMimetype(facade) << "text/uri-list";

    return types;
}

QMimeData *KFilePlacesModelGnome::mimeData(const QModelIndexList & indexes) const
{
    KUrl::List urls;
    QByteArray itemData;

    QDataStream stream(&itemData, QIODevice::WriteOnly);

    foreach (const QModelIndex &index, indexes) {
        KUrl itemUrl = facade->url(index);
        if (itemUrl.isValid())
            urls << itemUrl;
        stream << index.row();
    }

    QMimeData *mimeData = new QMimeData();

    if (!urls.isEmpty())
        urls.populateMimeData(mimeData);

    mimeData->setData(_k_internalMimetype(facade), itemData);

    return mimeData;
}

bool KFilePlacesModelGnome::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
    if (action == Qt::IgnoreAction)
        return true;

    if (column > 0)
        return false;

    if (row==-1 && parent.isValid()) {
        return false; // Don't allow to move an item onto another one,
                      // too easy for the user to mess something up
                      // If we really really want to allow copying files this way,
                      // let's do it in the views to get the good old drop menu
    }


    int bookmarkIx = 0;

    if (row==-1) {
        // The dropped item is moved or added to the last position

        bookmarkIx = items.bookmarkItems.count();

    } else {
        // The dropped item is moved or added before position 'row', ie after position 'row-1'

        if (row>0) {
            bookmarkIx = items.bookMarkIndexFromGlobalIndex(row);
            if (bookmarkIx < 0) return false;
        }
    }

    if (data->hasFormat(_k_internalMimetype(facade))) {
        // The operation is an internal move
        QByteArray itemData = data->data(_k_internalMimetype(facade));
        QDataStream stream(&itemData, QIODevice::ReadOnly);
        int itemRow;

        stream >> itemRow;

        int sourceBookmarkIx = items.bookMarkIndexFromGlobalIndex(itemRow);
        if (sourceBookmarkIx < 0) return false;

        gtkBookmarks.moveBookmark(sourceBookmarkIx, bookmarkIx);

    } else if (data->hasFormat("text/uri-list")) {
        // The operation is an add
        KUrl::List urls = KUrl::List::fromMimeData(data);
        gtkBookmarks.insertUrls(bookmarkIx, urls);

    } else {
        // Oops, shouldn't happen thanks to mimeTypes()
        kWarning() << ": received wrong mimedata, " << data->formats();
        return false;
    }

    return true;
}

////// slots

void KFilePlacesModelGnome::_k_storageAdded(const KStorage &storage)
{
    PlacesItem::Ptr item(new PlacesItemStorage(storage));
    items.deviceItems << item;

    int row = items.findIndexById(storage.id());
    beginInsertRows(QModelIndex(), row, row);
    endInsertRows();
}

void KFilePlacesModelGnome::_k_storageRemoved(const KStorage &storage)
{
    int row = items.findIndexById(storage.id());
    if (row>=0)
    {
        beginRemoveRows(QModelIndex(), row, row);
        items.removeAt(row);
        endRemoveRows();
    }
}

void KFilePlacesModelGnome::_k_storageChanged(const KStorage &storage)
{
    int row = items.findIndexById(storage.id());
    if (row>=0)
    {
        QModelIndex index = facade->index(row, 0);
        emitDataChanged(index, index);
    }
}

void KFilePlacesModelGnome::_k_itemChanged(const QString &id)
{
    int row = items.findIndexById(id);
    if (row>=0)
    {
        QModelIndex index = facade->index(row, 0);
        emitDataChanged(index, index);
    }
}

void KFilePlacesModelGnome::_k_storageSetupDone(const KStorage &storage, const KStorage::ErrorType error, const QString &errorMessage)
{
    QPersistentModelIndex index = storageSetupInProgress.take(storage);

    if (!index.isValid()) {
        return;
    }

    if (!error) {
        emitSetupDone(index, true);
    } else {
        if (!errorMessage.isNull()) {
            emitErrorMessage(i18n("An error occurred while accessing '%1', the system responded: %2",
                    facade->text(index),
                    errorMessage));
        } else {
            emitErrorMessage(i18n("An error occurred while accessing '%1'",
                    facade->text(index)));
        }
        emitSetupDone(index, false);
    }

}

void KFilePlacesModelGnome::_k_storageTeardownDone(const KStorage &storage, const KStorage::ErrorType error, const QString &errorMessage)
{
    if (error) {
        emitErrorMessage(errorMessage);
    }
}

void KFilePlacesModelGnome::_k_storageEjectDone(const KStorage &storage, const KStorage::ErrorType error, const QString &errorMessage)
{
    if (error) {
        emitErrorMessage(errorMessage);
    }
}

void KFilePlacesModelGnome::_k_bookmarkAdded(GtkBookmark bookmark, int index)
{
    kDebug() << "add " << index << " " << bookmark.text();

    PlacesItem::Ptr item(new PlacesItemBookmark(bookmark));

    int row = items.globalIndexFromBookmarkIndex(index);
    beginInsertRows(QModelIndex(), row, row);
    items.bookmarkItems.insert(index, item);
    endInsertRows();


}

void KFilePlacesModelGnome::_k_bookmarkRemoved(GtkBookmark bookmark, int index)
{
    kDebug() << "removed " << index << " " << bookmark.text();
    int row = items.globalIndexFromBookmarkIndex(index);
    beginRemoveRows(QModelIndex(), row, row);
    items.bookmarkItems.removeAt(index);
    endRemoveRows();
}

void KFilePlacesModelGnome::_k_bookmarkModified(GtkBookmark bookmark, int index)
{
    int row = items.globalIndexFromBookmarkIndex(index);
    QModelIndex modelIndex = facade->index(row, 0);

    kDebug() << "modified " << index << " " << bookmark.text();
    PlacesItem::Ptr item(new PlacesItemBookmark(bookmark));
    items.bookmarkItems[index] =  item;
    emitDataChanged(modelIndex, modelIndex);
}



