/*
 * QtTapioca, the Tapioca Qt4 Client Library
 * Copyright (C) 2006 by INdT
 *  @author Andre Moreira Magalhaes <andre.magalhaes@indt.org>
 *  @author Abner Jose de Faria Silva <abner.silva@indt.org.br>
 *  @author Tobias Hunger <info@basyskom.de>
 *
 * 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/Connection"
#include "QtTapioca/Contact"
#include "QtTapioca/ContactList"
#include "QtTapioca/Handle"
#include "QtTapioca/HandleFactory"
#include "QtTapioca/TextChannel"
#include "QtTapioca/StreamChannel"

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QMutex>
#include <QtCore/QSettings>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusMetaType>
#include <QtTelepathy/Client/Connection>
#include <QtTelepathy/Client/ConnectionAvatarsInterface>
#include <QtTelepathy/Client/ConnectionPresenceInterface>
#include <QtTelepathy/Client/ConnectionAliasingInterface>
#include <QtTelepathy/Client/ConnectionCapabilitiesInterface>

namespace QtTapioca {

/*
 * Private class
 */
class ConnectionPrivate {
public:
    ConnectionPrivate(org::freedesktop::Telepathy::Connection * const telepathyConn,
                      QtTapioca::Connection * const p)
        : bus(QDBusConnection::sessionBus()),
          parent(p),
          conn(telepathyConn),
          iAvatar(0),
          iPresence(0),
          iAliasing(0),
          iCapabilities(0),
          cl(0),
          initialPresence(ContactBase::Offline),
          initialPresenceMessage(""),
          uContact(0),
          selfHandle(0),
          handleFactory(new HandleFactory(telepathyConn, p))
    {
        Q_ASSERT(0 != telepathyConn);
        Q_ASSERT(0 != p);
        status = telepathyConn->GetStatus();
    }
    ~ConnectionPrivate()
    {
        delete iAvatar;
        delete iPresence;
        delete iAliasing;
        delete iCapabilities;
        delete uContact;
        delete cl;
        delete conn;
    }

    void loadInterfaces()
    {
        QStringList interfaces = conn->GetInterfaces();

        if (interfaces.contains(org::freedesktop::Telepathy::ConnectionAvatarsInterface::staticInterfaceName()))
            iAvatar = new org::freedesktop::Telepathy::ConnectionAvatarsInterface(conn->service(), conn->path(), bus);

        if (interfaces.contains(org::freedesktop::Telepathy::ConnectionPresenceInterface::staticInterfaceName()))
            iPresence = new org::freedesktop::Telepathy::ConnectionPresenceInterface(conn->service(), conn->path(), bus);

        if (interfaces.contains(org::freedesktop::Telepathy::ConnectionAliasingInterface::staticInterfaceName()))
            iAliasing = new org::freedesktop::Telepathy::ConnectionAliasingInterface(conn->service(), conn->path(), bus);

        if (interfaces.contains(org::freedesktop::Telepathy::ConnectionCapabilitiesInterface::staticInterfaceName()))
            iCapabilities = new org::freedesktop::Telepathy::ConnectionCapabilitiesInterface(conn->service(), conn->path(), bus);
    }

    QDBusConnection bus;
    QtTapioca::Connection * const parent;
    org::freedesktop::Telepathy::Connection * const conn;
    org::freedesktop::Telepathy::ConnectionAvatarsInterface *iAvatar;
    org::freedesktop::Telepathy::ConnectionPresenceInterface *iPresence;
    org::freedesktop::Telepathy::ConnectionAliasingInterface *iAliasing;
    org::freedesktop::Telepathy::ConnectionCapabilitiesInterface *iCapabilities;
    uint status;
    ContactList *cl;
    ContactBase::Presence initialPresence;
    QString initialPresenceMessage;
    UserContact *uContact;
    Handle *selfHandle;
    HandleFactory * const handleFactory;
    QHash<QString, Channel *> channels;
    QMutex mutex;
};

}


using namespace QtTapioca;

/*
 * Constructor
 */
Connection::Connection(const QString &serviceName, const QString &objPath, QObject *parent)
    : DBusProxyObject(serviceName, objPath, parent),
      d(new ConnectionPrivate(new org::freedesktop::Telepathy::Connection(serviceName,
                                                                          objPath,
                                                                          QDBusConnection::sessionBus()),
                              this))
{
    Q_ASSERT(d);

    QObject::connect(d->conn, SIGNAL(NewChannel(const QDBusObjectPath &, const QString &, uint, uint, bool)),
                     this, SLOT(onNewChannel(const QDBusObjectPath &, const QString &, uint, uint, bool)));
    QObject::connect(d->conn, SIGNAL(StatusChanged(uint,uint)), this, SLOT(onStatusChanged(uint,uint)));

    updateOpenChannels();

    if (d->status == Connected) { d->loadInterfaces(); }
}

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

/*
 * Get Protocol
 */
QString Connection::protocol() const
{
    return d->conn->GetProtocol();
}

/*
 * Get Status
 */
Connection::Status Connection::status() const
{
    return static_cast<Connection::Status>(d->status);
}

/*
 * Connect
 */
void Connection::connect(ContactBase::Presence initialPresence, const QString &initialMessage)
{
    if (d->status != Connection::Disconnected)
        return;

    d->initialPresence = initialPresence;
    d->initialPresenceMessage = initialMessage;
    d->conn->Connect();
}

/*
 * Disconnect
 */
void Connection::disconnect()
{
    if (d->status == Connection::Disconnected)
        return;
    d->conn->Disconnect();
}

/*
 * Create Channel
 */
Channel *Connection::createChannel(Channel::Type type, Contact *contact, bool suppress_handler)
{
    Channel *channel = 0;
    QString objPath;

    QMutexLocker lock(&(d->mutex));
    if (type == Channel::Text) {
        objPath = requestChannel("org.freedesktop.Telepathy.Channel.Type.Text", contact->handle(),
             suppress_handler);
        if (!objPath.isEmpty())
            channel = new TextChannel(this, serviceName(), objPath, contact, this);
    }
    else if (type == Channel::Stream) {
        objPath = requestChannel("org.freedesktop.Telepathy.Channel.Type.StreamedMedia", contact->handle(),
             suppress_handler);
        if (!objPath.isEmpty())
            channel = new StreamChannel(this, serviceName(), objPath, contact, this);
    }

    if (channel) {
        d->channels[objPath] = channel;
        QObject::connect(channel, SIGNAL(destroyed()),
                         this, SLOT(onChannelDestroyed()));
    }

    return channel;
}

/*
 * Get Channels
 */
QList<Channel *> Connection::openChannels() const
{
    return d->channels.values();
}

/*
 * Request Channel
 */
QString Connection::requestChannel(const QString &interface, Handle *handle, bool suppress_handler)
{
    QDBusReply<QDBusObjectPath> channel = d->conn->RequestChannel(
            interface, handle->type(), handle->id(), suppress_handler);

    if (channel.isValid())
        return static_cast<QDBusObjectPath>(channel).path();

    return QString();
}

/*
 * Get Contact List
 */
ContactList *Connection::contactList()
{
    if (d->status == Connection::Disconnected)
    { return 0; }

    if (!d->cl) {
        d->cl = new ContactList(d->conn,
                                d->iAvatar,
                                d->iPresence,
                                d->iAliasing,
                                d->iCapabilities,
                                d->handleFactory,
                                this);
    }
    return d->cl;
}

/*
 * Initialize UserContact.
 */
void Connection::initUserContact()
{
    if (!d->uContact) {
        if (!d->selfHandle) {
            /* Updated selfHandle */
            uint my_handle = d->conn->GetSelfHandle();
            d->selfHandle = d->handleFactory->createHandle(Handle::Contact, my_handle);
            Q_ASSERT (d->selfHandle != 0);
        }

        d->uContact = new UserContact(d->conn, d->iAvatar, d->iPresence, d->iAliasing, d->iCapabilities, d->selfHandle, this);

        d->uContact->setPresenceWithMessage(d->initialPresence, d->initialPresenceMessage);
    }
}

/*
 * Get User Contact
 */
UserContact *Connection::userContact()
{
    if (d->uContact == 0 && d->status == Connected) { initUserContact(); }
    return d->uContact;
}

/*
 * has * Support
 */
bool Connection::hasAvatarSupport() const
{ return (0 != d->iAvatar); }

bool Connection::hasPresenceSupport() const
{ return (0 != d->iPresence); }

bool Connection::hasAliasingSupport() const
{ return (0 != d->iAliasing); }

bool Connection::hasCapabilitiesSupport() const
{ return (0 != d->iCapabilities); }

void Connection::updateOpenChannels()
{
    if (d->status != Connection::Connected)
    { return; }

    org::freedesktop::Telepathy::ChannelInfo channelInfo;
    org::freedesktop::Telepathy::ChannelInfoList channelInfoList;

    channelInfoList = d->conn->ListChannels();

    foreach (channelInfo, channelInfoList) {
        incomingChannel(channelInfo.objectPath,
                        channelInfo.interfaceName,
                        channelInfo.handleType,
                        channelInfo.handle,
                        true);
    }
}

Channel * Connection::incomingChannel(const QDBusObjectPath &objPath,
                                      const QString &channelType,
                                      uint handleType,
                                      uint handleId,
                                      bool suppressHandler)
{
    Channel *channel = 0;

    QMutexLocker lock(&(d->mutex));

    if ((d->channels.contains(objPath.path())) ||
        ((channelType != "org.freedesktop.Telepathy.Channel.Type.Text") &&
        (channelType != "org.freedesktop.Telepathy.Channel.Type.StreamedMedia")))
    { return NULL; }

    Contact *contact = contactList()->contact(handleId);

    if (!contact) {
        Handle *handle = d->handleFactory->createHandle(handleType, handleId);
        contact = d->cl->addContact(handle);
        if (!contact) {
            qDebug() << "error creating a contact.";
            return NULL;
        }
    }

    if (channelType == "org.freedesktop.Telepathy.Channel.Type.Text")
        channel = new TextChannel(this, serviceName(), objPath.path(), contact, this);
    else if (channelType == "org.freedesktop.Telepathy.Channel.Type.StreamedMedia")
        channel = new StreamChannel(this, serviceName(), objPath.path(), contact, this);

    if (channel) {
        d->channels[objPath.path()] = channel;
        QObject::connect(channel, SIGNAL(destroyed()),
                         this, SLOT(onChannelDestroyed()));
    }

    return channel;
}

/*
 * On New Channel
 */
void Connection::onNewChannel(const QDBusObjectPath &objPath,
                              const QString &channelType,
                              uint handleType,
                              uint handleId,
                              bool suppressHandler)
{
    Channel *channel = incomingChannel(objPath,
                                       channelType,
                                       handleType,
                                       handleId,
                                       suppressHandler);

    if (channel)
        emit channelCreated(this, channel, suppressHandler);
}

/*
 * On Status Changed
 */
void Connection::onStatusChanged(uint status, uint reason)
{
    d->status = status;

    if (status == Connected) {
        d->loadInterfaces();
    }

    emit statusChanged(this,
                       static_cast<Connection::Status>(status),
                       static_cast<Connection::Reason>(reason));
    if (status == Disconnected) {
        /* we are no longer valid */
        deleteLater();
    }
}

/*
 * Channel Destroyed
 */
void Connection::onChannelDestroyed()
{
    Channel *channel = static_cast<Channel *>(sender());
    d->channels.remove(channel->objectPath());
}

