/*
 * QtTapioca, the Tapioca Qt4 Client Library
 * Copyright (C) 2006 by INdT
 *  @author Abner Jose de Faria Silva <abner.silva@indt.org.br>
 *  @author Andre Moreira Magalhaes <andre.magalhes@indt.org.br>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA
 */

#include "config.h"

#include "QtTapioca/ContactList"
#include "QtTapioca/Handle"
#include "QtTapioca/HandleFactory"
#include "QtTapioca/Contact"

#include <QDebug>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QtTelepathy/Client/Channel>
#include <QtTelepathy/Client/ChannelContactList>
#include <QtTelepathy/Client/ChannelGroupInterface>
#include <QtTelepathy/Client/Connection>

enum ListType {
    CONTACT_LIST_TYPE_SUBSCRIBE = 0,
    CONTACT_LIST_TYPE_PUBLISH,
    CONTACT_LIST_TYPE_HIDE,
    CONTACT_LIST_TYPE_ALLOW,
    CONTACT_LIST_TYPE_DENY,
    CONTACT_LIST_TYPE_LAST
};

namespace QtTapioca {

/*
 * Stores a single telepathy contact list (e.g. "subscribe")
 */
class ContactListInternal: public org::freedesktop::Telepathy::ChannelGroupInterface
{
public:
    ContactListInternal(const QString &serviceName, const QString &objPath, Handle *h)
        : org::freedesktop::Telepathy::ChannelGroupInterface(serviceName, objPath, QDBusConnection::sessionBus()),
          handle(h)
    {
    }
    ~ContactListInternal()
    {
        if (handle)
            delete handle;
    }

    Handle *handle;
};

/*
 * Private class
 */
class ContactListPrivate {
public:
    ContactListPrivate(org::freedesktop::Telepathy::Connection *conn,
                       org::freedesktop::Telepathy::ConnectionAvatarsInterface *iAvatar,
                       org::freedesktop::Telepathy::ConnectionPresenceInterface *iPresence,
                       org::freedesktop::Telepathy::ConnectionAliasingInterface *iAliasing,
                       org::freedesktop::Telepathy::ConnectionCapabilitiesInterface *iCapabilities,
                       HandleFactory *hFactory)
       : telepathyConn(conn),
         telepathyIAvatar(iAvatar),
         telepathyIPresence(iPresence),
         telepathyIAliasing(iAliasing),
         telepathyICapabilities(iCapabilities),
         handleFactory(hFactory)
    {
        const char *list_names[CONTACT_LIST_TYPE_LAST] = { "subscribe", "publish", "hide", "allow", "deny" };
        QDBusConnection bus = QDBusConnection::sessionBus();
        Handle *handle = NULL;
        ushort i;

        for (i = 0; i < CONTACT_LIST_TYPE_LAST; ++i) {
            handle = handleFactory->createHandle(Handle::List, list_names[i]);
            if (!handle) {
                lists[i] = 0;
                continue;
            }
            QString objPath = requestChannel ("org.freedesktop.Telepathy.Channel.Type.ContactList", handle);
            if (!objPath.isEmpty()) {
                lists[i] = new ContactListInternal(conn->service(), objPath,  handle);
            }
            else {
                lists[i] = 0;
                delete handle;
            }
        }
    }
    ~ContactListPrivate()
    {
        for (ushort i = 0; i < CONTACT_LIST_TYPE_LAST; ++i) {
            if (lists[i])
                delete lists[i];
        }
    }

    /*
     * Request Channel
     */
    QString requestChannel(const QString &interface, Handle *handle)
    {
        QDBusReply<QDBusObjectPath> channel = telepathyConn->RequestChannel(
                interface, handle->type(), handle->id(), false);
        if (channel.isValid()) {
            return static_cast<QDBusObjectPath>(channel).path();
        }

        qDebug() << "error requesting channel:" << channel.error().message();

        return QString();
    }

    org::freedesktop::Telepathy::Connection *telepathyConn;
    org::freedesktop::Telepathy::ConnectionAvatarsInterface  *telepathyIAvatar;
    org::freedesktop::Telepathy::ConnectionPresenceInterface *telepathyIPresence;
    org::freedesktop::Telepathy::ConnectionAliasingInterface *telepathyIAliasing;
    org::freedesktop::Telepathy::ConnectionCapabilitiesInterface *telepathyICapabilities;
    ContactListInternal *lists[CONTACT_LIST_TYPE_LAST];
    QHash<uint, Contact *> contacts;
    HandleFactory *handleFactory;
    QMutex mutex;
};

} // namespace

using namespace QtTapioca;

/*
 * Constructor
 */
ContactList::ContactList(org::freedesktop::Telepathy::Connection *telepathyConn,
                         org::freedesktop::Telepathy::ConnectionAvatarsInterface *iAvatar,
                         org::freedesktop::Telepathy::ConnectionPresenceInterface *iPresence,
                         org::freedesktop::Telepathy::ConnectionAliasingInterface *iAliasing,
                         org::freedesktop::Telepathy::ConnectionCapabilitiesInterface *iCapabilities,
                         HandleFactory *handleFactory,
                         QObject *parent)
    : QObject(parent),
      d(new ContactListPrivate(telepathyConn,
                               iAvatar,
                               iPresence,
                               iAliasing,
                               iCapabilities,
                               handleFactory))
{
    Q_ASSERT(d);

    loadContacts();

    if (d->lists[CONTACT_LIST_TYPE_SUBSCRIBE])
        QObject::connect(d->lists[CONTACT_LIST_TYPE_SUBSCRIBE], SIGNAL(MembersChanged(const QString &, const QList<uint> &, const QList<uint> &, const QList<uint> &, const QList<uint> &, uint, uint)),
                this, SLOT(onMembersChangedSubscribe(const QString &, const QList<uint> &, const QList<uint> &, const QList<uint> &, const QList<uint> &, uint, uint)));

    if (d->lists[CONTACT_LIST_TYPE_PUBLISH])
        QObject::connect(d->lists[CONTACT_LIST_TYPE_PUBLISH], SIGNAL(MembersChanged(const QString &, const QList<uint> &, const QList<uint> &, const QList<uint> &, const QList<uint> &, uint, uint)),
                this, SLOT(onMembersChangedPublish(const QString &, const QList<uint> &, const QList<uint> &, const QList<uint> &, const QList<uint> &, uint, uint)));
}

/*
 * Destructor
 */
ContactList::~ContactList()
{
    delete d;
}

/*
 * Add Contact
 */
Contact *ContactList::addContact(const QString &uri)
{
    Contact *contact = NULL;
    Handle *handle = NULL;

    /* check if contact is already on the list */
    d->mutex.lock();
    handle = d->handleFactory->createHandle(Handle::Contact, uri);
    if (!handle) {
        d->mutex.unlock();
        return 0;
    }
    if (d->contacts.contains(handle->id())) {
        contact = d->contacts[handle->id()];
        delete handle;
    }
    else {
        contact = new Contact(d->telepathyConn,
                              d->telepathyIAvatar,
                              d->telepathyIPresence,
                              d->telepathyIAliasing,
                              d->telepathyICapabilities,
                              d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                              d->lists[CONTACT_LIST_TYPE_PUBLISH],
                              d->lists[CONTACT_LIST_TYPE_HIDE],
                              d->lists[CONTACT_LIST_TYPE_ALLOW],
                              d->lists[CONTACT_LIST_TYPE_DENY],
                              handle, this);
        d->contacts[handle->id()] = contact;
    }
    d->mutex.unlock();

    return contact;
}

Contact *ContactList::addContact(const Handle *handle)
{
    Contact *contact = NULL;

    /* check if contact is already on the list */
    d->mutex.lock();
    if (d->contacts.contains(handle->id())) {
        contact = d->contacts[handle->id()];
        delete handle;
    }
    else {
        contact = new Contact(d->telepathyConn,
                              d->telepathyIAvatar,
                              d->telepathyIPresence,
                              d->telepathyIAliasing,
                              d->telepathyICapabilities,
                              d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                              d->lists[CONTACT_LIST_TYPE_PUBLISH],
                              d->lists[CONTACT_LIST_TYPE_HIDE],
                              d->lists[CONTACT_LIST_TYPE_ALLOW],
                              d->lists[CONTACT_LIST_TYPE_DENY],
                              const_cast<Handle *>(handle), this);
        d->contacts[handle->id()] = contact;
    }

    d->mutex.unlock();
    return contact;
}

Contact *ContactList::addContact(const uint id)
{
    Handle * handle(d->handleFactory->createHandle(Handle::Contact, id));

    if (handle == 0) { return 0; }

    return addContact(handle);
}

/*
 * Remove Contact
 */
void ContactList::removeContact(Contact *contact)
{
    d->mutex.lock();
    if (d->contacts.contains(contact->handle()->id())) {
        d->contacts.remove(contact->handle()->id());

        // Remove him from all lists (subscribe, publish, hide and deny)
        contact->subscribe(false);
        contact->authorize(false);
        contact->hideFrom(false);
        contact->block(false);

        contact->deleteLater();
    }
    d->mutex.unlock();
}

/*
 * Get Contact from Handle
 */
Contact *ContactList::contact(Handle *handle) const
{
    return contact(handle->id());
}

/*
 * Get Contact from URI
 */
Contact *ContactList::contact(const QString &uri) const
{
    Contact *contact;

    QMutexLocker lock(&(d->mutex));
    foreach (contact, d->contacts) {
        if (contact->uri() == uri)
            return contact;
    }
    return NULL;
}

/*
 * Get Contact from id
 */
Contact * ContactList::contact(const uint id) const
{
    Contact *c = NULL;

    QMutexLocker lock(&(d->mutex));
    if (d->contacts.contains(id))
        c = d->contacts.value(id);

    return c;
}

/*
 * Contacts
 */
QList<Contact *> ContactList::knownContacts() const
{
    return d->contacts.values();
}

QList<Contact *> ContactList::subscribedContacts() const
{
    QList<Contact *> rcontacts;
    QHash<uint, Contact *>::const_iterator i;

    for (i = d->contacts.constBegin(); i != d->contacts.end(); i++)
        if (i.value()->subscriptionStatus() == Contact::Subscribed)
            rcontacts << i.value();

    return rcontacts;
}

QList<Contact *> ContactList::authorizedContacts() const
{
    QList<Contact *> rcontacts;
    QHash<uint, Contact *>::const_iterator i;

    for (i = d->contacts.constBegin(); i != d->contacts.end(); i++)
        if (i.value()->authorizationStatus() == Contact::Authorized)
            rcontacts << i.value();

    return rcontacts;
}

QList<Contact *> ContactList::blockedContacts() const
{
    QList<Contact *> rcontacts;
    QHash<uint, Contact *>::const_iterator i;

    for (i = d->contacts.constBegin(); i != d->contacts.end(); i++)
        if (i.value()->isBlocked())
            rcontacts << i.value();

    return rcontacts;
}

QList<Contact *> ContactList::hiddenContacts() const
{
    QList<Contact *> rcontacts;
    QHash<uint, Contact *>::const_iterator i;

    for (i = d->contacts.constBegin(); i != d->contacts.end(); i++)
        if (i.value()->hidingFrom())
            rcontacts << i.value();

    return rcontacts;
}

/*
 * Is Stored On Server
 */
bool ContactList::isStoredOnServer() const
{
    /* FIXME */
    return TRUE;
}

/*
 * Load Contacts
 */
void ContactList::loadContacts()
{
    QList<uint> current, local_pending, remote_pending;
    uint id = 0;
    Handle *handle;
    Contact *contact;
    ushort i;

    for (i = 0; i < CONTACT_LIST_TYPE_LAST; ++i) {
        if (!d->lists[i])
            continue;

        current = d->lists[i]->GetAllMembers(local_pending, remote_pending);

        foreach (id, current) {
            handle = d->handleFactory->createHandle(Handle::Contact, id);
            if (!handle) continue;

            if (d->contacts.contains(id))
                contact = d->contacts[id];
            else {
                contact = new Contact(d->telepathyConn,
                                      d->telepathyIAvatar,
                                      d->telepathyIPresence,
                                      d->telepathyIAliasing,
                                      d->telepathyICapabilities,
                                      d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                                      d->lists[CONTACT_LIST_TYPE_PUBLISH],
                                      d->lists[CONTACT_LIST_TYPE_HIDE],
                                      d->lists[CONTACT_LIST_TYPE_ALLOW],
                                      d->lists[CONTACT_LIST_TYPE_DENY],
                                      handle, this);
                d->contacts[id] = contact;
            }

            switch (i) {
                case CONTACT_LIST_TYPE_SUBSCRIBE:
                    contact->setSubscriptionStatus(Contact::Subscribed);
                    break;
                case CONTACT_LIST_TYPE_PUBLISH:
                    contact->setAuthorizationStatus(Contact::Authorized);
                    break;
                case CONTACT_LIST_TYPE_HIDE:
                    contact->setHideStatus(true);
                    break;
                case CONTACT_LIST_TYPE_DENY:
                    contact->setBlockStatus(true);
                    break;
                default:
                    break;
            }
        }

        if (i == CONTACT_LIST_TYPE_SUBSCRIBE) {
            foreach (id, remote_pending) {
                handle = d->handleFactory->createHandle(Handle::Contact, id);
                if (!handle) continue;

                if (d->contacts.contains(id))
                    contact = d->contacts[id];
                else {
                    contact = new Contact(d->telepathyConn,
                                          d->telepathyIAvatar,
                                          d->telepathyIPresence,
                                          d->telepathyIAliasing,
                                          d->telepathyICapabilities,
                                          d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                                          d->lists[CONTACT_LIST_TYPE_PUBLISH],
                                          d->lists[CONTACT_LIST_TYPE_HIDE],
                                          d->lists[CONTACT_LIST_TYPE_ALLOW],
                                          d->lists[CONTACT_LIST_TYPE_DENY],
                                          handle,this);
                    d->contacts[id] = contact;
                }

                contact->setSubscriptionStatus(Contact::RemotePending);
            }
        }
        else if (i == CONTACT_LIST_TYPE_PUBLISH) {
            foreach (id, local_pending) {
                handle = d->handleFactory->createHandle(Handle::Contact, id);
                if (!handle)
                    continue;
                if (d->contacts.contains(id))
                    contact = d->contacts[id];
                else {
                    contact = new Contact(d->telepathyConn,
                                          d->telepathyIAvatar,
                                          d->telepathyIPresence,
                                          d->telepathyIAliasing,
                                          d->telepathyICapabilities,
                                          d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                                          d->lists[CONTACT_LIST_TYPE_PUBLISH],
                                          d->lists[CONTACT_LIST_TYPE_HIDE],
                                          d->lists[CONTACT_LIST_TYPE_ALLOW],
                                          d->lists[CONTACT_LIST_TYPE_DENY],
                                          handle, this);

                    d->contacts[id] = contact;
                }

                contact->setAuthorizationStatus(Contact::LocalPending);
            }
        }
    }
}

/*
 * On Members Changed Subscribe
 */
void ContactList::onMembersChangedSubscribe(const QString &message, const QList<uint> &added, const QList<uint> &removed,
                                            const QList<uint> &local, const QList<uint> &remote, uint actor, uint reason)
{
    if (!added.isEmpty()) {
        Contact *contact = NULL;
        QList<uint>::const_iterator i;

        d->mutex.lock();
        for (i = added.begin(); i != added.end(); i++) {
            if (d->contacts.contains(*i)) {
                contact = d->contacts[*i];
                if (contact->subscriptionStatus() == Contact::RemotePending) {
                    contact->setSubscriptionStatus(Contact::Subscribed);
                    emit subscriptionAccepted(contact);
                }
            }
        }
        d->mutex.unlock();
    }
}

/*
 * On Members Changed Publish
 */
void ContactList::onMembersChangedPublish(const QString &message, const QList<uint> &added, const QList<uint> &removed,
                                          const QList<uint> &local, const QList<uint> &remote, uint actor, uint reason)
{
    Handle *handle = NULL;

    if (!local.isEmpty()) {
        Contact *contact = NULL;
        QList<uint>::const_iterator i;

        d->mutex.lock();
        for (i = local.begin(); i != local.end(); i++) {
            if (d->contacts.contains(*i)) {
                contact = d->contacts[*i];
            }
            else {
                handle = d->handleFactory->createHandle(Handle::Contact, *i);
                if (!handle)
                    continue;

                contact = new Contact(d->telepathyConn,
                                      d->telepathyIAvatar,
                                      d->telepathyIPresence,
                                      d->telepathyIAliasing,
                                      d->telepathyICapabilities,
                                      d->lists[CONTACT_LIST_TYPE_SUBSCRIBE],
                                      d->lists[CONTACT_LIST_TYPE_PUBLISH],
                                      d->lists[CONTACT_LIST_TYPE_HIDE],
                                      d->lists[CONTACT_LIST_TYPE_ALLOW],
                                      d->lists[CONTACT_LIST_TYPE_DENY],
                                      handle, this);
            }

            contact->setAuthorizationStatus(Contact::LocalPending);
            d->contacts[*i] = contact;

            emit authorizationRequested(contact);
        }
        d->mutex.unlock();
    }
}

