/*
 * contactlistdragmodel.cpp - ContactListModel with support for Drag'n'Drop operations
 * Copyright (C) 2008-2010  Yandex LLC (Michail Pishchagin)
 *
 * This program 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.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "contactlistdragmodel.h"

#include "common.h"
#include "contactlistitem.h"
#include "contactlistmodelselection.h"
#include "debug.h"
#include "psiaccount.h"
#include "psicontact.h"
#include "psicontactlist.h"
#include "psioptions.h"

#include <QTimer>
#include <QtAlgorithms>

//----------------------------------------------------------------------------
// ContactListModelOperationList
//----------------------------------------------------------------------------

ContactListModelOperationList::ContactListModelOperationList(Action action) : action_(action) { }

ContactListModelOperationList::ContactListModelOperationList(Qt::DropAction action)
{
    if (action == Qt::CopyAction)
        action_ = Copy;
    else if (action == Qt::MoveAction)
        action_ = Move;
    else {
        Q_ASSERT_X(false, "ContactListModelOperationList", "Passed incorrect Qt::DropAction");
    }
}

ContactListModelOperationList::Action ContactListModelOperationList::action() const { return action_; }

void ContactListModelOperationList::addOperation(PsiContact *contact, const QString &groupFrom, const QString &groupTo)
{
    if (!contact) {
        qWarning("ContactListModelOperationList::addOperation(): contact is NULL");
        return;
    }

    if (!contact->isEditable()) {
        bool deleteOperation = contact->isRemovable() && groupTo.isEmpty();
        if (!deleteOperation) {
            qWarning("ContactListModelOperationList::addOperation(): contact is not editable '%s'",
                     qPrintable(contact->jid().full()));
            return;
        }
    }

    if (!contact->groupOperationPermitted(groupFrom, groupTo)) {
        qWarning("ContactListModelOperationList::addOperation(): contact '%s' refused group operation ('%s' -> '%s')",
                 qPrintable(contact->jid().full()), qPrintable(groupFrom), qPrintable(groupTo));
        return;
    }

    Operation op(groupFrom, groupTo);

    if (!operations_.contains(contact))
        operations_[contact] = QList<Operation>();

    operations_[contact] += op;
}

QList<ContactListModelOperationList::ContactOperation> ContactListModelOperationList::operations() const
{
    QList<ContactOperation> result;

    QHash<PsiContact *, QList<Operation>>::const_iterator it;
    for (it = operations_.constBegin(); it != operations_.constEnd(); ++it) {
        ContactOperation op;
        op.contact    = it.key();
        op.operations = it.value();
        result += op;
    }

    return result;
}

void ContactListModelOperationList::removeAccidentalContactMoveOperations()
{
    if (action_ != Move)
        return;

    QList<PsiContact *> contacts = operations_.keys();
    for (PsiContact *psiContact : contacts) {
        bool remove = false;
        for (const Operation &op : qAsConst(operations_[psiContact])) {
            if (psiContact->groups().contains(op.groupTo)) {
                remove = true;
                break;
            }
        }

        if (remove)
            operations_.remove(psiContact);
    }
}

//----------------------------------------------------------------------------
// ContactListDragModel
//----------------------------------------------------------------------------

ContactListDragModel::ContactListDragModel(PsiContactList *contactList, QObject *parent) :
    ContactListModel(contactList, parent)
{
}

Qt::DropActions ContactListDragModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; }

Qt::DropActions ContactListDragModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; }

Qt::ItemFlags ContactListDragModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags f = ContactListModel::flags(index);

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

    if (item) {
        return f | Qt::ItemIsDropEnabled | (item->isDragEnabled() ? Qt::ItemIsDragEnabled : f);
    }

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

    return f;
}

QStringList ContactListDragModel::mimeTypes() const
{
    return QStringList() << ContactListModelSelection::mimeType() << "text/plain";
}

QMimeData *ContactListDragModel::mimeData(const QModelIndexList &indexes) const
{
    QList<ContactListItem *> items;

    for (const auto &index : indexes) {
        if (!index.isValid())
            continue;

        ContactListItem *item = toItem(index);
        if (!item)
            continue;

        items << item;
    }

    return new ContactListModelSelection(items);
}

QModelIndexList ContactListDragModel::indexesFor(const QMimeData *data) const
{
    QModelIndexList           result;
    ContactListModelSelection selection(data);
    if (!selection.haveRosterSelection() || !contactList())
        return result;

    const auto &contacts = selection.contacts();
    for (const ContactListModelSelection::Contact &contact : contacts) {
        PsiAccount *account = contactList()->getAccount(contact.account);
        if (!account)
            continue;
        PsiContact *psiContact = account->findContact(contact.jid);
        if (!psiContact) {
            if (account->selfContact()->jid() == contact.jid)
                psiContact = account->selfContact();
        }

        if (!psiContact)
            continue;

        QModelIndexList indexes = ContactListModel::indexesFor(psiContact);
        result += indexes;
    }

    const auto &groups = selection.groups();
    for (const ContactListModelSelection::Group &group : groups) {
        ContactListItem *item  = static_cast<ContactListItem *>(root())->findGroup(group.fullName);
        QModelIndex      index = this->toModelIndex(item);
        if (!result.contains(index))
            result << index;
    }

    //    for (ContactListModelSelection::Account account: selection.accounts()) {
    //        PsiAccount* acc = contactList()->getAccount(account.id);
    //        if (!acc)
    //            continue;
    //        ContactListAccountGroup* rootGroup = dynamic_cast<ContactListAccountGroup*>(this->rootGroup());
    //        if (!rootGroup)
    //            continue;
    //        ContactListGroup* accountGroup = rootGroup->findAccount(acc);
    //        QModelIndex index = groupToIndex(accountGroup);
    //        if (!result.contains(index))
    //            result << index;
    //    }

    return result;
}

bool ContactListDragModel::supportsMimeDataOnIndex(const QMimeData *data, const QModelIndex &parent) const
{
    if ((!groupsEnabled() && !accountsEnabled()) || !ContactListModelSelection(data).haveRosterSelection()) {
        return false;
    }

    // disable dragging to special groups
    ContactListItem *item  = toItem(parent);
    ContactListItem *group = nullptr;

    if (item->isGroup())
        group = item;
    else if (item->parent() && item->parent()->isGroup())
        group = item->parent();

    if (group && !group->isEditable())
        return false;

    const auto &indexes = indexesFor(data);
    for (const QModelIndex &index : indexes) {
        if (index == parent) {
            return false;
        }

        // protection against dragging parent group inside its child
        ContactListItem *item = toItem(index);
        if (item->isContact() && accountsEnabled()) {

            // disable dragging to self contacts
            ContactListItem *parentItem = toItem(parent);
            if (parentItem->isContact() && parentItem->contact()->isSelf())
                return false;

            // disable dragging to different accounts and to self account
            ContactListItem *accountItem = parentItem;
            while (!accountItem->isRoot() && !accountItem->isAccount()) {
                accountItem = accountItem->parent();
            }

            if (accountItem->isRoot())
                return false;

            return accountItem != parentItem && accountItem->account() == item->account();
        }
    }

    return true;
}

bool ContactListDragModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
                                        const QModelIndex &parent)
{
    Q_UNUSED(row);
    Q_UNUSED(column);

    ContactListModelSelection selection(data);
    if (!selection.haveRosterSelection() || !contactList())
        return false;

    const auto &contacts = selection.contacts();
    for (const ContactListModelSelection::Contact &contact : contacts) {
        PsiAccount *account    = contactList()->getAccount(contact.account);
        PsiContact *psiContact = account ? account->findContact(contact.jid) : nullptr;

        if (psiContact) {
            QStringList groups;
            if (action == Qt::DropAction::CopyAction && !psiContact->noGroups()) {
                groups = psiContact->groups();
            }
            groups << getDropGroupName(parent);
            psiContact->setGroups(groups);
        }
    }

    return true;
}

void ContactListDragModel::renameGroup(ContactListItem *group, const QString &newName)
{
    Q_UNUSED(group);
    Q_UNUSED(newName);
}

PsiAccount *ContactListDragModel::getDropAccount(PsiAccount *account, const QModelIndex &parent) const
{
    Q_UNUSED(account);

    if (!parent.isValid())
        return nullptr;

    ContactListItem *item = static_cast<ContactListItem *>(parent.internalPointer());
    Q_ASSERT(item);
    if (!item)
        return nullptr;

    if (item->isContact()) {
        return item->contact()->account();
    } else if (item->isAccount()) {
        return item->account();
    }

    return nullptr;
}

QString ContactListDragModel::getDropGroupName(const QModelIndex &parent) const
{
    if (!parent.isValid())
        return QString();

    ContactListItem *item = static_cast<ContactListItem *>(parent.internalPointer());
    Q_ASSERT(item);
    if (!item)
        return nullptr;

    if (item->isGroup()) {
        return item->name();
    } else if (item->isContact() && item->parent()->isGroup()) {
        return item->parent()->name();
    }

    return QString();
}

QString ContactListDragModel::sourceOperationsForContactGroup(const QString &groupName, PsiContact *contact) const
{
    Q_UNUSED(groupName);
    Q_UNUSED(contact);

    return QString();
}

QString ContactListDragModel::destinationOperationsForContactGroup(const QString &groupName, PsiContact *contact) const
{
    Q_UNUSED(groupName);
    Q_UNUSED(contact);

    return QString();
}

QModelIndexList ContactListDragModel::indexesFor(PsiContact *contact, QMimeData *contactSelection) const
{
    QModelIndexList indexes;
    if (contactSelection) {
        indexes += indexesFor(contactSelection);
    }
    if (indexes.isEmpty() && contact) {
        indexes += ContactListModel::indexesFor(contact);
    }
    return indexes;
}
