/*
 * Very basic Tp-Qt <-> Tp-Farsight integration.
 *
 * Copyright © 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/>
 * Copyright © 2009 Nokia Corporation
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "farsight-channel.h"
#include "_gen/farsight-channel.moc.hpp"

#include "video-widget.h"

#include <QDebug>

#include <TelepathyQt4/Connection>
#include <TelepathyQt4/Farsight/Channel>
#include <TelepathyQt4/StreamedMediaChannel>

namespace Tp {

struct FarsightChannel::Private
{
    Private(FarsightChannel *parent, const StreamedMediaChannelPtr &channel);
    ~Private();

    static gboolean busWatch(GstBus *bus,
            GstMessage *message, FarsightChannel::Private *self);
    static void onClosed(TfChannel *tfChannel,
            FarsightChannel::Private *self);
    static void onSessionCreated(TfChannel *tfChannel,
            FsConference *conference, FsParticipant *participant,
            FarsightChannel::Private *self);
    static void onStreamCreated(TfChannel *tfChannel,
            TfStream *stream, FarsightChannel::Private *self);
    static void onSrcPadAdded(TfStream *stream,
            GstPad *pad, FsCodec *codec, FarsightChannel::Private *self);
    static gboolean onRequestResource(TfStream *stream,
            guint direction, gpointer data);

    FarsightChannel *parent;
    StreamedMediaChannelPtr channel;
    Status status;
    TfChannel *tfChannel;
    GstBus *bus;
    GstElement *pipeline;
    GstElement *audioInput;
    GstElement *audioOutput;
    GstElement *videoInput;
    GstElement *videoTee;
    VideoWidget *videoPreview;
    VideoWidget *videoOutput;
};

FarsightChannel::Private::Private(FarsightChannel *parent,
        const StreamedMediaChannelPtr &channel)
    : parent(parent),
      channel(channel),
      status(StatusDisconnected),
      tfChannel(0),
      bus(0),
      pipeline(0),
      audioInput(0),
      audioOutput(0),
      videoInput(0),
      videoTee(0),
      videoPreview(0),
      videoOutput(0)
{
    tfChannel = createFarsightChannel(channel);
    if (!tfChannel) {
        qWarning() << "Unable to construct TfChannel";
        return;
    }

    /* Set up the telepathy farsight channel */
    g_signal_connect(tfChannel, "closed",
        G_CALLBACK(&FarsightChannel::Private::onClosed), this);
    g_signal_connect(tfChannel, "session-created",
        G_CALLBACK(&FarsightChannel::Private::onSessionCreated), this);
    g_signal_connect(tfChannel, "stream-created",
        G_CALLBACK(&FarsightChannel::Private::onStreamCreated), this);

    pipeline = gst_pipeline_new(NULL);
    bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));

    audioInput = gst_element_factory_make("autoaudiosrc", NULL);
    gst_object_ref(audioInput);
    gst_object_sink(audioInput);

    audioOutput = gst_bin_new("audio-output-bin");
    GstElement *resample = gst_element_factory_make("audioresample", NULL);
    GstElement *audioSink = gst_element_factory_make("autoaudiosink", NULL);
    gst_bin_add_many(GST_BIN(audioOutput), resample, audioSink, NULL);
    gst_element_link_many(resample, audioSink, NULL);
    GstPad *sink = gst_element_get_static_pad(resample, "sink");
    GstPad *ghost = gst_ghost_pad_new("sink", sink);
    gst_element_add_pad(GST_ELEMENT(audioOutput), ghost);
    gst_object_unref(G_OBJECT(sink));
    gst_object_ref(audioOutput);
    gst_object_sink(audioOutput);

    videoInput = gst_bin_new("video-input-bin");
    GstElement *scale = gst_element_factory_make("videoscale", NULL);
    GstElement *rate = gst_element_factory_make("videorate", NULL);
    GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", NULL);
    GstElement *capsfilter = gst_element_factory_make("capsfilter", NULL);
    GstCaps *caps = gst_caps_new_simple("video/x-raw-yuv",
            "width", G_TYPE_INT, 320,
            "height", G_TYPE_INT, 240,
            NULL);
    g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL);
    GstElement *videoSrc = gst_element_factory_make("autovideosrc", NULL);
    // GstElement *videoSrc = gst_element_factory_make("videotestsrc", NULL);
    gst_bin_add_many(GST_BIN(videoInput), videoSrc, scale, rate,
            colorspace, capsfilter, NULL);
    gst_element_link_many(videoSrc, scale, rate, colorspace, capsfilter, NULL);
    GstPad *src = gst_element_get_static_pad(capsfilter, "src");
    ghost = gst_ghost_pad_new("src", src);
    Q_ASSERT(gst_element_add_pad(GST_ELEMENT(videoInput), ghost));
    gst_object_unref(G_OBJECT(src));
    gst_object_ref(audioInput);
    gst_object_sink(audioInput);

    videoTee = gst_element_factory_make("tee", NULL);
    gst_object_ref(videoTee);
    gst_object_sink(videoTee);

    videoPreview = new VideoWidget(bus);
    GstElement *videoPreviewElement = videoPreview->element();

    gst_bin_add_many(GST_BIN(pipeline), videoInput, videoTee,
            videoPreviewElement, NULL);
    gst_element_link_many(videoInput, videoTee,
            videoPreviewElement, NULL);

    videoOutput = new VideoWidget(bus);

    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    status = StatusConnecting;
    emit parent->statusChanged(status);
}

FarsightChannel::Private::~Private()
{
    if (tfChannel) {
        g_object_unref(tfChannel);
        tfChannel = 0;
    }

    if (bus) {
        g_object_unref(bus);
        bus = 0;
    }

    gst_element_set_state(pipeline, GST_STATE_NULL);

    if (pipeline) {
        g_object_unref(pipeline);
        pipeline = 0;
    }

    if (audioInput) {
        g_object_unref(audioInput);
        audioInput = 0;
    }

    if (audioOutput) {
        g_object_unref(audioOutput);
        audioOutput = 0;
    }
}

gboolean FarsightChannel::Private::busWatch(GstBus *bus,
        GstMessage *message, FarsightChannel::Private *self)
{
    tf_channel_bus_message(self->tfChannel, message);
    return TRUE;
}

void FarsightChannel::Private::onClosed(TfChannel *tfChannel,
        FarsightChannel::Private *self)
{
    self->status = StatusDisconnected;
    emit self->parent->statusChanged(self->status);
}

void FarsightChannel::Private::onSessionCreated(TfChannel *tfChannel,
        FsConference *conference, FsParticipant *participant,
        FarsightChannel::Private *self)
{
    gst_bus_add_watch(self->bus,
            (GstBusFunc) &FarsightChannel::Private::busWatch, self);

    gst_bin_add(GST_BIN(self->pipeline), GST_ELEMENT(conference));
    gst_element_set_state(GST_ELEMENT(conference), GST_STATE_PLAYING);
}

void FarsightChannel::Private::onStreamCreated(TfChannel *tfChannel,
        TfStream *stream, FarsightChannel::Private *self)
{
    guint media_type;
    GstPad *sink;

    g_signal_connect(stream, "src-pad-added",
        G_CALLBACK(&FarsightChannel::Private::onSrcPadAdded), self);
    g_signal_connect(stream, "request-resource",
        G_CALLBACK(&FarsightChannel::Private::onRequestResource), NULL);

    g_object_get(stream, "media-type", &media_type,
            "sink-pad", &sink, NULL);

    GstPad *pad;
    switch (media_type) {
    case TP_MEDIA_STREAM_TYPE_AUDIO:
        gst_bin_add(GST_BIN(self->pipeline), self->audioInput);
        gst_element_set_state(self->audioInput, GST_STATE_PLAYING);

        pad = gst_element_get_static_pad(self->audioInput, "src");
        gst_pad_link(pad, sink);
        break;
    case TP_MEDIA_STREAM_TYPE_VIDEO:
        pad = gst_element_get_request_pad(self->videoTee, "src%d");
        gst_pad_link(pad, sink);
        break;
    default:
        Q_ASSERT(false);
    }

    gst_object_unref(sink);
}

void FarsightChannel::Private::onSrcPadAdded(TfStream *stream,
        GstPad *src, FsCodec *codec, FarsightChannel::Private *self)
{
    guint media_type;

    g_object_get(stream, "media-type", &media_type, NULL);

    GstPad *pad;
    GstElement *element = 0;

    switch (media_type) {
    case TP_MEDIA_STREAM_TYPE_AUDIO:
        element = self->audioOutput;
        g_object_ref(element);
        break;
    case TP_MEDIA_STREAM_TYPE_VIDEO:
        element = self->videoOutput->element();
        break;
    default:
        Q_ASSERT(false);
    }

    gst_bin_add(GST_BIN(self->pipeline), element);

    pad = gst_element_get_static_pad(element, "sink");
    gst_element_set_state(element, GST_STATE_PLAYING);

    gst_pad_link(src, pad);

    self->status = StatusConnected;
    emit self->parent->statusChanged(self->status);
}

gboolean FarsightChannel::Private::onRequestResource(TfStream *stream,
        guint direction, gpointer data)
{
    return TRUE;
}

FarsightChannel::FarsightChannel(const StreamedMediaChannelPtr &channel,
        QObject *parent)
    : QObject(parent),
      mPriv(new Private(this, channel))
{
}

FarsightChannel::~FarsightChannel()
{
    delete mPriv;
}

VideoWidget *FarsightChannel::videoPreview() const
{
    return mPriv->videoPreview;
}

VideoWidget *FarsightChannel::videoWidget() const
{
    return mPriv->videoOutput;
}

} // Tp
