#include "gstCapture.h"
#include <iostream>

using namespace cv;
using namespace std;


gstCapture::gstCapture()
{
    pipeline = NULL;
    m_opened = false;
}

bool gstCapture::grabFrame()
{
    if(!GST_IS_ELEMENT(pipeline))
        return false;

    // start the pipeline if it was not in playing state yet
    if(!this->isPipelinePlaying())
        this->startPipeline();

    // bail out if EOS
    if(gst_app_sink_is_eos(GST_APP_SINK(sink)))
        return false;

    // if(sample)
//        gst_sample_unref(sample);
    sample = gst_app_sink_pull_sample(GST_APP_SINK(sink));
    if(!sample)
        return false;

    return true;
}

bool gstCapture::read(cv::OutputArray image)
{
    if(grabFrame())
        retrieveFrame(0,image);
    else
        image.release();
    return !image.empty();
}

bool gstCapture::isPipelinePlaying()
{
    if (!GST_IS_ELEMENT(pipeline))
    {
        std::cout << "GStreamer: pipeline have not been created"<< endl;
        return false;
    }
    GstState current, pending;
    GstClockTime timeout = 5*GST_SECOND;
    GstStateChangeReturn ret = gst_element_get_state(pipeline, &current, &pending, timeout);
    if (!ret)
    {
        std::cout << "GStreamer: unable to query pipeline state"<< endl;
        return false;
    }
    return current == GST_STATE_PLAYING;
}

bool gstCapture::close()
{
    std::cout << "Closing stream."<< endl;

    if (isPipelinePlaying())
        stopPipeline();
    if (pipeline && GST_IS_ELEMENT(pipeline))
    {
        gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_NULL);
        gst_object_unref(GST_OBJECT(pipeline));
    }
    if(sink)
        gst_object_unref(sink);
    
    m_opened = false;
    return true;
}

void gstCapture::startPipeline()
{
    if (!GST_IS_ELEMENT(pipeline))
    {
        std::cout << "GStreamer: pipeline have not been created"<< endl;
        return;
    }
    GstStateChangeReturn status = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    if (status == GST_STATE_CHANGE_ASYNC)
    {
        // wait for status update
        status = gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
    }
    if (status == GST_STATE_CHANGE_FAILURE)
    {
        gst_object_unref(pipeline);
        pipeline = NULL;
        std::cout << "GStreamer: unable to start pipeline"<< endl;
        return;
    }
}

void gstCapture::stopPipeline()
{
    if (!GST_IS_ELEMENT(pipeline))
    {
        std::cout <<"GStreamer: pipeline have not been created"<< endl;
        return;
    }
    if(gst_element_set_state(pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE)
    {
        std::cout << "GStreamer: unable to stop pipeline"<< endl;
        gst_object_unref(pipeline);
        pipeline = NULL;
    }
}

bool gstCapture::retrieveFrame(int, cv::OutputArray dst)
{
    if(!sample)
        return false;
 
    Size sz;
    if (!determineFrameDims(sz))
        return false;
    // gstreamer expects us to handle the memory at this point
    // so we can just wrap the raw buffer and be done with it
    GstBuffer *buf = gst_sample_get_buffer(sample);
    if (!buf)
        return false;
    GstMapInfo info;
    if (!gst_buffer_map(buf, &info, GST_MAP_READ))
    {
        //something weird went wrong here. abort. abort.
      	std::cout << "Failed to map GStreamerbuffer to system memory";
        return false;
    }
    Mat src = Mat(sz, CV_MAKETYPE(CV_8U, channels), info.data);
    CV_Assert(src.isContinuous());
    src.copyTo(dst);
    src.release();
    gst_buffer_unmap(buf, &info);
    gst_sample_unref(sample);
    return true;
}


bool gstCapture::openCapture(const std::string& filename)
{
    gst_init(NULL,NULL);

    if (m_opened) close();

    GError *err = NULL;
    pipeline = gst_parse_launch(filename.c_str(), &err);
    if(!pipeline)
    {
        std::stringstream ls;
        std::cout << "GStreamer: Error opening bin: " << err->message;
        GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-error");
        return false;
    }

    GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-init");

    GstStateChangeReturn status = gst_element_set_state(GST_ELEMENT(pipeline),
                                false ? GST_STATE_PAUSED : GST_STATE_PLAYING);
    if (status == GST_STATE_CHANGE_ASYNC)
    {
        // wait for status update
        status = gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
    }
    if (status == GST_STATE_CHANGE_FAILURE)
    {
        gst_object_unref(pipeline);
        pipeline = NULL;
        std::cout <<"GStreamer: unable to start pipeline" << endl;

        return false;
    }

    if (pipeline)
        {
            sink = gst_bin_get_by_name(GST_BIN(pipeline), "mysink");
            g_assert_nonnull(sink);
            m_opened = true;
            GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline");

            return true;
        }
    else
        {
            std::cout << "VIDEOIO can't create capture" << endl;
        }  

    return false;
}

bool gstCapture::determineFrameDims(Size &sz)
{
    GstCaps * frame_caps = gst_sample_get_caps(sample);
    // bail out in no caps
    if (!GST_CAPS_IS_SIMPLE(frame_caps))
        return false;

    gchar *temp_caps = gst_caps_to_string(frame_caps);
    std::string streaminfo = temp_caps;
    g_free(temp_caps);
    //std::string streaminfo = gst_caps_to_string(frame_caps);
    //std::cout << "Sample Frame caps:" << streaminfo;
    GstStructure* structure = gst_caps_get_structure(frame_caps, 0);

    
    // bail out if width or height are 0
    if (!gst_structure_get_int(structure, "width", &width)
        || !gst_structure_get_int(structure, "height", &height))
        return false;

    sz = Size(width, height);

    const gchar* name = gst_structure_get_name(structure);

    if (!name)
        return false;

    if (strcasecmp(name, "video/x-raw") == 0)
    {
        const gchar* format = gst_structure_get_string(structure, "format");
        if (!format)
            return false;
        if (strcasecmp(format, "BGR") == 0)
        {
            channels = 3;
        }
        else if( (strcasecmp(format, "UYVY") == 0) || (strcasecmp(format, "YUY2") == 0) || (strcasecmp(format, "YVYU") == 0) )
        {
            channels = 2;
        }
        else if( (strcasecmp(format, "NV12") == 0) || (strcasecmp(format, "NV21") == 0) || (strcasecmp(format, "YV12") == 0) || (strcasecmp(format, "I420") == 0) )
        {
            channels = 1;
            sz.height = sz.height * 3 / 2;
        }
        else if(strcasecmp(format, "GRAY8") == 0)
        {
            channels = 1;
        }
    }
    return true;
}