/*
    Kopete Contactlist Model

    Copyright (c) 2007      by Aleix Pol              <aleixpol@gmail.com>
    Copyright (c) 2008      by Matt Rogers            <mattr@kde.org>
    Copyright (c) 2009      by Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net>
    Copyright     2009      by Roman Jarosz           <kedgedev@gmail.com>

    Kopete    (c) 2002-2009 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * 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.                                   *
    *                                                                       *
    *************************************************************************
*/

#include "contactlistplainmodel.h"

#include <QMimeData>
#include <QDomDocument>
#include <QDomElement>

#include "kopeteaccount.h"
#include "kopetemetacontact.h"
#include "kopetecontact.h"
#include "kopetecontactlist.h"
#include "kopeteitembase.h"
#include "kopeteappearancesettings.h"

namespace Kopete {
namespace UI {
ContactListPlainModel::ContactListPlainModel(QObject *parent)
    : ContactListModel(parent)
{
}

ContactListPlainModel::~ContactListPlainModel()
{
    saveModelSettings(QStringLiteral("Plain"));
}

void ContactListPlainModel::addMetaContact(Kopete::MetaContact *mc)
{
    ContactListModel::addMetaContact(mc);
    addMetaContactImpl(mc);
}

void ContactListPlainModel::addMetaContactImpl(Kopete::MetaContact *mc)
{
    int mcIndex = m_contacts.indexOf(mc);
    int mcDesireIndex = m_contacts.count();

    // If we use manual sorting we most likely will have possition where the metaContact should be inserted.
    if (m_manualMetaContactSorting) {
        if (m_addContactPosition.contains(mc)) {
            mcDesireIndex = m_addContactPosition.value(mc);
            m_addContactPosition.remove(mc);
        }
    }

    // Check if mcDesireIndex isn't invalid (shouldn't happen)
    if (mcDesireIndex < 0 || mcDesireIndex > m_contacts.count()) {
        mcDesireIndex = m_contacts.count();
    }

    if (mcIndex != -1) {
        // If the manual index is the same do nothing otherwise change possition
        if (mcIndex == mcDesireIndex) {
            return;
        }

        // We're moving metaContact so temporary remove it so model is aware of the change.
        beginRemoveRows(QModelIndex(), mcIndex, mcIndex);
        m_contacts.removeAt(mcIndex);
        endRemoveRows();

        // If mcDesireIndex was after mcIndex decrement it because we have removed metaContact
        if (mcIndex < mcDesireIndex) {
            mcDesireIndex--;
        }
    }

    beginInsertRows(QModelIndex(), mcDesireIndex, mcDesireIndex);
    m_contacts.insert(mcDesireIndex, mc);
    endInsertRows();
}

void ContactListPlainModel::removeMetaContact(Kopete::MetaContact *mc)
{
    ContactListModel::removeMetaContact(mc);

    // if the mc is not on the list anymore, just returns
    int offset = m_contacts.indexOf(mc);
    if (offset == -1) {
        return;
    }

    beginRemoveRows(QModelIndex(), offset, offset);
    m_contacts.removeAt(offset);
    endRemoveRows();
}

int ContactListPlainModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_contacts.count();
    } else {
        return 0;
    }
}

bool ContactListPlainModel::hasChildren(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return !m_contacts.isEmpty();
    } else {
        return false;
    }
}

QModelIndex ContactListPlainModel::index(int row, int column, const QModelIndex &parent) const
{
    if (row < 0 || row >= rowCount(parent)) {
        return QModelIndex();
    }

    QModelIndex idx;
    if (!parent.isValid()) {
        return createIndex(row, column, m_contacts[row]);
    } else {
        return QModelIndex();
    }
}

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

    using namespace Kopete;

    /* do all the casting up front. I need to profile to see how expensive this is though */
    Kopete::ContactListElement *cle = static_cast<Kopete::ContactListElement *>(index.internalPointer());
    Kopete::MetaContact *mc = dynamic_cast<Kopete::MetaContact *>(cle);

    return metaContactData(mc, role);
}

Qt::ItemFlags ContactListPlainModel::flags(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return (m_manualMetaContactSorting) ? Qt::ItemIsDropEnabled : Qt::NoItemFlags;
    }

    Qt::ItemFlags f(Qt::ItemIsEnabled | Qt::ItemIsSelectable);

    // if it is a contact item, add the selectable flag
    if (index.data(Kopete::Items::TypeRole) == Kopete::Items::MetaContact) {
        // TODO: for now we are only allowing drag-n-drop of a
        // metacontact if all the accounts its contacts belong are online
        Kopete::ContactListElement *cle = static_cast<Kopete::ContactListElement *>(index.internalPointer());
        Kopete::MetaContact *mc = dynamic_cast<Kopete::MetaContact *>(cle);
        if (mc) {
            f |= Qt::ItemIsEditable;
            bool online = true;
            foreach (Kopete::Contact *c, mc->contacts()) {
                if (!c->account()->isConnected()) {
                    online = false;
                    break;
                }
            }
            if (online) {
                f = f | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
            }
        }
    }

    return f;
}

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

    // for now only accepting drop of metacontacts
    // TODO: support dropping of files in metacontacts to allow file transfers
    if (!data->hasFormat(QStringLiteral("application/kopete.metacontacts.list")) && !data->hasUrls()) {
        return false;
    }

    // contactlist has only one column
    if (column > 0) {
        return false;
    }

    if (data->hasUrls()) {
        return dropUrl(data, row, parent, action);
    } else if (data->hasFormat(QStringLiteral("application/kopete.metacontacts.list"))) {
        // decode the mime data
        QByteArray encodedData = data->data(QStringLiteral("application/kopete.metacontacts.list"));
        QDataStream stream(&encodedData, QIODevice::ReadOnly);
        QList<GroupMetaContactPair> items;

        while (!stream.atEnd())
        {
            QString line;
            stream >> line;

            QStringList entry = line.split(QStringLiteral("/"));

            QString grp = entry[0];
            QString id = entry[1];

            GroupMetaContactPair pair;
            pair.first = Kopete::ContactList::self()->group(grp.toUInt());
            pair.second = Kopete::ContactList::self()->metaContact(QUuid(id).toString());
            items.append(pair);
        }

        return dropMetaContacts(row, parent, action, items);
    }

    return false;
}

bool ContactListPlainModel::dropMetaContacts(int row, const QModelIndex &parent, Qt::DropAction action, const QList<GroupMetaContactPair> &items)
{
    if (items.isEmpty()) {
        return false;
    }

    if (ContactListModel::dropMetaContacts(row, parent, action, items)) {
        return true;
    }

    if (!parent.isValid()) {
        for (int i = 0; i < items.count(); ++i) {
            if (m_manualMetaContactSorting) {
                GroupMetaContactPair pair = items.at(i);
                m_addContactPosition.insert(pair.second, row + i);
                addMetaContactImpl(pair.second);
            }
        }
        return true;
    }

    return false;
}

QModelIndex ContactListPlainModel::parent(const QModelIndex &) const
{
    return QModelIndex();
}

QModelIndexList ContactListPlainModel::indexListFor(Kopete::ContactListElement *cle) const
{
    QModelIndexList indexList;
    Kopete::MetaContact *mc = dynamic_cast<Kopete::MetaContact *>(cle);

    // Contact list doesn't contain myself account contact so ignore it
    if (mc && mc != Kopete::ContactList::self()->myself()) {
        int mcPos = m_contacts.indexOf(mc);
        if (mcPos != -1) {
            QModelIndex mcIndex = index(mcPos, 0);
            if (mcIndex.isValid()) {
                indexList.append(mcIndex);
            }
        }
    }

    return indexList;
}

void ContactListPlainModel::handleContactDataChange(Kopete::MetaContact *mc)
{
    // get all the indexes for this metacontact
    QModelIndexList indexList = indexListFor(mc);

    // and now notify all the changes
    foreach (QModelIndex index, indexList) {
        emit dataChanged(index, index);
    }
}

void ContactListPlainModel::appearanceConfigChanged()
{
    AppearanceSettings *as = AppearanceSettings::self();
    bool manualGroupSorting = (as->contactListGroupSorting() == AppearanceSettings::EnumContactListGroupSorting::Manual);
    bool manualMetaContactSorting = (as->contactListMetaContactSorting() == AppearanceSettings::EnumContactListMetaContactSorting::Manual);

    if (m_manualMetaContactSorting != manualMetaContactSorting) {
        saveModelSettings(QStringLiteral("Plain"));
        m_manualGroupSorting = manualGroupSorting;
        m_manualMetaContactSorting = manualMetaContactSorting;
        loadModelSettings(QStringLiteral("Plain"));
    }
}

void ContactListPlainModel::loadContactList()
{
    ContactListModel::loadContactList();

    foreach (Kopete::MetaContact *mc, Kopete::ContactList::self()->metaContacts()) {
        addMetaContact(mc);
    }

    if (m_manualMetaContactSorting) {
        loadModelSettings(QStringLiteral("Plain"));
        reset();
    }
}

void ContactListPlainModel::saveModelSettingsImpl(QDomDocument &doc, QDomElement &rootElement)
{
    if (m_manualMetaContactSorting) {
        QDomElement metaContactRootElement = rootElement.firstChildElement(QStringLiteral("MetaContactPositions"));
        if (!metaContactRootElement.isNull()) {
            rootElement.removeChild(metaContactRootElement);
        }

        metaContactRootElement = doc.createElement(QStringLiteral("MetaContactPositions"));
        rootElement.appendChild(metaContactRootElement);

        for (int i = 0; i < m_contacts.count(); ++i) {
            Kopete::MetaContact *mc = m_contacts.value(i);
            QDomElement metaContactElement = doc.createElement(QStringLiteral("MetaContact"));
            metaContactElement.setAttribute(QStringLiteral("uuid"), mc->metaContactId().toString());
            metaContactElement.setAttribute(QStringLiteral("possition"), i);
            metaContactRootElement.appendChild(metaContactElement);
        }
    }
}

// Temporary hash, only used for sorting when contact list is loaded.
QHash<const Kopete::MetaContact *, int> *_metaContactPositionPlain = 0;

bool manualMetaContactSort(const Kopete::MetaContact *mc1, const Kopete::MetaContact *mc2)
{
    return _metaContactPositionPlain->value(mc1, -1) < _metaContactPositionPlain->value(mc2, -1);
}

void ContactListPlainModel::loadModelSettingsImpl(QDomElement &rootElement)
{
    if (!m_manualMetaContactSorting) {
        return;
    }

    if (m_manualMetaContactSorting) {
        QDomElement metaContactRootElement = rootElement.firstChildElement(QStringLiteral("MetaContactPositions"));
        if (!metaContactRootElement.isNull()) {
            // Temporary hash for faster item lookup
            QHash<QUuid, const Kopete::MetaContact *> uuidToMetaContact;
            foreach (const Kopete::MetaContact *mc, m_contacts) {
                uuidToMetaContact.insert(mc->metaContactId(), mc);
            }

            _metaContactPositionPlain = new QHash<const Kopete::MetaContact *, int>();
            QDomNodeList metaContactList = metaContactRootElement.elementsByTagName(QStringLiteral("MetaContact"));

            for (int index = 0; index < metaContactList.size(); ++index) {
                QDomElement metaContactElement = metaContactList.item(index).toElement();
                if (metaContactElement.isNull()) {
                    continue;
                }

                // Put position into hash.
                QUuid uuid(metaContactElement.attribute(QStringLiteral("uuid")));
                int metaContactPosition = metaContactElement.attribute(QStringLiteral("possition"), QStringLiteral("-1")).toInt();
                const Kopete::MetaContact *mc = uuidToMetaContact.value(uuid, 0);
                if (mc) {
                    _metaContactPositionPlain->insert(mc, metaContactPosition);
                }
            }

            qStableSort(m_contacts.begin(), m_contacts.end(), manualMetaContactSort);

            delete _metaContactPositionPlain;
            _metaContactPositionPlain = 0;
        }
    }
}
}
}

//kate: tab-width 4
