/***************************************************************************
 * actioncollectionmodel.cpp
 * This file is part of the KDE project
 * copyright (C) 2006-2007 by Sebastian Sauer (mail@dipe.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * This program 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 program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 ***************************************************************************/

#include "actioncollectionmodel.h"
#include "krossui_debug.h"

#include <kross/core/action.h>
#include <kross/core/actioncollection.h>
#include <kross/core/manager.h>

#include <klocalizedstring.h>
#include <QDebug>

#include <QEvent>
#include <QMimeData>
#include <QPointer>

using namespace Kross;

/******************************************************************************
 * ActionCollectionModel
 */

namespace Kross
{

/// \internal d-pointer class.
class ActionCollectionModel::Private
{
public:
    QPointer<ActionCollection> collection;
    Mode mode;
};

}

ActionCollectionModel::ActionCollectionModel(QObject *parent, ActionCollection *collection, Mode mode)
    : QAbstractItemModel(parent)
    , d(new Private())
{
    //krossdebug( QString( "ActionCollectionModel::ActionCollectionModel:") );
    d->collection = collection ? collection : Kross::Manager::self().actionCollection();
    d->mode = mode;
    //setSupportedDragActions(Qt::MoveAction);

    //ActionCollection propagates signals to parent
    QObject::connect(d->collection, SIGNAL(dataChanged(Action*)), this, SLOT(slotDataChanged(Action*)));
    QObject::connect(d->collection, SIGNAL(dataChanged(ActionCollection*)), this, SLOT(slotDataChanged(ActionCollection*)));

    QObject::connect(d->collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionToBeInserted(ActionCollection*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionInserted(ActionCollection*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionToBeRemoved(ActionCollection*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SLOT(slotCollectionRemoved(ActionCollection*,ActionCollection*)));

    QObject::connect(d->collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SLOT(slotActionToBeInserted(Action*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SLOT(slotActionInserted(Action*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SLOT(slotActionToBeRemoved(Action*,ActionCollection*)));
    QObject::connect(d->collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SLOT(slotActionRemoved(Action*,ActionCollection*)));
}

ActionCollectionModel::~ActionCollectionModel()
{
    delete d;
}

ActionCollection *ActionCollectionModel::rootCollection() const
{
    return d->collection;
}

int ActionCollectionModel::rowNumber(ActionCollection *collection) const
{
    Q_ASSERT(collection != nullptr);
    ActionCollection *par = collection->parentCollection();
    Q_ASSERT(par != nullptr);
    int row = par->collections().indexOf(collection->objectName()) + par->actions().count();
    return row;
}

QModelIndex ActionCollectionModel::indexForCollection(ActionCollection *collection) const
{
    if (collection == d->collection) {
        return QModelIndex();
    }
    return createIndex(rowNumber(collection), 0, collection->parentCollection());
}

QModelIndex ActionCollectionModel::indexForAction(Action *act) const
{
    ActionCollection *coll = static_cast<ActionCollection *>(act->parent());
    return createIndex(coll->actions().indexOf(act), 0, coll);
}

void ActionCollectionModel::slotCollectionToBeInserted(ActionCollection *child, ActionCollection *parent)
{
    //krossdebug( QString( "ActionCollectionModel::slotCollectionToBeInserted: %1 %2" ).arg( child->name() ).arg( parent->name( ) )  );
    Q_ASSERT(parent);
    Q_UNUSED(child)
    int row = parent->actions().count() + parent->collections().count(); // we assume child is appended!!
    QModelIndex parIdx = indexForCollection(parent);
    beginInsertRows(parIdx, row, row);
}

void ActionCollectionModel::slotCollectionInserted(ActionCollection *, ActionCollection *)
{
    //krossdebug( QString( "ActionCollectionModel::slotCollectionInserted: %1 %2" ).arg( child->name( ) ).arg( parent->name( ) )  );
    endInsertRows();
}

void ActionCollectionModel::slotCollectionToBeRemoved(ActionCollection *child, ActionCollection *parent)
{
    //krossdebug( QString( "ActionCollectionModel::slotCollectionToBeRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    int row = rowNumber(child);
    QModelIndex parIdx = indexForCollection(parent);
    beginRemoveRows(parIdx, row, row);
}

void ActionCollectionModel::slotCollectionRemoved(ActionCollection *, ActionCollection *)
{
    //krossdebug( QString( "ActionCollectionModel::slotCollectionRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    endRemoveRows();
}

void ActionCollectionModel::slotActionToBeInserted(Action *child, ActionCollection *parent)
{
    //krossdebug( QString( "ActionCollectionModel::slotActionInserted: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    Q_ASSERT(parent);
    Q_UNUSED(child)
    int row = parent->actions().count(); // assume child is appended to actions!!
    QModelIndex parIdx = indexForCollection(parent);
    beginInsertRows(parIdx, row, row);
}

void ActionCollectionModel::slotActionInserted(Action *, ActionCollection *)
{
    //krossdebug( QString( "ActionCollectionModel::slotActionInserted: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    endInsertRows();
}

void ActionCollectionModel::slotActionToBeRemoved(Action *child, ActionCollection *parent)
{
    //krossdebug( QString( "ActionCollectionModel::slotActionToBeRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    Q_ASSERT(parent);
    int row = parent->actions().indexOf(child);
    QModelIndex parIdx = indexForCollection(parent);
    beginRemoveRows(parIdx, row, row);
}

void ActionCollectionModel::slotActionRemoved(Action *, ActionCollection *)
{
    //krossdebug( QString( "ActionCollectionModel::slotActionRemoved: %1 %2" ).arg( child->name() ).arg( parent->name() ) );
    endRemoveRows();
}

//NOTE: not used anymore, remove?
void ActionCollectionModel::slotUpdated()
{
    //emit layoutAboutToBeChanged();
    //emit layoutChanged();
}

void ActionCollectionModel::slotDataChanged(ActionCollection *coll)
{
    //krossdebug( QString( "ActionCollectionModel::slotDataChanged: %1" ).arg( coll->name() ) );
    QModelIndex idx = indexForCollection(coll);
    emit dataChanged(idx, idx);   // NOTE: change if more than one column
}

void ActionCollectionModel::slotDataChanged(Action *act)
{
    //krossdebug( QString( "ActionCollectionModel::slotDataChanged: %1" ).arg( act->name() ) );
    QModelIndex idx = indexForAction(act);
    emit dataChanged(idx, idx);   // NOTE: change if more than one column
}

Action *ActionCollectionModel::action(const QModelIndex &index)
{
    ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
    if (par == nullptr || index.row() >= par->actions().count()) {
        return nullptr;
    }
    return par->actions().value(index.row());
}

ActionCollection *ActionCollectionModel::collection(const QModelIndex &index)
{
    ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
    if (par == nullptr) {
        return nullptr;
    }
    int row = index.row() - par->actions().count();
    if (row < 0) {
        return nullptr; // this is probably an action
    }
    return par->collection(par->collections().value(row));
}

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

int ActionCollectionModel::rowCount(const QModelIndex &index) const
{
    if (action(index)) {
        return 0;
    }
    ActionCollection *par = index.isValid() ? collection(index) : d->collection.data();
    Q_ASSERT_X(par, "ActionCollectionModel::rowCount", "index is not an action nor a collection");
    if (!par) {
        qWarning() << "index is not an action nor a collection" << index;
        return 0;
    }
    int rows = par->actions().count() + par->collections().count();
    return rows;
}

QModelIndex ActionCollectionModel::index(int row, int column, const QModelIndex &parent) const
{
    if (! hasIndex(row, column, parent)) {
        return QModelIndex();
    }
    ActionCollection *par = parent.isValid() ? collection(parent) : d->collection.data();
    if (par == nullptr) {
        // safety: may happen if parent index is an action (ModelTest tests this)
        return QModelIndex();
    }
    return createIndex(row, column, par);
}

QModelIndex ActionCollectionModel::parent(const QModelIndex &index) const
{
    if (! index.isValid()) {
        return QModelIndex();
    }
    ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
    Q_ASSERT(par != nullptr);
    if (par == d->collection) {
        return QModelIndex();
    }
    return createIndex(rowNumber(par), 0, par->parentCollection());
}

Qt::ItemFlags ActionCollectionModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    if (! index.isValid()) {
        return Qt::ItemIsDropEnabled | flags;
    }

    flags |= Qt::ItemIsSelectable;
    //flags |= Qt::ItemIsEditable;
    flags |= Qt::ItemIsDragEnabled;
    flags |= Qt::ItemIsDropEnabled;

    if ((index.column() == 0) && (d->mode & UserCheckable)) {
        flags |= Qt::ItemIsUserCheckable;
    }
    return flags;
}

QVariant ActionCollectionModel::data(const QModelIndex &index, int role) const
{
    if (index.isValid()) {
        Action *act = action(index);
        if (act) {
            switch (role) {
            case Qt::DecorationRole: {
                if (d->mode & Icons)
                    if (! act->iconName().isEmpty()) {
                        return act->icon();
                    }
            } break;
            case Qt::DisplayRole:
                return KLocalizedString::removeAcceleratorMarker(act->text());
            case Qt::ToolTipRole: // fall through
            case Qt::WhatsThisRole: {
                if (d->mode & ToolTips) {
                    const QString file = QFileInfo(act->file()).fileName();
                    return QString("<qt><b>%1</b><br>%2</qt>")
                           .arg(file.isEmpty() ? act->name() : file)
                           .arg(act->description());
                }
            } break;
            case Qt::CheckStateRole: {
                if (d->mode & UserCheckable) {
                    return act->isEnabled() ? Qt::Checked : Qt::Unchecked;
                }
            } break;
            default: break;
            }
            return QVariant();
        }
        ActionCollection *coll = collection(index);
        if (coll) {
            switch (role) {
            case Qt::DecorationRole: {
                if (d->mode & Icons)
                    if (! coll->iconName().isEmpty()) {
                        return coll->icon();
                    }
            } break;
            case Qt::DisplayRole:
                return coll->text();
            case Qt::ToolTipRole: // fall through
            case Qt::WhatsThisRole: {
                if (d->mode & ToolTips) {
                    return QString("<qt><b>%1</b><br>%2</qt>").arg(coll->text()).arg(coll->description());
                }
            } break;
            case Qt::CheckStateRole: {
                if (d->mode & UserCheckable) {
                    return coll->isEnabled() ? Qt::Checked : Qt::Unchecked;
                }
            } break;
            default: break;
            }
            return QVariant();
        }
    }
    return QVariant();
}

bool ActionCollectionModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_UNUSED(value);
    if (! index.isValid() /*|| ! (d->mode & UserCheckable)*/) {
        return false;
    }

    Action *act = action(index);
    if (act) {
        switch (role) {
        //case Qt::EditRole: act->setText( value.toString() ); break;
        case Qt::CheckStateRole: act->setEnabled(! act->isEnabled()); break;
        default: return false;
        }
        return false;
    }
    ActionCollection *coll = collection(index);
    if (coll) {
        switch (role) {
        //case Qt::EditRole: item->coll->setText( value.toString() ); break;
        case Qt::CheckStateRole: coll->setEnabled(! coll->isEnabled()); break;
        default: return false;
        }
        return false;
    }
    //emit dataChanged(index, index);
    return true;
}

bool ActionCollectionModel::insertRows(int row, int count, const QModelIndex &parent)
{
    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: row=" << row << " count=" << count;
    if (! parent.isValid()) {
        return false;
    }

    ActionCollection *coll = collection(parent);
    if (coll) {
        qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: parentindex is ActionCollection with name=" << coll->name();
    } else {
        Action *act = action(parent);
        if (act) {
            qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertRows: parentindex is Action with name=" << act->name();
        }
    }
    return QAbstractItemModel::insertRows(row, count, parent);
}

bool ActionCollectionModel::removeRows(int row, int count, const QModelIndex &parent)
{
    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::removeRows: row=" << row << " count=" << count;
    return QAbstractItemModel::removeRows(row, count, parent);
}

bool ActionCollectionModel::insertColumns(int column, int count, const QModelIndex &parent)
{
    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::insertColumns: column=" << column << " count=" << count;
    return QAbstractItemModel::insertColumns(column, count, parent);
}

bool ActionCollectionModel::removeColumns(int column, int count, const QModelIndex &parent)
{
    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::removeColumns: column=" << column << " count=" << count;
    return QAbstractItemModel::removeColumns(column, count, parent);
}

QStringList ActionCollectionModel::mimeTypes() const
{
    //krossdebug( QString("ActionCollectionModel::mimeTypes") );
    return QStringList() << "application/vnd.text.list";
}

QString fullPath(const QModelIndex &index)
{
    if (! index.isValid()) {
        return QString();
    }
    QString n;
    Action *a = ActionCollectionModel::action(index);
    if (a) {
        n = a->name();
    } else {
        ActionCollection *c = ActionCollectionModel::collection(index);
        if (c) {
            n = c->name() + '/';
            if (! n.endsWith('/')) {
                n += '/';
            }
        }
    }
    ActionCollection *par = static_cast<ActionCollection *>(index.internalPointer());
    for (ActionCollection *p = par; p != nullptr; p = par->parentCollection()) {
        QString s = p->name();
        if (! s.endsWith('/')) {
            s += '/';
        }
        n = s + n;
    }
    return n;
}

QMimeData *ActionCollectionModel::mimeData(const QModelIndexList &indexes) const
{
    //krossdebug( QString("ActionCollectionModel::mimeData") );
    QMimeData *mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);
    foreach (const QModelIndex &index, indexes) {
        //if( ! index.isValid() ) continue;
        //QString text = data(index, Qt::DisplayRole).toString();
        QString path = fullPath(index);
        if (! path.isNull()) {
            stream << path;
        }
    }

    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

bool ActionCollectionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: row=" << row << " col=" << column;
    if (action == Qt::IgnoreAction) {
        return true;
    }
    if (! data->hasFormat("application/vnd.text.list")) {
        return false;
    }
    if (column > 0) {
        return false;
    }

    qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: ENCODED DATA:";
    QByteArray encodedData = data->data("application/vnd.text.list");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QStringList newItems;
    int rows = 0;
    while (! stream.atEnd()) {
        QString text;
        stream >> text;
        newItems << text;
        qCDebug(KROSS_UI_LOG) << QString("  %1 \"%2\"").arg(rows).arg(text);
        ++rows;
    }

    //FIXME: return false for now since insertRows/removeRows need to be implemented before!
    //return false;

    /*
    int beginRow;
    if( row != -1 )
        beginRow = row;
    else if( parent.isValid() )
        beginRow = parent.row();
    else
        beginRow = rowCount( QModelIndex() );
    krossdebug( QString("ActionCollectionModel::dropMimeData: beginRow=%1").arg(beginRow) );
    */

    QModelIndex targetindex = index(row, column, parent);
    ActionCollection *coll = collection(targetindex);
    if (coll) {
        qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: parentindex is ActionCollection with name=" << coll->name();
    } else {
        Action *act = this->action(targetindex);
        if (act) {
            qCDebug(KROSS_UI_LOG) << "ActionCollectionModel::dropMimeData: parentindex is Action with name=" << act->name();
        }
    }
    return false;
    //return QAbstractItemModel::dropMimeData(data, action, row, column, parent);
}

Qt::DropActions ActionCollectionModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction | Qt::TargetMoveAction;
    //return Qt::CopyAction | Qt::MoveAction | Qt::TargetMoveAction | Qt::LinkAction;
}

/******************************************************************************
 * ActionCollectionProxyModel
 */

ActionCollectionProxyModel::ActionCollectionProxyModel(QObject *parent, ActionCollectionModel *model)
    : QSortFilterProxyModel(parent)
{
    setSourceModel(model ? model : new ActionCollectionModel(this));
    setFilterCaseSensitivity(Qt::CaseInsensitive);
    setDynamicSortFilter(true);
}

ActionCollectionProxyModel::~ActionCollectionProxyModel()
{
}

void ActionCollectionProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
{
    Q_ASSERT(dynamic_cast< ActionCollectionModel * >(sourceModel));
    QSortFilterProxyModel::setSourceModel(sourceModel);
}

bool ActionCollectionProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
    //krossdebug( QString( "ActionCollectionProxyModel::filterAcceptsRow: row=%1 parentrow=%2" ).arg( source_row ).arg( source_parent.row() ) );
    QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
    if (! index.isValid()) {
        return false;
    }

    Action *action = ActionCollectionModel::action(index);
    if (action) {
        return action->isEnabled() && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
    }
    ActionCollection *collection = ActionCollectionModel::collection(index);
    if (collection) {
        return collection->isEnabled();
    }
    return true;
}

