/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2008, Nils Hasler, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

// Author: Nils Hasler <hasler@mpi-inf.mpg.de>
//
//         Max-Planck-Institut Informatik
//
// this implementation was inspired by gnash's gstreamer interface

//
// use GStreamer to read a video
//

#include "precomp.hpp"
#include <unistd.h>
#include <string.h>
#include <map>
#include <gst/gst.h>
#include <gst/video/video.h>
#ifdef HAVE_GSTREAMER_APP
#include <gst/app/gstappsink.h>
#include <gst/app/gstappsrc.h>
#include <gst/riff/riff-media.h>

#else
#include "gstappsink.h"
#endif

#ifdef NDEBUG
#define CV_WARN(message)
#else
#define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__)
#endif

static bool isInited = false;
class CvCapture_GStreamer : public CvCapture
{
public:
    CvCapture_GStreamer() { init(); }
    virtual ~CvCapture_GStreamer() { close(); }

    virtual bool open( int type, const char* filename );
    virtual void close();

    virtual double getProperty(int);
    virtual bool setProperty(int, double);
    virtual bool grabFrame();
    virtual IplImage* retrieveFrame(int);

protected:   
    void init();
    bool reopen(); 
    void handleMessage();
    void restartPipeline();
    void setFilter(const char*, int, int, int);
    void removeFilter(const char *filter);
    void static newPad(GstElement *myelement,
                             GstPad     *pad,
                             gpointer    data);
    GstElement	       *pipeline;
	GstElement	       *uridecodebin;
	GstElement	       *color;
	GstElement	       *sink;

	GstBuffer	       *buffer;
    GstCaps            *caps;
	IplImage	       *frame;
};
void CvCapture_GStreamer::init()
{
	pipeline=0;
	frame=0;
    buffer=0;
    frame=0;
    
}

void CvCapture_GStreamer::handleMessage()
{
	GstBus* bus = gst_element_get_bus(pipeline);

	while(gst_bus_have_pending(bus)) {
		GstMessage* msg = gst_bus_pop(bus);

//		printf("Got %s message\n", GST_MESSAGE_TYPE_NAME(msg));
        
		switch (GST_MESSAGE_TYPE (msg)) {
		case GST_MESSAGE_STATE_CHANGED:
			GstState oldstate, newstate, pendstate;
			gst_message_parse_state_changed(msg, &oldstate, &newstate, &pendstate);
//			printf("state changed from %d to %d (%d)\n", oldstate, newstate, pendstate);
			break;
		case GST_MESSAGE_ERROR: {
			GError *err;
			gchar *debug;
			gst_message_parse_error(msg, &err, &debug);

			fprintf(stderr, "GStreamer Plugin: Embedded video playback halted; module %s reported: %s\n",
				  gst_element_get_name(GST_MESSAGE_SRC (msg)), err->message);

			g_error_free(err);
			g_free(debug);

			gst_element_set_state(pipeline, GST_STATE_NULL);

			break;
			}
		case GST_MESSAGE_EOS:
//			CV_WARN("NetStream has reached the end of the stream.");

			break;
		default:
//			CV_WARN("unhandled message\n");
			break;
		}

		gst_message_unref(msg);
	}

	gst_object_unref(GST_OBJECT(bus));
}

//
// start the pipeline, grab a buffer, and pause again
//
bool CvCapture_GStreamer::grabFrame()
{
	
	if(!pipeline)
		return false;

	if(gst_app_sink_is_eos(GST_APP_SINK(sink))) {
		//printf("end of stream\n");
		return false;
	}

	if(buffer)
		gst_buffer_unref(buffer);
	handleMessage();

#ifndef HAVE_GSTREAMER_APP
	if(gst_app_sink_get_queue_length(GST_APP_SINK(sink)))
	{
//		printf("peeking buffer, %d buffers in queue\n",
		buffer = gst_app_sink_peek_buffer(GST_APP_SINK(sink));
	}
	else
#endif
	{
		buffer = gst_app_sink_pull_buffer(GST_APP_SINK(sink));
	} 
	if(!buffer)
		return false;

	return true;
}

//
// decode buffer
//
IplImage * CvCapture_GStreamer::retrieveFrame(int)
{
	if(!buffer)
		return false;

	if(!frame) {
		gint height, width;
        GstCaps *buff_caps = gst_buffer_get_caps(buffer);
		assert(gst_caps_get_size(buff_caps) == 1);
		GstStructure* structure = gst_caps_get_structure(buff_caps, 0);

		if(!gst_structure_get_int(structure, "width", &width) ||
	   !gst_structure_get_int(structure, "height", &height))
			return false;

		frame = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
		gst_caps_unref(buff_caps);
	}

	memcpy (frame->imageData, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE (buffer));
	//gst_data_copy_into (frame->imageData,GST_BUFFER_DATA(buffer));
	gst_buffer_unref(buffer);
	buffer = 0;
	return frame;
}

void CvCapture_GStreamer::restartPipeline()
{
	CV_FUNCNAME("icvRestartPipeline");

	__BEGIN__;

	printf("restarting pipeline, going to ready\n");

	if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_READY) ==
	   GST_STATE_CHANGE_FAILURE) {
		CV_ERROR(CV_StsError, "GStreamer: unable to start pipeline\n");
		return;
	}

	printf("ready, relinking\n");

	gst_element_unlink(uridecodebin, color);
	printf("filtering with %s\n", gst_caps_to_string(caps));
	gst_element_link_filtered(uridecodebin, color, caps);

	printf("relinked, pausing\n");

	if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING) ==
	   GST_STATE_CHANGE_FAILURE) {
		CV_ERROR(CV_StsError, "GStreamer: unable to start pipeline\n");
		return;
	}

	printf("state now paused\n");

 	__END__;
}

void CvCapture_GStreamer::setFilter(const char *property, int type, int v1, int v2)
{

	if(!caps) {
		if(type == G_TYPE_INT)
			caps = gst_caps_new_simple("video/x-raw-rgb", property, type, v1, NULL);
		else
			caps = gst_caps_new_simple("video/x-raw-rgb", property, type, v1, v2, NULL);
	} else {
		printf("caps before setting %s\n", gst_caps_to_string(caps));
		if(type == G_TYPE_INT)
			gst_caps_set_simple(caps, "video/x-raw-rgb", property, type, v1, NULL);
		else
			gst_caps_set_simple(caps, "video/x-raw-rgb", property, type, v1, v2, NULL);
	}	

	restartPipeline();
}

void CvCapture_GStreamer::removeFilter(const char *filter)
{
	if(!caps)
		return;

	GstStructure *s = gst_caps_get_structure(caps, 0);
	gst_structure_remove_field(s, filter);

	restartPipeline();
}


//
// connect uridecodebin dynamically created source pads to colourconverter
//
void CvCapture_GStreamer::newPad(GstElement *uridecodebin,
                             GstPad     *pad,
                             gpointer    data)
{
  GstPad *sinkpad;
  GstElement *color = (GstElement *) data;


  sinkpad = gst_element_get_static_pad (color, "sink");

  gst_pad_link (pad, sinkpad);

  gst_object_unref (sinkpad);
}

bool CvCapture_GStreamer::open( int type, const char* filename )
{
    close();
    CV_FUNCNAME("cvCaptureFromCAM_GStreamer");

	__BEGIN__;

//	teststreamer(filename);

//	return false;

	if(!isInited) {
//		printf("gst_init\n");
		gst_init (NULL, NULL);


		isInited = true;
	}
    bool stream=false;
    char *uri=NULL;
	//const char *sourcetypes[] = {"dv1394src", "v4lsrc", "v4l2src", "filesrc"};
	//printf("entered capturecreator %d\n", type);
    if  (type == CV_CAP_GSTREAMER_FILE) {	
		if (!gst_uri_is_valid(filename)) {
			uri=realpath(filename,NULL);
			if (uri)
				uri=g_filename_to_uri(uri,NULL,NULL);
			else {
				CV_WARN("Error opening file\n");
				close();
				return false;
			}	
			stream=false;
		}

		else {
			stream=true;
			uri=g_strdup (filename);
			printf("Trying to connect to stream \n");
		}	
		uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
		g_object_set(G_OBJECT(uridecodebin),"uri",uri, NULL);
		if(!uridecodebin) {
			close();
			return false;
		}		
	}	
	else {
		close();
		return false;
	}	
	color = gst_element_factory_make("ffmpegcolorspace", NULL);

#ifdef HAVE_GSTREAMER_APP
	sink = gst_element_factory_make("appsink", NULL);
	gst_app_sink_set_max_buffers (GST_APP_SINK(sink),1);
	if (stream) {
		gst_app_sink_set_drop (GST_APP_SINK(sink),true);
	}	
#else
	sink = gst_element_factory_make("opencv-appsink", NULL);
#endif
    GstCaps* caps= gst_caps_new_simple("video/x-raw-rgb",
									   "red_mask",   G_TYPE_INT, 255,
 	                                   "green_mask", G_TYPE_INT, 65280,
 	                                   "blue_mask",  G_TYPE_INT, 16711680,
                                       NULL);
    //GstCaps *caps=gst_video_format_new_caps(GST_VIDEO_FORMAT_BGR,,368,30,1,1,1);
    gst_app_sink_set_caps(GST_APP_SINK(sink), caps);
	gst_caps_unref(caps);
	g_signal_connect(uridecodebin, "pad-added", G_CALLBACK(newPad), color);

	pipeline = gst_pipeline_new (NULL);

	gst_bin_add_many(GST_BIN(pipeline), uridecodebin, color, sink, NULL);

	switch(type) {
	case CV_CAP_GSTREAMER_V4L2: // default to 640x480, 30 fps
		break;
	case CV_CAP_GSTREAMER_V4L:
	case CV_CAP_GSTREAMER_1394:
        break;
    }
	if(!gst_element_link(color, sink)) {
		CV_ERROR(CV_StsError, "GStreamer: cannot link color -> sink\n");
		gst_object_unref(pipeline);
		return false;
	}

//	printf("linked, pausing\n");

	if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_READY) ==
	   GST_STATE_CHANGE_FAILURE) {
		CV_WARN("GStreamer: unable to set pipeline to paused\n");
//		icvHandleMessage(capture);
//		cvReleaseCapture((CvCapture **)(void *)&capture);
		gst_object_unref(pipeline);
		return false;
	}

	if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING) ==
	   GST_STATE_CHANGE_FAILURE) {
		CV_WARN("GStreamer: unable to set pipeline to paused\n");
//		icvHandleMessage(capture);
//		cvReleaseCapture((CvCapture **)(void *)&capture);
		gst_object_unref(pipeline);
		return false;
	}



	handleMessage();

	__END__;
   
	return true;
}
#ifdef HAVE_GSTREAMER_APP
//
//
// gstreamer image sequence writer
//
//
class CvVideoWriter_GStreamer : public CvVideoWriter
{
public:
    CvVideoWriter_GStreamer() { init(); }
    virtual ~CvVideoWriter_GStreamer() { close(); }

    virtual bool open( const char* filename, int fourcc,
                       double fps, CvSize frameSize, bool isColor );
    virtual void close();
    virtual bool writeFrame( const IplImage* image ); 
protected:
    void init();
    std::map<int, char*> encs;
    GstElement* source;
    GstElement* file;
    GstElement* enc;
    GstElement* mux;
    GstElement* color;
    GstBuffer* buffer;
    GstElement* pipeline;
    int input_pix_fmt;
};

void CvVideoWriter_GStreamer::init()
{
	encs[CV_FOURCC('H','F','Y','U')]=(char*)"ffenc_huffyuv";
	encs[CV_FOURCC('D','R','A','C')]=(char*)"diracenc";
	encs[CV_FOURCC('X','V','I','D')]=(char*)"xvidenc";
	encs[CV_FOURCC('X','2','6','4')]=(char*)"x264enc";
	encs[CV_FOURCC('M','P','1','V')]=(char*)"mpeg2enc";
	//encs[CV_FOURCC('M','P','2','V')]=(char*)"mpeg2enc";
	pipeline=0;
	buffer=0;
}
void CvVideoWriter_GStreamer::close()
{
  if (pipeline) {	
	gst_app_src_end_of_stream(GST_APP_SRC(source));
	gst_element_set_state (pipeline, GST_STATE_NULL);
	gst_object_unref (GST_OBJECT (pipeline));
  }
}
bool CvVideoWriter_GStreamer::open( const char * filename, int fourcc,
		double fps, CvSize frameSize, bool is_color )
{
	CV_FUNCNAME("CvVideoWriter_GStreamer::open");
	
	__BEGIN__;
    //actually doesn't support fourcc parameter and encode an avi with jpegenc
    //we need to find a common api between backend to support fourcc for avi 
    //but also to choose in a common way codec and container format (ogg,dirac,matroska)
	// check arguments
	
	assert (filename);
	assert (fps > 0);
	assert (frameSize.width > 0  &&  frameSize.height > 0);
	std::map<int,char*>::iterator encit;
    encit=encs.find(fourcc);
    if (encit==encs.end()) 
        CV_ERROR( CV_StsUnsupportedFormat,"Gstreamer Opencv backend doesn't support this codec acutally.");  
    if(!isInited) {
		gst_init (NULL, NULL);
		isInited = true;
	}
	close();
    source=gst_element_factory_make("appsrc",NULL);
    file=gst_element_factory_make("filesink", NULL);
    enc=gst_element_factory_make(encit->second, NULL);
    mux=gst_element_factory_make("avimux", NULL);
    color = gst_element_factory_make("ffmpegcolorspace", NULL);
    if (!enc) 
		CV_ERROR( CV_StsUnsupportedFormat, "Your version of Gstreamer doesn't support this codec acutally or needed plugin missing.");
    g_object_set(G_OBJECT(file), "location", filename, NULL);
    pipeline = gst_pipeline_new (NULL);
    GstCaps* caps;
    if (is_color) {
        input_pix_fmt=1;
		caps= gst_video_format_new_caps(GST_VIDEO_FORMAT_BGR,
												 frameSize.width,
												 frameSize.height,
												 fps,
												 1,
												 1,
												 1); 	
	}								 
	else  {
		input_pix_fmt=0;
		caps= gst_caps_new_simple("video/x-raw-gray",
								  "width", G_TYPE_INT, frameSize.width,
								  "height", G_TYPE_INT, frameSize.height,
								  "framerate", GST_TYPE_FRACTION, int(fps),1,
								  "bpp",G_TYPE_INT,8, 
								  "depth",G_TYPE_INT,8,
                                           NULL);  
    }                                                                       
	gst_app_src_set_caps(GST_APP_SRC(source), caps);
	if (fourcc==CV_FOURCC_DEFAULT) {  
		gst_bin_add_many(GST_BIN(pipeline), source, color,mux, file, NULL);
		if(!gst_element_link_many(source,color,enc,mux,file,NULL)) {
			CV_ERROR(CV_StsError, "GStreamer: cannot link elements\n");
		}
	}	
	else { 
		gst_bin_add_many(GST_BIN(pipeline), source, color,enc,mux, file, NULL);
		if(!gst_element_link_many(source,color,enc,mux,file,NULL)) {
			CV_ERROR(CV_StsError, "GStreamer: cannot link elements\n");
		}	
	}


	if(gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING) ==
		GST_STATE_CHANGE_FAILURE) {
			CV_ERROR(CV_StsError, "GStreamer: cannot put pipeline to play\n");
	}
    __END__;
	return true;
}	
bool CvVideoWriter_GStreamer::writeFrame( const IplImage * image )
{
    
    CV_FUNCNAME("CvVideoWriter_GStreamer::writerFrame");

	__BEGIN__;
	if (input_pix_fmt == 1) {
        if (image->nChannels != 3 || image->depth != IPL_DEPTH_8U) {
            CV_ERROR(CV_StsUnsupportedFormat, "cvWriteFrame() needs images with depth = IPL_DEPTH_8U and nChannels = 3.");
        }
    }
	else if (input_pix_fmt == 0) {
        if (image->nChannels != 1 || image->depth != IPL_DEPTH_8U) {
            CV_ERROR(CV_StsUnsupportedFormat, "cvWriteFrame() needs images with depth = IPL_DEPTH_8U and nChannels = 1.");
        }
    }
	else {
        assert(false);
    }
    int size;
    size = image->imageSize;
	buffer = gst_buffer_new_and_alloc (size); 
    //gst_buffer_set_data (buffer,(guint8*)image->imageData, size);
    memcpy (GST_BUFFER_DATA(buffer),image->imageData, size); 
    gst_app_src_push_buffer(GST_APP_SRC(source),buffer);
    //gst_buffer_unref(buffer);
	//buffer = 0;
    __END__;
	return true;
}
CvVideoWriter* cvCreateVideoWriter_GStreamer(const char* filename, int fourcc, double fps,
                                           CvSize frameSize, int isColor )
{
    CvVideoWriter_GStreamer* wrt = new CvVideoWriter_GStreamer;
    if( wrt->open(filename, fourcc, fps,frameSize, isColor))
        return wrt;

    delete wrt;
    return false;
}
#else
CvVideoWriter* cvCreateVideoWriter_GStreamer(const char*, int, double, CvSize, int )
{
    return false;
}

#endif
void CvCapture_GStreamer::close()
{
	if(pipeline) {
		gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_NULL);
		gst_object_unref(GST_OBJECT(pipeline));
	}
	if(buffer)
		gst_buffer_unref(buffer);
}

double CvCapture_GStreamer::getProperty( int propId )
{
    GstFormat format;
	//GstQuery q;
	gint64 value;

	if(!pipeline) {
		CV_WARN("GStreamer: no pipeline");
		return false;
	}

	switch(propId) {
	case CV_CAP_PROP_POS_MSEC:
		format = GST_FORMAT_TIME;
		if(!gst_element_query_position(pipeline, &format, &value)) {
			CV_WARN("GStreamer: unable to query position of stream");
			return false;
		}
		return value * 1e-6; // nano seconds to milli seconds
	case CV_CAP_PROP_POS_FRAMES:
		format = GST_FORMAT_DEFAULT;
		if(!gst_element_query_position(pipeline, &format, &value)) {
			CV_WARN("GStreamer: unable to query position of stream");
			return false;
		}
		return value;
	case CV_CAP_PROP_POS_AVI_RATIO:
		format = GST_FORMAT_PERCENT;
		if(!gst_element_query_position(pipeline, &format, &value)) {
			CV_WARN("GStreamer: unable to query position of stream");
			return false;
		}
		return ((double) value) / GST_FORMAT_PERCENT_MAX;
	case CV_CAP_PROP_FRAME_WIDTH:
	case CV_CAP_PROP_FRAME_HEIGHT:
	case CV_CAP_PROP_FPS:
	case CV_CAP_PROP_FOURCC:
		break;
	case CV_CAP_PROP_FRAME_COUNT:
		format = GST_FORMAT_DEFAULT;
		if(!gst_element_query_duration(pipeline, &format, &value)) {
			CV_WARN("GStreamer: unable to query position of stream");
			return false;
		}
		return value;
	case CV_CAP_PROP_FORMAT:
	case CV_CAP_PROP_MODE:
	case CV_CAP_PROP_BRIGHTNESS:
	case CV_CAP_PROP_CONTRAST:
	case CV_CAP_PROP_SATURATION:
	case CV_CAP_PROP_HUE:
	case CV_CAP_PROP_GAIN:
	case CV_CAP_PROP_CONVERT_RGB:
		break;
	default:
		CV_WARN("GStreamer: unhandled property");
		break;
	}
	return false;
}

bool CvCapture_GStreamer::setProperty( int propId, double value )
{
   	GstFormat format;
	GstSeekFlags flags;

	if(!pipeline) {
		CV_WARN("GStreamer: no pipeline");
		return false;
	}

	switch(propId) {
	case CV_CAP_PROP_POS_MSEC:
		format = GST_FORMAT_TIME;
		flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_ACCURATE);
		if(!gst_element_seek_simple(GST_ELEMENT(pipeline), format,
					    flags, (gint64) (value * GST_MSECOND))) {
			CV_WARN("GStreamer: unable to seek");
		}
		break;
	case CV_CAP_PROP_POS_FRAMES:
		format = GST_FORMAT_DEFAULT;
		flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_ACCURATE);
		if(!gst_element_seek_simple(GST_ELEMENT(pipeline), format,
					    flags, (gint64) value)) {
			CV_WARN("GStreamer: unable to seek");
		}
		break;
	case CV_CAP_PROP_POS_AVI_RATIO:
		format = GST_FORMAT_PERCENT;
		flags = (GstSeekFlags) (GST_SEEK_FLAG_FLUSH|GST_SEEK_FLAG_ACCURATE);
		if(!gst_element_seek_simple(GST_ELEMENT(pipeline), format,
					    flags, (gint64) (value * GST_FORMAT_PERCENT_MAX))) {
			CV_WARN("GStreamer: unable to seek");
		}
		break;
	case CV_CAP_PROP_FRAME_WIDTH:
		if(value > 0)
			setFilter("width", G_TYPE_INT, (int) value, 0);
		else
			removeFilter("width");
		break;
	case CV_CAP_PROP_FRAME_HEIGHT:
		if(value > 0)
			setFilter("height", G_TYPE_INT, (int) value, 0);
		else
			removeFilter("height");
		break;
	case CV_CAP_PROP_FPS:
		if(value > 0) {
			int num, denom;
			num = (int) value;
			if(value != num) { // FIXME this supports only fractions x/1 and x/2
				num = (int) (value * 2);
				denom = 2;
			} else
				denom = 1;

			setFilter("framerate", GST_TYPE_FRACTION, num, denom);
		} else
			removeFilter("framerate");
		break;
	case CV_CAP_PROP_FOURCC:
	case CV_CAP_PROP_FRAME_COUNT:
	case CV_CAP_PROP_FORMAT:
	case CV_CAP_PROP_MODE:
	case CV_CAP_PROP_BRIGHTNESS:
	case CV_CAP_PROP_CONTRAST:
	case CV_CAP_PROP_SATURATION:
	case CV_CAP_PROP_HUE:
	case CV_CAP_PROP_GAIN:
	case CV_CAP_PROP_CONVERT_RGB:
		break;
	default:
		CV_WARN("GStreamer: unhandled property");
	}
	return false;
}
CvCapture* cvCreateCapture_GStreamer(int type, const char* filename )
{
    CvCapture_GStreamer* capture = new CvCapture_GStreamer;

    if( capture->open( type, filename ))
        return capture;

    delete capture;
    return false;
}
