#include "videostream.h"
#include "videowidget.h"
#include <QListIterator>

/*lint -e960 bus has been commented out to avoid compiler warnings*/
bool VideoStream::handleMessage (GstBus * /* bus */, GstMessage *message, gpointer data)
/*lint +960*/
{
    VideoStream *videoStream = (VideoStream *)data;
    switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:
        {
            GError *gerror;
            /*lint -e971 Required by Gstreamer interface*/
            gchar *debug;
            /*lint +e971*/
            
            /* dump graph on error */
            GST_DEBUG_BIN_TO_DOT_FILE (GST_BIN (videoStream->a_pipeline),
                GST_DEBUG_GRAPH_SHOW_ALL, "video.error");
            
            gst_message_parse_error (message, &gerror, &debug);
            gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
            g_error_free (gerror);
            g_free (debug);
            break;
        }
        
    default:
        break;
    }
    return true;
}

VideoStream::VideoStream(unsigned short int rtpPort, unsigned long int rtcpPort, const QString& rtcpHost)
{
    GstCaps *capsSource;
    
    a_rtpPort = rtpPort;
    a_rtcpPort = rtcpPort;
    a_rtcpHost = rtcpHost;
    
    /* init */
    gst_init (0, NULL);
    
    /* create pipeline */
    a_pipeline = gst_pipeline_new (NULL);
    
    /* get the pipeline bus and set the bus message handler.
    The installed handler shall raise signals when messages are received. */
    a_bus = gst_pipeline_get_bus (GST_PIPELINE (a_pipeline));
    gst_bus_set_sync_handler (a_bus, gst_bus_sync_signal_handler, NULL);
    g_signal_connect (a_bus, "sync-message", G_CALLBACK (handleMessage), this);
    
    capsSource = gst_caps_new_simple ("application/x-rtp",
        "media", G_TYPE_STRING, "video",
        "payload", G_TYPE_INT, 96,
        "clock-rate", G_TYPE_INT, 90000,
        "encoding-name", G_TYPE_STRING, "MP4V-ES",
        NULL);
    
    /* create elements */
    a_rtpSource = gst_element_factory_make ("udpsrc", NULL);
    g_object_set (G_OBJECT (a_rtpSource), "port", a_rtpPort, NULL);
    g_object_set (G_OBJECT (a_rtpSource), "caps", capsSource, NULL);
    a_rtpSession = gst_element_factory_make ("gstrtpsession", NULL);
    a_depay = gst_element_factory_make ("rtpmp4vdepay", NULL);
    a_decoder = gst_element_factory_make ("ffdec_mpeg4", NULL);
    a_tee = gst_element_factory_make ("tee", NULL);
    a_rtcpSink = gst_element_factory_make ("udpsink", NULL);
    g_object_set (G_OBJECT (a_rtcpSink), "host", a_rtcpHost.toLocal8Bit().data(), NULL);
    g_object_set (G_OBJECT (a_rtcpSink), "port", a_rtcpPort, NULL);
    
    if (!a_pipeline || !a_rtpSource || !a_rtpSession ||
        !a_depay || !a_decoder || !a_tee || !a_rtcpSink)
    {
        qFatal("GStreamer elements creation failure");
    }
    
    bool success=true;
    
    /* must add elements to pipeline before linking them */
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_rtpSource);
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_rtpSession);
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_depay);
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_decoder);
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_tee);
    success&=(bool)gst_bin_add (GST_BIN (a_pipeline), a_rtcpSink);
    if (!success) {
        qFatal("GStreamer elements add failure");
    }
    
    /* link */
    success&=(bool)gst_element_link_pads (a_rtpSource, "src", a_rtpSession, "recv_rtp_sink");
    success&=(bool)gst_element_link_pads (a_rtpSession, "recv_rtp_src", a_depay, "sink");
    success&=(bool)gst_element_link_pads (a_rtpSession, "send_rtcp_src", a_rtcpSink, "sink");
    success&=(bool)gst_element_link_many (a_depay, a_decoder, a_tee, NULL);
    if (!success) {
        qFatal("GStreamer elements link failure");
    }
}

VideoStream::~VideoStream()
{
    /* stop the pipeline */
    const GstStateChangeReturn result = gst_element_set_state (GST_ELEMENT (a_pipeline), GST_STATE_NULL);
    Q_ASSERT (result != GST_STATE_CHANGE_FAILURE);
    /* disconnect all widgets */
    QListIterator<VideoWidget*> i(a_connectedWidgets);
    while (i.hasNext()) {
        i.next()->disconnect();
    }
    gst_object_unref (GST_OBJECT (a_pipeline));
}

void VideoStream::addConnection(VideoWidget *videoWidget)
{
    a_connectedWidgets.append(videoWidget);
}

void VideoStream::removeConnection(VideoWidget *videoWidget)
{
    a_connectedWidgets.removeAll(videoWidget);
}

void VideoStream::setRtpPort(unsigned short rtpPort)
{
    Q_ASSERT (a_rtpSource != NULL);
    a_rtpPort = rtpPort;
    g_object_set (G_OBJECT (a_rtpSource), "port", rtpPort, NULL);
}

unsigned short int VideoStream::getRtpPort() const
{
    return a_rtpPort;
}

void VideoStream::setRtcpPort(unsigned short rtcpPort)
{
    Q_ASSERT (a_rtcpSink != NULL);
    a_rtcpPort = rtcpPort;
    g_object_set (G_OBJECT (a_rtcpSink), "port", rtcpPort, NULL);
}

unsigned short int VideoStream::getRtcpPort() const
{
    return a_rtcpPort;
}

void VideoStream::setRtcpHost(const QString& rtcpHost)
{
    Q_ASSERT (a_rtcpSink != NULL);
    a_rtcpHost = rtcpHost;
    g_object_set (G_OBJECT (a_rtcpSink), "host", rtcpHost.toLocal8Bit().data(), NULL);
}

QString VideoStream::getRtcpHost() const
{
    return a_rtcpHost;
}

void VideoStream::play()
{
    /* start the pipeline */
    const GstStateChangeReturn result = gst_element_set_state (GST_ELEMENT (a_pipeline), GST_STATE_PLAYING);
    Q_ASSERT (result != GST_STATE_CHANGE_FAILURE);
}

void VideoStream::stop()
{
    /* stop the pipeline */
    const GstStateChangeReturn result = gst_element_set_state (GST_ELEMENT (a_pipeline), GST_STATE_NULL);
    Q_ASSERT (result != GST_STATE_CHANGE_FAILURE);
}

VideoStream::E_STREAM_STATE VideoStream::getStreamState() const
{
    E_STREAM_STATE state;
    /*lint -e961 GST_STATE macro is part of Gstreamer interface*/
    switch (GST_STATE(a_pipeline)) {
        /*lint +e961*/
    case GST_STATE_NULL:
        state = E_STREAM_STOPPED;
        break;
    case GST_STATE_PLAYING:
        state = E_STREAM_PLAYING;
        break;
    case GST_STATE_PAUSED:
        state = E_STREAM_PAUSED;
        break;
    default:
        state = E_STREAM_ERROR;
        break;
    }
    return state;
}
