/*  This file is part of the KDE project.

    Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

    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 or 3 of the License.

    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, see <http://www.gnu.org/licenses/>.
*/

#include "qwidgetvideosink.h"

#include <QApplication>

#include <gst/video/video.h>

#include "videowidget.h"

#include "phonon-config-gstreamer.h"
#include <gst/gst.h>

namespace Phonon
{
namespace Gstreamer
{

static GstVideoSinkClass*   parentClass;

/*!
    \class gstreamer::QWidgetVideoSink
    \internal
*/

template <VideoFormat FMT>
GstCaps* QWidgetVideoSink<FMT>::get_caps(GstBaseSink* sink)
{
    Q_UNUSED(sink);
    return 0;
}

template <>
const char* QWidgetVideoSinkClass<VideoFormat_YUV>::get_name()
{
    return "QWidgetVideoSinkYUV";
}

template <>
const char* QWidgetVideoSinkClass<VideoFormat_RGB>::get_name()
{
    return "QWidgetVideoSinkRGB";
}

template <VideoFormat FMT>
gboolean QWidgetVideoSink<FMT>::set_caps(GstBaseSink* sink, GstCaps* caps)
{
    GstStructure *data;
    QWidgetVideoSink<FMT> *self = G_TYPE_CHECK_INSTANCE_CAST(sink, QWidgetVideoSinkClass<FMT>::get_type(), QWidgetVideoSink<FMT>);

    data = gst_caps_get_structure(caps, 0);

    gst_structure_get_int(data, "width", &self->width);
    gst_structure_get_int(data, "height", &self->height);
    gst_structure_get_int(data, "bpp", &self->bpp);
    gst_structure_get_int(data, "depth", &self->depth);
    return TRUE;
}

template <VideoFormat FMT>
GstStateChangeReturn QWidgetVideoSink<FMT>::change_state(GstElement* element, GstStateChange transition)
{
    return GST_ELEMENT_CLASS(parentClass)->change_state(element, transition);
}

template <VideoFormat FMT>
GstFlowReturn QWidgetVideoSink<FMT>::render(GstBaseSink* sink, GstBuffer* buf)
{
    GstFlowReturn rc = GST_FLOW_OK;

    if (buf != 0) {
        QWidgetVideoSink<FMT> *self = G_TYPE_CHECK_INSTANCE_CAST(sink, QWidgetVideoSinkClass<FMT>::get_type(), QWidgetVideoSink<FMT>);
        QByteArray frame;
        GstMapInfo info;
        gst_buffer_map(buf, &info, GST_MAP_READ);
        frame.resize(info.size);
        memcpy(frame.data(), info.data, info.size);
        gst_buffer_unmap(buf, &info);
        NewFrameEvent *frameEvent = new NewFrameEvent(frame, self->width, self->height);
        QApplication::postEvent(self->renderWidget, frameEvent);
    } else {
        rc = GST_FLOW_ERROR;
    }
    return rc;
}

static GstStaticPadTemplate template_factory_yuv =
    GST_STATIC_PAD_TEMPLATE("sink",
                            GST_PAD_SINK,
                            GST_PAD_ALWAYS,
                            GST_STATIC_CAPS(
                                    "video/x-raw, "
                                    "format = (string) {YUY2, YVYU, UYVY, Y41P, IYU2, Y42B, YV12, I420, Y41B, YUV9, YVU9, Y800}")
    );

static GstStaticPadTemplate template_factory_rgb =
    GST_STATIC_PAD_TEMPLATE("sink",
                            GST_PAD_SINK,
                            GST_PAD_ALWAYS,
                            GST_STATIC_CAPS(
                                    "video/x-raw, "
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
                                    "format = (string) BGRx,"
#else
                                    "format = (string) xRGB,"
#endif
                                    "width = " GST_VIDEO_SIZE_RANGE ","
                                    "height = " GST_VIDEO_SIZE_RANGE ","
                                    "framerate = " GST_VIDEO_FPS_RANGE)
    );

template <VideoFormat FMT>
struct template_factory;


template <>
struct template_factory<VideoFormat_YUV>
{
    static GstStaticPadTemplate *getFactory()
    {
        return &template_factory_yuv;
    }
};

template <>
struct template_factory<VideoFormat_RGB>
{
    static GstStaticPadTemplate *getFactory()
    {
        return &template_factory_rgb;
    }
};

template <VideoFormat FMT>
void QWidgetVideoSink<FMT>::base_init(gpointer g_class)
{
    gst_element_class_add_pad_template(GST_ELEMENT_CLASS(g_class),
                                       gst_static_pad_template_get(template_factory<FMT>::getFactory()));
}

template <VideoFormat FMT>
void QWidgetVideoSink<FMT>::instance_init(GTypeInstance *instance, gpointer g_class)
{
    Q_UNUSED(g_class);

    QWidgetVideoSink<FMT>* self = reinterpret_cast<QWidgetVideoSink<FMT>*>(instance);

    self->renderWidget = 0;
    self->width = 0;
    self->height = 0;
    self->bpp = 0;
    self->depth = 0;
}

// QWidgetVideoSinkClass
template <VideoFormat FMT>
void QWidgetVideoSinkClass<FMT>::class_init(gpointer g_class, gpointer class_data)
{
    Q_UNUSED(class_data);
    GstBaseSinkClass*   gstBaseSinkClass = (GstBaseSinkClass*)g_class;
    GstElementClass*    gstElementClass = (GstElementClass*)g_class;

    parentClass = reinterpret_cast<GstVideoSinkClass*>(g_type_class_peek_parent(g_class));

    // base
    gstBaseSinkClass->set_caps = QWidgetVideoSink<FMT>::set_caps;
    gstBaseSinkClass->preroll = QWidgetVideoSink<FMT>::render;
    gstBaseSinkClass->render = QWidgetVideoSink<FMT>::render;

    // element
    gstElementClass->change_state = QWidgetVideoSink<FMT>::change_state;
}

template <VideoFormat FMT>
GType QWidgetVideoSinkClass<FMT>::get_type()
{
    static GType type = 0;

    if (type == 0)
    {
        static const GTypeInfo info =
        {
            sizeof(QWidgetVideoSinkClass<FMT>),                 // class_size
            QWidgetVideoSink<FMT>::base_init,                   // base init
            NULL,                                               // base_finalize

            QWidgetVideoSinkClass<FMT>::class_init,             // class_init
            NULL,                                               // class_finalize
            NULL,                                               // class_data

            sizeof(QWidgetVideoSink<FMT>),                      // instance_size
            0,                                                  // n_preallocs
            QWidgetVideoSink<FMT>::instance_init,               // instance_init
            0                                                   // value_table
        };

        type = g_type_register_static(GST_TYPE_VIDEO_SINK,
                                      QWidgetVideoSinkClass<FMT>::get_name(),
                                      &info,
                                      GTypeFlags(0));
    }
    return type;
}

GType get_type_YUV()
{
    return QWidgetVideoSinkClass<VideoFormat_YUV>::get_type();
}

GType get_type_RGB()
{
    return QWidgetVideoSinkClass<VideoFormat_RGB>::get_type();
}

}
} //namespace Phonon::Gstreamer
