// video.h

#ifndef VIDEO_H
#define VIDEO_H

#include <QtGui/QWidget>
#include <QTimer>
#include <QString>

class Video : public QObject
{
    Q_OBJECT

public:
    Video(QString cmd, WId handle, WId handle2, WId handle3, WId handle4, WId handle5);
    ~Video();

private slots:
    void startPollingVideoBus();
    void tick();

private:
    void setupVideo();
    void cleanup();

    void videoFuture();

private:
    QTimer timer_;
    bool shuttingDown_;

};

#endif // VIDEO_H

/******************************************************************************/

// video.cc

#include <gstreamer-1.0/gst/gst.h>
#include <gstreamer-1.0/gst/video/videooverlay.h>

#include "video.h"

#include <QtConcurrentRun>
#include <QFutureWatcher>

namespace /* anonymous */
{
int const BUS_POLL_RATE = 1000/10;

bool gstreamerInit = false;
GstElement *pipeline = 0;
GstBus *bus = 0;
GstMessage *msg = 0;
guintptr window_handle[5] = {0, 0, 0, 0, 0};

QFutureWatcher<void> *watcher = 0;
QFuture<void> future;

QString videoCmd;
}; // namespace

/**
 * Callback function to handle gstreamer sync bus messages
 */
static GstBusSyncReply bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
{
    if(GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT) return GST_BUS_PASS;

    if(!gst_is_video_overlay_prepare_window_handle_message(message)) return GST_BUS_PASS;

    if(window_handle)
    {
        GstVideoOverlay *overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message)); // GST_MESSAGE_SRC (message) will be the video sink element

        if(window_handle[0] != 0) if(strstr(message->src->name, "left"))    gst_video_overlay_set_window_handle(overlay, window_handle[0]);
        if(window_handle[1] != 0) if(strstr(message->src->name, "forward")) gst_video_overlay_set_window_handle(overlay, window_handle[1]);
        if(window_handle[2] != 0) if(strstr(message->src->name, "right"))   gst_video_overlay_set_window_handle(overlay, window_handle[2]);
        if(window_handle[3] != 0) if(strstr(message->src->name, "rear"))    gst_video_overlay_set_window_handle(overlay, window_handle[3]);
        if(window_handle[4] != 0) if(strstr(message->src->name, "external")) gst_video_overlay_set_window_handle(overlay, window_handle[4]); 
    }

    gst_message_unref(message);
    return GST_BUS_DROP;
}

Video::Video(QString cmd, WId handle, WId handle2, WId handle3, WId handle4, WId handle5)
{
    videoCmd = cmd;

    window_handle[0] = (guintptr)handle; // store the window handle for later use by gstreamer
    window_handle[1] = (guintptr)handle2;
    window_handle[2] = (guintptr)handle3;
    window_handle[3] = (guintptr)handle4;
    window_handle[4] = (guintptr)handle5;

    videoFuture();
}

Video::~Video()
{
    cleanup();
    if(gstreamerInit) gst_deinit();
}

void Video::videoFuture()
{
    // Use a seperate thread to setup the video
    // When video setup is complete, start polling the bus for messages
    watcher = new QFutureWatcher<void>();
    if(watcher)
    {
        QObject::connect( watcher, SIGNAL(finished()), this, SLOT(startPollingVideoBus()) );
        future = QtConcurrent::run(this, &Video::setupVideo);
        watcher->setFuture(future);
    }
}

void Video::setupVideo()
{
    GError *error = 0;

    static int count = 0;
    qDebug() << "setupVideo() " << count++;

    /* Initialize GStreamer */
    if(gstreamerInit == false)
    {
        if(gst_init_check (0, NULL, &error))
        {
            gstreamerInit = true;
        }
        else
        {
            return; // abort
        }
    }

/*
    videoCmd is read from an xml file but the following is a typical example

    uridecodebin name=vehiclebin uri=rtsp://127.0.0.1:8554/vehicle.sdp !
                 tee name=tp
                 tp. ! queue ! videocrop top=0   left=10  right=376 bottom=290 ! autovideosink name=rear
                 tp. ! queue ! videocrop top=0   left=400 right=0   bottom=290 ! autovideosink name=forward
                 tp. ! queue ! videocrop top=290 left=10  right=376 bottom=6   ! autovideosink name=left
                 tp. ! queue ! videocrop top=290 left=398 right=0   bottom=6   ! autovideosink name=right
*/

    /* Build the pipeline */
    pipeline = gst_parse_launch(videoCmd.toStdString().c_str(), &error);

    if(pipeline)
    {
        /* Get the bus */
        bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));

        /* Direct the gstreamer output to the Qt Label Widget */
        gst_bus_set_sync_handler(bus, (GstBusSyncHandler)bus_sync_handler, NULL, NULL); // register handle, widget for overlay will be assigned in the sync callback

        /* Start playing */
        if(gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL)
        {
            qDebug() << "Live source";
        }
    }
    else if(error)
    {
        qDebug() << "GST_MESSAGE_ERROR: " << error->message;
    }
}

void Video::startPollingVideoBus()
{
    if(watcher)
    {
        delete watcher;
        watcher = 0;
    }

    qDebug() << "startPollingVideoBus()";

    if(pipeline && bus)
    {
        QObject::connect( &timer_, SIGNAL(timeout()), SLOT(tick()) );
        timer_.start(BUS_POLL_RATE); // check the gstreamer bus for messages at the given rate
    }
}

void Video::tick()
{
    if(pipeline == 0 || bus == 0) return; // abort

    GError *err = 0;
    gchar *debug = 0;
    int code = 0;
    bool restartGStreamer = false;

    GstStreamStatusType type;
    GstElement *owner;
    const GValue *val;

    do
    {
        msg = gst_bus_pop_filtered(bus, static_cast<GstMessageType>(GST_MESSAGE_ANY)); //GST_MESSAGE_WARNING | GST_MESSAGE_ERROR | GST_MESSAGE_EOS  | GST_MESSAGE_CLOCK_LOST | GST_MESSAGE_STREAM_STATUS | GST_MESSAGE_BUFFERING | GST_MESSAGE_LATENCY | GST_STATE_CHANGE_NO_PREROLL));
        if(msg)
        {
            switch(GST_MESSAGE_TYPE(msg))
            {
            case GST_MESSAGE_WARNING:
                qDebug() << "GST_MESSAGE_WARNING";
                break;

            case GST_MESSAGE_EOS:
                qDebug() << "GST_MESSAGE_EOS";

                gst_element_set_state(pipeline, GST_STATE_READY);

                // streaming
                restartGStreamer = true;
                break;

            case GST_MESSAGE_ERROR:
                gst_message_parse_error(msg, &err, &debug);
                code = err->code;
                qDebug() << "GST_MESSAGE_ERROR: " << err->message;
                g_error_free(err);
                g_free(debug);
                gst_element_set_state (pipeline, GST_STATE_NULL);

                if(code == 1) // internal data flow error
                {
                    restartGStreamer = true;
                }
                else if(code == 7) // could not open resourece for reading and writing
                {
                    restartGStreamer = true;
                }
                break;

            case GST_MESSAGE_CLOCK_LOST:
                qDebug() << "GST_MESSAGE_CLOCK_LOST";
                break;

            case GST_MESSAGE_STREAM_STATUS:
                qDebug() << "GST_MESSAGE_STREAM_STATUS";
                //gst_message_parse_stream_status(msg, &type, &owner);
                //val = gst_message_get_stream_status_object(msg);  
                break;

            case GST_MESSAGE_BUFFERING:
                // ignore buffering messages as using a live stream
                qDebug() << "GST_MESSAGE_BUFFERING";
                break;

            case GST_MESSAGE_LATENCY:
                qDebug() << "GST_MESSAGE_LATENCY";
                break;

            case GST_STATE_CHANGE_NO_PREROLL:
                qDebug() << "GST_STATE_CHANGE_NO_PREROLL";
                break;

            default:
                break;
            }

            gst_message_unref(msg);
        }
    }
    while(msg && restartGStreamer == false);

    if(restartGStreamer)
    {
        qDebug() << "restarting gstreamer";

        cleanup();
        videoFuture();
    }
}

void Video::cleanup()
{
    qDebug() << "cleanup()";

    timer_.stop();

    if(watcher)
    {
        delete watcher;
        watcher = 0; 
    }

    if(bus)
    {
        gst_bus_set_sync_handler(bus, NULL, NULL, NULL);
        gst_object_unref (bus);
        bus = 0;
    }

    if(pipeline)
    {
        gst_element_set_state (pipeline, GST_STATE_NULL); // stop playing
        gst_object_unref (pipeline);
        pipeline = 0;
    }
}
