/*
 * QtTapioca, the Tapioca Qt4 Client Library
 * Copyright (C) 2007 by INdT
 *  @author Abner Jose de Faria Silva <abner.silva@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 <QDebug>
#include <QtCore/QHash>
#include <QtCore/QMutex>

#include "QtTapioca/Handle"
#include "QtTapioca/Contact"
#include "QtTapioca/AudioStream"
#include "QtTapioca/VideoStream"
#include "QtTapioca/StreamChannel"
#include "QtTapioca/Connection"

#include <QtTelepathy/Client/ChannelHandler>
#include <QtTelepathy/Client/StreamEngine>
#include <QtTelepathy/Client/ChannelStreamedMedia>
#include <QtTelepathy/Client/ChannelGroupInterface>

#define STREAM_ENGINE_IFACE         "org.freedesktop.Telepathy.StreamEngine"
#define STREAM_ENGINE_PATH          "/org/freedesktop/Telepathy/StreamEngine"

namespace QtTapioca {

class StreamChannelPrivate {
public:
    StreamChannelPrivate(Connection *conn, const QString &serviceName, const QString &objPath)
        : m_conn(conn),
          telepathyChannelHandler(NULL)
    {
        telepathyStreamedMedia = new org::freedesktop::Telepathy::ChannelStreamedMedia(serviceName, objPath, QDBusConnection::sessionBus());
        telepathyIChannelGroup = new org::freedesktop::Telepathy::ChannelGroupInterface(serviceName, objPath, QDBusConnection::sessionBus());
        telepathyStreamEngine = new org::freedesktop::Telepathy::StreamEngine(STREAM_ENGINE_IFACE, STREAM_ENGINE_PATH, QDBusConnection::sessionBus());
    }
    ~StreamChannelPrivate()
    {
        delete telepathyStreamedMedia;
        delete telepathyIChannelGroup;

        if (telepathyStreamEngine)
            delete telepathyStreamEngine;

        if (telepathyChannelHandler)
            delete telepathyChannelHandler;
    }

    QMutex mutex;
    Connection *m_conn;
    QHash<uint, Stream *> stream_hash;
    org::freedesktop::Telepathy::StreamEngine *telepathyStreamEngine;
    org::freedesktop::Telepathy::ChannelHandler *telepathyChannelHandler;
    org::freedesktop::Telepathy::ChannelStreamedMedia *telepathyStreamedMedia;
    org::freedesktop::Telepathy::ChannelGroupInterface *telepathyIChannelGroup;
};

}

using namespace QtTapioca;

StreamChannel::StreamChannel(Connection *conn, const QString &serviceName, const QString &objPath, ChannelTarget *target, QObject *parent)
    : Channel(conn, serviceName, objPath, Channel::Stream, target, parent),
      d(new StreamChannelPrivate(conn, serviceName, objPath))
{
    Q_ASSERT(d);

    QObject::connect(d->telepathyStreamedMedia, SIGNAL(StreamAdded(uint, uint, uint)),
                     this, SLOT(onStreamAdded(uint, uint, uint)));
    QObject::connect(d->telepathyStreamedMedia, SIGNAL(StreamRemoved(uint)),
                     this, SLOT(onStreamRemoved(uint)));

    if (hasSupportFor("org.freedesktop.Telepathy.Channel.Interface.MediaSignalling"))
        d->telepathyChannelHandler = new org::freedesktop::Telepathy::ChannelHandler(STREAM_ENGINE_IFACE, STREAM_ENGINE_PATH, QDBusConnection::sessionBus());

    if (d->telepathyChannelHandler) {
        QDBusReply<void> ret = d->telepathyChannelHandler->HandleChannel(d->m_conn->serviceName(),
                                                                         QDBusObjectPath(d->m_conn->objectPath()),
                                                                         "org.freedesktop.Telepathy.Channel.Type.StreamedMedia",
                                                                         QDBusObjectPath(objectPath()),
                                                                         0,
                                                                         0);
        if (!ret.isValid())
            qDebug() << "error calling HandleChannel:" << ret.error().message();
    }

    updateStreamList();

}

StreamChannel::~StreamChannel()
{
    delete d;
}

QList<Stream *> StreamChannel::streams() const
{
    return d->stream_hash.values();
}

void StreamChannel::requestStreams(const Contact *contact, QList<QtTapioca::Stream::Type> types)
{
    d->mutex.lock();
    Q_ASSERT(contact);
    Q_ASSERT(d->telepathyStreamedMedia);

    if (!types.isEmpty()) {
        org::freedesktop::Telepathy::StreamTypeList type_list;

        foreach (QtTapioca::Stream::Type type, types)
            type_list << (uint)type;

        QDBusReply<org::freedesktop::Telepathy::StreamInfoList> reply = d->telepathyStreamedMedia->RequestStreams(contact->handle()->id(), type_list);

        if (!reply.isValid())
            qDebug() << "error requesting streams:" << reply.error().message();
    }
    d->mutex.unlock();
}

void StreamChannel::removeStream (QtTapioca::Stream *stream)
{
    d->mutex.lock();
    Q_ASSERT(stream);
    Q_ASSERT(d->telepathyStreamedMedia);

    QDBusReply<void> reply = d->telepathyStreamedMedia->RemoveStreams(QList<uint>() << stream->m_id);

    if (!reply.isValid())
        qDebug() << "error removing streams:" << reply.error().message();

    d->mutex.unlock();
}

void StreamChannel::updateStreamList()
{
    Q_ASSERT(d->telepathyStreamedMedia);

    QtTapioca::Stream *s = NULL;
    org::freedesktop::Telepathy::StreamInfo info;
    org::freedesktop::Telepathy::StreamInfoList infoList;

    QDBusReply<org::freedesktop::Telepathy::StreamInfoList> reply = d->telepathyStreamedMedia->ListStreams();

    if (!reply.isValid()) {
        qDebug() << "error listing streams:" << reply.error().message();
        return;
    }

    infoList = reply.value();

    foreach (info, infoList) {
        if (d->stream_hash.contains(info.id)) {
            //TODO - update some stream's attributes.
        }
        else {
            if (info.type == Stream::Audio)
                s = new QtTapioca::AudioStream(d->telepathyStreamedMedia,
                                               d->telepathyStreamEngine,
                                               info.id,
                                               info.contactHandle,
                                               info.type,
                                               info.state,
                                               info.direction,
                                               info.pendingFlags,
                                               this);
            else if (info.type == Stream::Video)
                s = new QtTapioca::VideoStream(d->telepathyStreamedMedia,
                                               d->telepathyStreamEngine,
                                               info.id,
                                               info.contactHandle,
                                               info.type,
                                               info.state,
                                               info.direction,
                                               info.pendingFlags,
                                               this);
            if (s)
                d->stream_hash[info.id] = s;
        }
    }
}

void StreamChannel::join()
{
    Q_ASSERT(d->telepathyIChannelGroup);

    QDBusReply<QList<uint> > reply = d->telepathyIChannelGroup->GetLocalPendingMembers();

    if (!reply.isValid()) {
        qDebug() << "error getting local pending members:" << reply.error().message();
        return;
    }

    if (!reply.value().isEmpty()) {
        QDBusReply<void> replyAdd = d->telepathyIChannelGroup->AddMembers(reply.value(), "");

        if (!replyAdd.isValid()) {
            qDebug() << "error adding members:" << reply.error().message();
            return;
        }

        Channel::join();
    }
}

void StreamChannel::onStreamAdded(uint id, uint contactHandle, uint type)
{
    qDebug() << "onStreamAdded signal emitted";

    d->mutex.lock();
    if (!d->stream_hash.contains(id)) {
        QtTapioca::Stream *s = NULL;

        qDebug() << "new stream added. id:" << id << "contact:" << contactHandle << "type:" << type;

        if (type == Stream::Audio)
            s = new QtTapioca::AudioStream(d->telepathyStreamedMedia, d->telepathyStreamEngine, id, contactHandle, type, this);
        else if (type == Stream::Video)
            s = new QtTapioca::VideoStream(d->telepathyStreamedMedia, d->telepathyStreamEngine, id, contactHandle, type, this);

        if (s) {
            d->stream_hash[id] = s;
            emit newStream(s);
        }
    }
    d->mutex.unlock();
}

void StreamChannel::onStreamRemoved(uint id)
{
    qDebug() << "onStreamRemoved signal emmited";

    d->mutex.lock();
    if (d->stream_hash.contains(id)) {
        QtTapioca::Stream *s = d->stream_hash[id];

        emit streamRemoved(s);

        d->stream_hash.remove(id);
        s->deleteLater();
    }
    d->mutex.unlock();
}
