/*
 * mucaffiliationsmodel.cpp
 * Copyright (C) 2006  Remko Troncon
 *
 * 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 "mucaffiliationsmodel.h"

#include <QFont>
#include <QIODevice>
#include <QMimeData>
#include <QVariant>

using namespace XMPP;

MUCAffiliationsModel::MUCAffiliationsModel() : QStandardItemModel(Unknown, 2)
{
    QFont font;
    font.setBold(true);
    QVariant font_variant = QVariant::fromValue(font);
    for (int i = 0; i < Unknown; i++) {
        QModelIndex ind = index(i, 0, QModelIndex());
        setData(ind, QVariant(affiliationlistindexToString(AffiliationListIndex(i))));
        setData(ind, font_variant, Qt::FontRole);
        insertColumns(0, 1, ind);
        insertColumns(1, 1, ind);
        enabled_[AffiliationListIndex(i)] = false;
    }
    setHorizontalHeaderLabels(QStringList() << tr("JID") << tr("Reason"));
}

Qt::ItemFlags MUCAffiliationsModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags a;
    if (!index.parent().isValid()) {
        // List headers
        if (enabled_[AffiliationListIndex(index.row())]) {
            a |= Qt::ItemIsDropEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
        }
    } else {
        a |= Qt::ItemIsDropEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled;
    }
    return a;
}

Qt::DropActions MUCAffiliationsModel::supportedDropActions() const { return Qt::MoveAction; }

bool MUCAffiliationsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int,
                                        const QModelIndex &parent)
{
    if ((parent.isValid() && parent.column() != 0) || !data || action != Qt::MoveAction
        || !(data->hasFormat("application/vnd.text.list") || data->hasFormat("text/plain"))) {
        return false;
    }

    // Decode the data
    QStringList newItems;
    int         nb_rows = 0;
    if (data->hasFormat("application/vnd.text.list")) {
        QByteArray  encodedData = data->data("application/vnd.text.list");
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        while (!stream.atEnd()) {
            QString text;
            stream >> text;
            newItems << text;
            nb_rows++;
        }
    } else if (data->hasFormat("text/plain")) {
        QString item(data->data("text/plain"));
        if (Jid(item).isValid()) {
            newItems += item;
            nb_rows++;
        }
    }

    if (nb_rows == 0)
        return false;

    // Determine the correct index
    QModelIndex real_index;
    if (parent.isValid()) {
        real_index = (parent.parent().isValid() ? parent.parent() : parent);
    } else {
        if (row > 0)
            real_index = index(row - 1, 0, parent);
        else if (row == 0)
            real_index = index(0, 0, parent);
        else
            real_index = index(Outcast, 0, parent);
    }
    int real_row = rowCount(real_index);

    // Insert the data
    insertRows(real_row, nb_rows, real_index);
    for (const QString &text : std::as_const(newItems)) {
        QModelIndex idx = index(real_row, 0, real_index);
        setData(idx, text);
        real_row++;
    }

    return true;
}

QStringList MUCAffiliationsModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    types << "text/plain";
    return types;
}

QMimeData *MUCAffiliationsModel::mimeData(const QModelIndexList &indexes) const
{
    QMimeData  *mimeData = new QMimeData();
    QByteArray  encodedData;
    QDataStream stream(&encodedData, QIODevice::WriteOnly);
    for (const QModelIndex &index : indexes) {
        if (index.isValid() && index.column() == 0) {
            QString text = data(index, Qt::DisplayRole).toString();
            stream << text;
        }
    }
    mimeData->setData("application/vnd.text.list", encodedData);
    return mimeData;
}

void MUCAffiliationsModel::resetAffiliationLists()
{
    items_.clear();
    resetAffiliationList(MUCItem::Outcast);
    resetAffiliationList(MUCItem::Member);
    resetAffiliationList(MUCItem::Admin);
    resetAffiliationList(MUCItem::Owner);
}

void MUCAffiliationsModel::resetAffiliationList(MUCItem::Affiliation a)
{
    emit layoutAboutToBeChanged();
    enabled_[AffiliationListIndex(affiliationToIndex(a))] = false;
    QModelIndex index                                     = affiliationListIndex(a);
    if (hasChildren(index)) {
        removeRows(0, rowCount(index), index);
    }
    emit layoutChanged();
}

void MUCAffiliationsModel::setAffiliationListEnabled(MUCItem::Affiliation a, bool b)
{
    emit        layoutAboutToBeChanged();
    QModelIndex index                           = affiliationListIndex(a);
    enabled_[AffiliationListIndex(index.row())] = b;
    emit layoutChanged();
}

QString MUCAffiliationsModel::affiliationlistindexToString(AffiliationListIndex list)
{
    if (list == Members)
        return tr("Members");
    else if (list == Admins)
        return tr("Administrators");
    else if (list == Owners)
        return tr("Owners");
    else if (list == Outcast)
        return tr("Banned");
    return QString();
}

QModelIndex MUCAffiliationsModel::affiliationListIndex(MUCItem::Affiliation a)
{
    AffiliationListIndex i = affiliationToIndex(a);
    if (i == Unknown) {
        qWarning("mucconfigdlg.cpp: Unexpected affiliation");
        return QModelIndex();
    }
    return index(i, 0, QModelIndex());
}

MUCAffiliationsModel::AffiliationListIndex MUCAffiliationsModel::affiliationToIndex(MUCItem::Affiliation a)
{
    if (a == MUCItem::Member)
        return Members;
    else if (a == MUCItem::Admin)
        return Admins;
    else if (a == MUCItem::Owner)
        return Owners;
    else if (a == MUCItem::Outcast)
        return Outcast;
    else
        return Unknown;
}

void MUCAffiliationsModel::addItems(const QList<MUCItem> &items)
{
    bool dirty = false;
    for (const MUCItem &item : items) {
        QModelIndex list = affiliationListIndex(item.affiliation());
        if (list.isValid() && !item.jid().isEmpty()) {
            if (!dirty) {
                emit layoutAboutToBeChanged();
            }
            int row = rowCount(list);
            if (row == 0) {
                enabled_[AffiliationListIndex(list.row())] = true;
            }
            insertRows(row, 1, list);
            setData(index(row, 0, list), QVariant(item.jid().full()));
            setData(index(row, 1, list), QVariant(item.reason()));
            MUCItem i(MUCItem::UnknownRole, item.affiliation());
            i.setJid(item.jid());
            items_ += i;
            dirty = true;
        } else {
            qDebug("Unexpected item");
        }
    }
    if (dirty)
        emit layoutChanged();
}

QList<MUCItem> MUCAffiliationsModel::changes() const
{
    QList<MUCItem> items_old = items_;
    QList<MUCItem> items_delta;

    // Add all new items
    for (int i = 0; i < rowCount(QModelIndex()); i++) {
        QModelIndex list = index(i, 0, QModelIndex());
        for (int j = 0; j < rowCount(list); j++) {
            Jid     jid(data(index(j, 0, list)).toString());
            MUCItem item(MUCItem::UnknownRole, indexToAffiliation(i));
            item.setJid(jid);
            if (!items_.contains(item)) {
                items_delta += item;
            } else
                items_old.removeAll(item);
        }
    }

    // Remove all old items not present in the delta
    for (const MUCItem &item_old : items_old) {
        bool found = false;
        for (const MUCItem &item_new : std::as_const(items_delta)) {
            if (item_new.jid().compare(item_old.jid(), false)) {
                found = true;
                break;
            }
        }
        if (!found) {
            MUCItem item(MUCItem::UnknownRole, MUCItem::NoAffiliation);
            item.setJid(item_old.jid());
            items_delta += item;
        }
    }

    return items_delta;
}

MUCItem::Affiliation MUCAffiliationsModel::indexToAffiliation(int li)
{
    if (li == Members)
        return MUCItem::Member;
    else if (li == Admins)
        return MUCItem::Admin;
    else if (li == Owners)
        return MUCItem::Owner;
    else if (li == Outcast)
        return MUCItem::Outcast;
    else
        return MUCItem::UnknownAffiliation;
}
