#include "qgstreamerplayer2.h"

#include "qrtspthread.h"
#include "qvideowidget.h"

#include "mainwindow.h"

QGstreamerPlayer2::QGstreamerPlayer2(QObject *parent) : QGstreamerPlayer(parent)
{

}

QGstreamerPlayer2::~QGstreamerPlayer2()
{

}

int QGstreamerPlayer2::OpenCamera(QString strVideoDevice)
{
    m_bWorkingRTSP = true;
    m_nFrameWidth = 0;
    m_nFrameHeight = 0;

    m_DataGST.m_strFilename = strVideoDevice;

    pid_t pid_current = getpid();
    GstBus *bus;
    GstStateChangeReturn ret;
    GstMessage *msg;
    GError *error = NULL;
    gboolean bTrapEnabled = FALSE;

    GstPad *queue_app_pad;
    GstCaps *video_caps;

    m_bTerminate = false;

    /* init */
    //int argc = 0;
    //gst_init (&argc, NULL);
    //gst_segtrap_set_enabled(FALSE);
    bTrapEnabled = gst_segtrap_is_enabled();

    QString strTest = QString("v4l2src device=/dev/video0 name=test_src ! video/x-raw,format=(string)YUY2,width=1920,height=1080,framerate=(fraction)60/1 ! queue name=queue_src ! tee name=teename ! queue name=queue_sink ! appsink name=test_sink");

#ifdef _GSTREAMER_1_0
#ifdef __x86_64
    m_DataGST.pipeline = gst_parse_launch(strTest.toStdString().c_str(), &error);

#else
#endif
#else
#endif

    if(error!=NULL)
    {
        g_printerr ("Can not Create Pipeline.\n");
        return -1;
    }

    GstElement* pVideoRate = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "test_videorate");
    GstElement* pTimeOverlay = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "test_timeoverlay");
    GstElement* pQueue1 = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "queue_name1");
    GstElement* pQueue2 = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "queue_name2");

    m_DataGST.source = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "test_src");
    m_DataGST.sink = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "test_sink");

    m_DataGST.queue_src = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "queue_src");
    m_DataGST.queue_sink = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "queue_sink");

    m_DataGST.tee = gst_bin_get_by_name(GST_BIN(m_DataGST.pipeline), "teename");

    m_DataGST.pWidget = m_pVideoWidget;
    m_DataGST.pPlayer = this;

    m_DataGST.m_bShow = true;

    g_signal_connect (m_DataGST.source, "pad-added", G_CALLBACK (QGstreamerPlayer::pad_added_handler), &m_DataGST);

    GstPad* pad = gst_element_get_static_pad (m_DataGST.sink, "sink");


    if (!m_DataGST.pipeline || !m_DataGST.source || !m_DataGST.queue_src || !m_DataGST.sink) {
       g_printerr ("Not all elements could be created.\n");
       return -2;
    }



    if(m_DataGST.queue_src!=NULL)
    {
        g_object_set (m_DataGST.queue_src, "max-size-buffers", 10, NULL);
    }

    if(m_DataGST.source!=NULL)
    {
        //g_object_set (m_DataGST.source, "device", strVideoDevice.toStdString().c_str(), NULL);

    }

    GstAppSinkCallbacks* appsink_callbacks = NULL;
    if(m_DataGST.sink!=NULL)
    {
        appsink_callbacks = (GstAppSinkCallbacks*)malloc(sizeof(GstAppSinkCallbacks));

        appsink_callbacks->eos = app_sink_eos;
        appsink_callbacks->new_preroll = app_sink_new_preroll;
#ifdef _GSTREAMER_1_0
        appsink_callbacks->new_sample = app_sink_new_sample;
#else
        appsink_callbacks->new_buffer = app_sink_new_sample;
#endif
        gst_app_sink_set_callbacks(GST_APP_SINK(m_DataGST.sink), appsink_callbacks, (gpointer)&m_DataGST, NULL);
        g_object_set (m_DataGST.sink, "sync", FALSE, NULL);
        gst_app_sink_set_drop(GST_APP_SINK(m_DataGST.sink), TRUE);

    }

    //ret = gst_element_set_state (m_DataGST.pipeline, GST_STATE_PLAYING);
    ret = gst_element_set_state (m_DataGST.pipeline, GST_STATE_PAUSED);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr ("Unable to set the pipeline to the playing state.\n");
        gst_object_unref (m_DataGST.pipeline);
        return -3;
    }

    bus = gst_element_get_bus (m_DataGST.pipeline);

    m_bPipeStart = true;

    bool bSignalOut = false;

    int64_t tEnd=0;
    do {
        msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
            (GstMessageType)(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_STEP_DONE | GST_MESSAGE_STEP_START | GST_MESSAGE_ASYNC_DONE));

        if(tEnd==0)
        {
            gst_element_query_duration (m_DataGST.pipeline, GST_FORMAT_TIME, &tEnd);
            if(tEnd!=0)
            {
                int nHour = 0;
                int nMinute = 0;
                int nSec = 0;

                int nMilliSec = tEnd/(1000*1000);
                nSec = nMilliSec/1000;
                nMinute = nSec/60;
                nHour = nMinute/60;

                nSec = nSec%60;
                nMinute = nMinute%60;
                nHour = nHour;
                nMilliSec = (nMilliSec/1000)%1000;
                qDebug() << "Duration: " << nHour << ":" << nMinute << ":" << nSec << ":" << nMilliSec ;

                GstState state;
                GstState pending;

                gst_element_get_state(m_DataGST.pipeline, &state, &pending, GST_CLOCK_TIME_NONE);
            }
        }
        /* Parse message */
        if (msg != NULL) {
          GError *err;
          gchar *debug_info;

          switch (GST_MESSAGE_TYPE (msg)) {
            case GST_MESSAGE_ERROR:
              gst_message_parse_error (msg, &err, &debug_info);
              g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
              g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
              g_clear_error (&err);
              g_free (debug_info);
              m_bTerminate = true;
              break;

            case GST_MESSAGE_EOS:
              g_print ("End-Of-Stream reached.\n");
              {
                  //gst_message_unref (msg);
                  bSignalOut = true;
                  m_bTerminate = true;
              }
              break;
            case GST_MESSAGE_STATE_CHANGED:
              /* We are only interested in state-changed messages from the pipeline */
              GstState old_state, new_state, pending_state;
              gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);

              if (GST_MESSAGE_SRC (msg) == GST_OBJECT (m_DataGST.pipeline)) {

                g_print ("Pipeline state changed from %s to %s:\n",
                    gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

                if(new_state==GST_STATE_PAUSED && old_state==GST_STATE_PLAYING)
                {
                    //m_bTerminate = true;
                }
              }
              break;

            case GST_MESSAGE_STEP_DONE:
            {
                GstFormat format;
                guint64 amount;
                gdouble rate;
                gboolean flush, intermediate;
                guint64 duration;
                gboolean eos;

                gst_message_parse_step_done (msg, &format, &amount, &rate,
                &flush, &intermediate, &duration, &eos);

                app_sink_new_sample((GstAppSink *)m_DataGST.sink, &m_DataGST);

            }
            break;

          case GST_MESSAGE_STEP_START:
          {

              break;
          }

          case GST_MESSAGE_ASYNC_DONE:
          {
              qDebug() << "Async Done";
              //app_sink_new_sample((GstAppSink *)m_DataGST.sink, &m_DataGST);
              m_bSeeking = false;
              break;
          }

            default:
              /* We should not reach here */
              g_printerr ("Unexpected message received.\n");
              break;
          }


          gst_message_unref (msg);

          if(m_DataGST.terminate==true)
          {
              m_bTerminate = true;
          }
        }
    } while (m_bTerminate==false);


    if(pVideoRate!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), pVideoRate);
        gst_element_set_state (pVideoRate, GST_STATE_NULL);
        gst_object_unref (pVideoRate);
    }

    if(pTimeOverlay!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), pTimeOverlay);
        gst_element_set_state (pTimeOverlay, GST_STATE_NULL);
        gst_object_unref (pTimeOverlay);
    }


    if(pQueue1!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), pQueue1);
        gst_element_set_state (pQueue1, GST_STATE_NULL);
        gst_object_unref (pQueue1);
    }

    if(pQueue2!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), pQueue2);
        gst_element_set_state (pQueue2, GST_STATE_NULL);
        gst_object_unref (pQueue2);
    }


    if(m_DataGST.source!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), m_DataGST.source);
        gst_element_set_state (m_DataGST.source, GST_STATE_NULL);
        gst_object_unref (m_DataGST.source);
    }

    if(m_DataGST.tee!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), m_DataGST.tee);
        gst_element_set_state (m_DataGST.tee, GST_STATE_NULL);
        gst_object_unref (m_DataGST.tee);
    }

    if(m_DataGST.queue_src!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), m_DataGST.queue_src);
        gst_element_set_state (m_DataGST.queue_src, GST_STATE_NULL);
        gst_object_unref (m_DataGST.queue_src);
    }


    if(m_DataGST.queue_sink!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), m_DataGST.queue_sink);
        gst_element_set_state (m_DataGST.queue_sink, GST_STATE_NULL);
        gst_object_unref (m_DataGST.queue_sink);
    }


    if(m_DataGST.sink!=NULL)
    {
        gst_bin_remove (GST_BIN (m_DataGST.pipeline), m_DataGST.sink);
        gst_element_set_state (m_DataGST.sink, GST_STATE_NULL);
        gst_object_unref (m_DataGST.sink);
    }


    if(m_DataGST.pipeline!=NULL)
    {
        gst_element_set_state (m_DataGST.pipeline, GST_STATE_NULL);
        gst_object_unref (m_DataGST.pipeline);
    }

    gst_object_unref (bus);

    free(appsink_callbacks);

    m_bPipeStart = false;
    m_bTerminate = false;
    m_DataGST.terminate = false;
    //gst_deinit();

    m_bWorkingRTSP = false;

    ExitCapture();

    if(bSignalOut==true)
    {
        return -1;
    }
    return 0;
}


void QGstreamerPlayer2::SaveFile(QString strFilename)
{

#ifdef __x86_64
    CustomDataSink *sink = NULL;
    GstPad *sinkpad;
    GstPadTemplate *templ;

    templ = gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (m_DataGST.tee), "src_%u");

    g_print ("add\n");

    if(m_DataGST.m_pSink==NULL)
    {
        sink = g_new0 (CustomDataSink, 1);
    }
    else
    {
        sink = m_DataGST.m_pSink;
    }


    sink->m_pTeePad = gst_element_request_pad (m_DataGST.tee, templ, NULL, NULL);

    if(sink->m_pQueue==NULL)
    {
        sink->m_pQueue = gst_element_factory_make ("queue", NULL);
    }

    if(sink->m_pConvert==NULL)
    {
        sink->m_pConvert = gst_element_factory_make ("videoconvert", NULL);
    }

    if(sink->m_pEncoder==NULL)
    {
        sink->m_pEncoder = gst_element_factory_make("x264enc", NULL);
    }

    if(sink->m_pParser==NULL)
    {
        sink->m_pParser = gst_element_factory_make("matroskamux", NULL);
    }

    if(sink->m_pSink==NULL)
    {
        sink->m_pSink = gst_element_factory_make ("filesink", NULL);
    }
    else
    {
        sink->m_pSink = gst_element_factory_make ("filesink", NULL);
    }





    sink->removing = FALSE;

    gst_bin_add_many (GST_BIN (m_DataGST.pipeline), (GstElement*)gst_object_ref (sink->m_pQueue),
                      gst_object_ref (sink->m_pConvert),
                      gst_object_ref (sink->m_pEncoder), gst_object_ref (sink->m_pParser), //gst_object_ref (pQueue2),
                      gst_object_ref (sink->m_pSink), NULL);



    gboolean link_ok;
    gst_element_link_many (sink->m_pQueue, sink->m_pConvert, sink->m_pEncoder, sink->m_pParser, sink->m_pSink, NULL);


    g_object_set (sink->m_pSink, "location", strFilename.toStdString().c_str(), NULL);

    g_object_set (sink->m_pEncoder, "bitrate", 10240, NULL);
    g_object_set (sink->m_pEncoder, "byte-stream", true, NULL);
    g_object_set (sink->m_pEncoder, "speed-preset", 1, NULL);
    g_object_set (sink->m_pEncoder, "tune", 4, NULL);


    g_object_set (sink->m_pSink, "sync", TRUE, NULL);
    gst_element_sync_state_with_parent (sink->m_pQueue);
    gst_element_sync_state_with_parent (sink->m_pConvert);
    gst_element_sync_state_with_parent (sink->m_pEncoder);
    gst_element_sync_state_with_parent (sink->m_pParser);
    gst_element_sync_state_with_parent (sink->m_pSink);

    sinkpad = gst_element_get_static_pad (sink->m_pQueue, "sink");
    gst_pad_link (sink->m_pTeePad, sinkpad);
    gst_object_unref (sinkpad);

    g_print ("added\n");

    gst_object_unref(templ);

    m_DataGST.m_pSink = sink;
#else
#endif
}

void QGstreamerPlayer2::SaveEnd()
{
    CustomDataSink* sink =m_DataGST.m_pSink;

    if(sink==NULL)
    {
        return;
    }

    GST_CustomData* pData = (GST_CustomData*)&m_DataGST;

    GstPad *sinkpad;

    sink->removing = TRUE;

    sinkpad = gst_element_get_static_pad (sink->m_pQueue, "sink");

    GstElement* pPipeLine = m_DataGST.pipeline;





    gst_pad_unlink (sink->m_pTeePad, sinkpad);
    gst_object_unref (sinkpad);
    //gst_pad_send_event (sinkpad, gst_event_new_eos ());

    gst_element_send_event(pData->m_pSink->m_pEncoder, gst_event_new_eos());
    //usleep(100*1000);

#ifdef __x86_64
    gst_bin_remove (GST_BIN (pData->pipeline), pData->m_pSink->m_pQueue);
    gst_bin_remove (GST_BIN (pData->pipeline), pData->m_pSink->m_pConvert);
    gst_bin_remove (GST_BIN (pData->pipeline), pData->m_pSink->m_pEncoder);
    gst_bin_remove (GST_BIN (pData->pipeline), pData->m_pSink->m_pParser);
    gst_bin_remove (GST_BIN (pData->pipeline), pData->m_pSink->m_pSink);

    gst_element_set_state (pData->m_pSink->m_pQueue, GST_STATE_NULL);
    gst_element_set_state (pData->m_pSink->m_pConvert, GST_STATE_NULL);
    gst_element_set_state (pData->m_pSink->m_pEncoder, GST_STATE_NULL);
    gst_element_set_state (pData->m_pSink->m_pSink, GST_STATE_NULL);
    gst_element_set_state (pData->m_pSink->m_pParser, GST_STATE_NULL);

    gst_object_unref (pData->m_pSink->m_pQueue);
    gst_object_unref (pData->m_pSink->m_pConvert);
    gst_object_unref (pData->m_pSink->m_pEncoder);
    gst_object_unref (pData->m_pSink->m_pParser);
    gst_object_unref (pData->m_pSink->m_pSink);
#else
#endif

    gst_element_release_request_pad (pData->tee, pData->m_pSink->m_pTeePad);
    gst_element_remove_pad(pData->tee, pData->m_pSink->m_pTeePad);
    gst_object_unref (pData->m_pSink->m_pTeePad);

    g_free(pData->m_pSink);
    pData->m_pSink = NULL;


    usleep(10*1000);
}
