/*
   Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz>

   This library 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 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 "hotkeys_model.h"

#include "action_data/simple_action_data.h"
#include "action_data/action_data_group.h"

#include <typeinfo>

#include <QMimeData>

#include <KDE/KDebug>
#include <KDE/KLocale>
#include <KDE/KIcon>

static KHotKeys::ActionDataBase *findElement(
        void *ptr
        ,KHotKeys::ActionDataGroup *root)
    {
    Q_ASSERT(root);
    if (!root) return NULL;

    KHotKeys::ActionDataBase *match = NULL;

    Q_FOREACH( KHotKeys::ActionDataBase *element, root->children())
        {
        if (ptr == element)
            {
            match = element;
            break;
            }

        if (KHotKeys::ActionDataGroup *subGroup = dynamic_cast<KHotKeys::ActionDataGroup*>(element))
            {
            match = findElement(ptr, subGroup);
            if (match) break;
            }
        }

    return match;
    }



KHotkeysModel::KHotkeysModel( QObject *parent )
    : QAbstractItemModel(parent)
     ,_settings()
     ,_actions(0)
    {}


KHotkeysModel::~KHotkeysModel()
    {
    }


QModelIndex KHotkeysModel::addGroup( const QModelIndex & parent )
    {
    KHotKeys::ActionDataGroup *list;
    if (parent.isValid())
        {
        list = indexToActionDataGroup(parent);
        }
    else
        {
        list = _actions;
        }
    Q_ASSERT(list);

    beginInsertRows( parent, list->size(), list->size() );

    /* KHotKeys:: ActionDataGroup *action = */
    new KHotKeys::ActionDataGroup( list, i18n("New Group"), i18n("Comment"));

    endInsertRows();
    return index( list->size()-1, NameColumn, parent );
    }


// Add a group
QModelIndex KHotkeysModel::insertActionData(  KHotKeys::ActionDataBase *data, const QModelIndex & parent )
    {
    Q_ASSERT(data);

    KHotKeys::ActionDataGroup *list;
    if (parent.isValid())
        {
        list = indexToActionDataGroup(parent);
        }
    else
        {
        list = _actions;
        }
    Q_ASSERT(list);

    beginInsertRows( parent, list->size(), list->size() );

    data->reparent(list);

    endInsertRows();
    return index( list->size()-1, NameColumn, parent );
    }


int KHotkeysModel::columnCount( const QModelIndex & ) const
    {
    return 2;
    }


QVariant KHotkeysModel::data( const QModelIndex &index, int role ) const
    {
    // Check that the index is valid
    if (!index.isValid())
        {
        return QVariant();
        }

    // Get the item behind the index
    KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
    Q_ASSERT(action);

    // Handle CheckStateRole
    if (role==Qt::CheckStateRole)
        {
        switch(index.column())
            {
            case 1:
                // If the parent is enabled we display the state of the object.
                // If the parent is disabled this object is disabled too.
                if (action->parent() && !action->parent()->enabled())
                    {
                    return Qt::Unchecked;
                    }
                return action->enabled()
                    ? Qt::Checked
                    : Qt::Unchecked;

            default:
                return QVariant();
            }
        }

    // Display and Tooltip. Tooltip displays the complete name. That's nice if
    // there is not enough space
    if (role==Qt::DisplayRole || role==Qt::ToolTipRole)
        {
        switch (index.column())
            {
            case 0:
                return action->name();

            case 1:
                return QVariant();

            case 2:
                return indexToActionDataGroup(index)!=0;

            case 3:
                {
                const std::type_info &ti = typeid(*action);
                if (ti==typeid(KHotKeys::SimpleActionData))
                    return KHotkeysModel::SimpleActionData;
                else if (ti==typeid(KHotKeys::ActionDataGroup))
                    return KHotkeysModel::ActionDataGroup;
                else
                    return KHotkeysModel::Other;
                }

            default:
                return QVariant();
            }
        }

    // Decoration role
    if (role==Qt::DecorationRole)
        {
        switch (index.column())
            {
            case 0:
                return dynamic_cast<KHotKeys::ActionDataGroup*>(action)
                    ? KIcon("folder")
                    : QVariant();

            default:
                return QVariant();
            }
        }


    // For everything else
    return QVariant();
    }


bool KHotkeysModel::dropMimeData(
        const QMimeData *data
        ,Qt::DropAction action
        ,int row
        ,int column
        ,const QModelIndex &parent)
    {
    kDebug()
        << parent.data(Qt::DisplayRole) << ","
        << row << ","
        << column;

    // We only support move actions and our own mime type
    if ( (action!=Qt::CopyAction)
            || !data->hasFormat("application/x-pointer"))
        {
        kDebug() << "Drop not supported " << data->formats();
        return false;
        }

    // Decode the stream
    QByteArray encodedData = data->data("application/x-pointer");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QList<quintptr> ptrs;
    while (!stream.atEnd())
        {
        quintptr ptr;
        stream >> ptr;
        ptrs << ptr;
        }

    // No pointers, nothing to do
    if (ptrs.empty()) return false;

    // Get the group we have to drop into
    QModelIndex dropIndex = parent;
    KHotKeys::ActionDataGroup *dropToGroup = indexToActionDataGroup(dropIndex);
    if (!dropToGroup)
        {
        dropIndex = parent.parent();
        dropToGroup = indexToActionDataGroup(dropIndex);
        }

    kDebug() << "dropping to " << dropToGroup->name();

    // Do the moves
    Q_FOREACH(quintptr ptr, ptrs)
        {
        KHotKeys::ActionDataBase *element = findElement(
                reinterpret_cast<void*>(ptr),
                _actions);

        if (element) moveElement(element, dropToGroup);
        }

    return true;
    }


void KHotkeysModel::emitChanged(KHotKeys::ActionDataBase *item)
    {
    Q_ASSERT( item );

    KHotKeys::ActionDataGroup *parent = item->parent();
    QModelIndex topLeft;
    QModelIndex bottomRight;
    if (!parent)
        {
        topLeft = createIndex( 0, 0, _actions );
        bottomRight = createIndex( 0, 0, _actions );
        }
    else
        {
        int row = parent->children().indexOf(item);
        topLeft = createIndex( row, 0, parent );
        bottomRight = createIndex( row, columnCount(topLeft), parent );
        }

    emit dataChanged( topLeft, bottomRight );
    }


Qt::ItemFlags KHotkeysModel::flags( const QModelIndex &index ) const
    {
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);

    if (!index.isValid())
        {
            return flags | Qt::ItemIsDropEnabled;
        }

    switch (index.column())
        {
        case 1:
            return flags
                | Qt::ItemIsUserCheckable
                | Qt::ItemIsDragEnabled
                | Qt::ItemIsDropEnabled;

        default:
            return flags
                | Qt::ItemIsEditable
                | Qt::ItemIsDragEnabled
                | Qt::ItemIsDropEnabled;
        }
    }


// Get header data for section
QVariant KHotkeysModel::headerData( int section, Qt::Orientation, int role ) const
    {
    if (role!=Qt::DisplayRole)
        {
        return QVariant();
        }

    switch (section)
        {
        case 0:
            return QVariant(i18n("Name"));

        case 1:
            return QVariant(i18n("Enabled"));

        case 2:
            return QVariant(i18n("Type"));

        default:
            return QVariant();
        }
    }


QModelIndex KHotkeysModel::index( int row, int column, const QModelIndex &parent ) const
    {
    KHotKeys::ActionDataGroup *actionGroup = indexToActionDataGroup(parent);
    if (!actionGroup || row>=actionGroup->children().size() )
        {
        return QModelIndex();
        }

    KHotKeys::ActionDataBase *action =  actionGroup->children().at(row);
    Q_ASSERT( action );
    return createIndex( row, column, action );
    }


// Convert index to ActionDataBase
KHotKeys::ActionDataBase *KHotkeysModel::indexToActionDataBase( const QModelIndex &index ) const
    {
    if (!index.isValid())
        {
        return _actions;
        }
    return static_cast<KHotKeys::ActionDataBase*>( index.internalPointer() );
    }


// Convert index to ActionDataGroup
KHotKeys::ActionDataGroup *KHotkeysModel::indexToActionDataGroup( const QModelIndex &index ) const
    {
    if (!index.isValid())
        {
        return _actions;
        }
    return dynamic_cast<KHotKeys::ActionDataGroup*>( indexToActionDataBase(index) );
    }


void KHotkeysModel::load()
    {
    _settings.read_settings(true);
    _actions = _settings.actions();
    reset();
    }


QMimeData *KHotkeysModel::mimeData(const QModelIndexList &indexes) const
    {
    QMimeData * mimeData = new QMimeData();
    QByteArray encodedData;

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

    Q_FOREACH (QModelIndex index, indexes)
        {
        if (index.isValid() and index.column() == 0)
            {
            KHotKeys::ActionDataBase *element = indexToActionDataBase(index);
            // We use the pointer as id.
            stream << reinterpret_cast<quintptr>(element);
            }
        }

    mimeData->setData("application/x-pointer", encodedData);
    return mimeData;
    }


QStringList KHotkeysModel::mimeTypes() const
    {
    QStringList types;
    types << "application/x-pointer";
    return types;
    }


bool KHotkeysModel::moveElement(
        KHotKeys::ActionDataBase *element
        ,KHotKeys::ActionDataGroup *newGroup
        ,int position)
    {
    Q_ASSERT(element && newGroup);
    if (!element || !newGroup) return false;

    // TODO: Make this logic more advanced
    // We do not allow moving into our systemgroup
    if (newGroup->is_system_group()) return false;

    // Make sure we don't move a group to one of it's children or
    // itself.
    KHotKeys::ActionDataGroup *tmp = newGroup;
    do  {
        if (tmp == element)
            {
            kDebug() << "Forbidden move" << tmp->name();
            return false;
            }
        }
        while((tmp = tmp->parent()));

    KHotKeys::ActionDataGroup *oldParent = element->parent();

    // TODO: Make this logic more advanced
    // We do not allow moving from our systemgroup
    if (oldParent->is_system_group()) return false;

    // Remove it from it's current place
    kDebug() << "Removing from";
    kDebug() << KHotkeysModel::data(createIndex(0, 0, oldParent), Qt::DisplayRole);
    kDebug() << "item " << oldParent->children().indexOf(element);
    kDebug() << "from " << oldParent->children().size();
    beginRemoveRows(
            createIndex(0, 0, oldParent),
            oldParent->children().indexOf(element),
            oldParent->children().indexOf(element));
    element->reparent(0);
    endRemoveRows();

    // Add to the new
    kDebug() << "Adding to";
    kDebug() << KHotkeysModel::data(createIndex(0, 0, newGroup), Qt::DisplayRole);
    kDebug() << "from " << newGroup->children().size();
    beginInsertRows(
            createIndex(0, 0, newGroup),
            newGroup->children().size(),
            newGroup->children().size());
    element->reparent(newGroup);
    endInsertRows();

    return true;
    }


// Get parent object for index
QModelIndex KHotkeysModel::parent( const QModelIndex &index ) const
    {
    KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
    if (!action)
        {
        return QModelIndex();
        }

    KHotKeys::ActionDataGroup *parent = action->parent();
    if (!parent)
        {
        return QModelIndex();
        }

    KHotKeys::ActionDataGroup *grandparent = parent->parent();
    if (!grandparent)
        {
        return QModelIndex();
        }

    int row = grandparent->children().indexOf(parent);
    return createIndex( row, 0, parent );
    }


// Remove rows ( items )
bool KHotkeysModel::removeRows( int row, int count, const QModelIndex &parent )
    {
    Q_ASSERT( count == 1 );

    beginRemoveRows( parent, row, row+count-1 );

    KHotKeys::ActionDataGroup *list;
    if (parent.isValid())
        {
        list = indexToActionDataGroup(parent);
        }
    else
        {
        list = _actions;
        }
    Q_ASSERT(list);

    KHotKeys::ActionDataBase *action = indexToActionDataBase(index(row,0,parent));

    action->aboutToBeErased();
    delete action;

    endRemoveRows();
    return true;
    }


// Number of rows for index
int KHotkeysModel::rowCount( const QModelIndex &index ) const
    {
    KHotKeys::ActionDataGroup *group = indexToActionDataGroup(index);
    if (!group)
        {
        return 0;
        }

    return group->children().count();
    }


void KHotkeysModel::save()
    {
    _settings.write_settings();
    }


// Set data
bool KHotkeysModel::setData( const QModelIndex &index, const QVariant &value, int role )
    {

    if ( !index.isValid() || role != Qt::EditRole )
        {
        return false;
        }

    KHotKeys::ActionDataBase *action = indexToActionDataBase(index);
    Q_ASSERT( action );

    switch ( index.column() )
        {
        case NameColumn:
            {
            action->set_name( value.toString() );
            }
            break;

        default:
            return false;
        }

    emit dataChanged( index, index );
    return true;
    }

#include "moc_hotkeys_model.cpp"
