/* $Header: $ */

/* Patrick J. Thomas, California Institute of Technology, LIGO Hanford */


#include "acquisitiontype.h"

// From the documentation for the QImage constructor: "The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer." This is why a QPixmap copy of the QImage is sent instead of the QImage. If the QImage is sent then the buffer is not valid at the end of this callback and before the QImage is used in the slot function. The QPixmap copy is not reliant on the buffer remaining valid.
GstFlowReturn AcquisitionType::new_sample_callback(GstAppSink* appsink, gstreamer_data* user_data) {
	GstSample* sample;
	GstCaps* caps;
	GstStructure* s;
	GstBuffer* buffer;
	GstMapInfo map;
	QImage image;
	QPixmap pixmap;
	int width;
	int height;


	sample = gst_app_sink_pull_sample(appsink);
	if (sample) {
		caps = gst_sample_get_caps(sample);
		if (caps) {
			s = gst_caps_get_structure(caps, 0);

			if (gst_structure_get_int(s, "width", &width) && gst_structure_get_int(s, "height", &height)) {
				buffer = gst_sample_get_buffer(sample);

				if (buffer) {
					if (gst_buffer_map(buffer, &map, GST_MAP_READ)) {
						//printf("have sample\n");

						image = QImage(map.data, width, height, QImage::Format_RGBX8888);
						pixmap.convertFromImage(image);

						gst_buffer_unmap(buffer, &map);
					}
				}
			}
		}

		gst_sample_unref(sample);
	}

	emit (user_data->instance)->setData(pixmap);

	return GST_FLOW_OK;
}

GstPadProbeReturn AcquisitionType::udp_source_buffer_pad_probe_callback(GstPad* pad, GstPadProbeInfo* info, gstreamer_data* user_data) {
	(void) pad;
	(void) info;
	GstBus* bus;


	//printf("have data\n");

	bus = gst_element_get_bus(user_data->pipeline);
	user_data->signal_handler_id = g_signal_connect(G_OBJECT(bus), "message::element", (GCallback) udp_source_timeout_callback, user_data);
	gst_object_unref(bus);

	return GST_PAD_PROBE_REMOVE;
}

void AcquisitionType::udp_source_timeout_callback(GstBus* bus, GstMessage* message, gstreamer_data* user_data) {
	const GstStructure* st = gst_message_get_structure(message);
	GstPad* pad;
	QPixmap pixmap;


	if (GST_MESSAGE_TYPE(message) == GST_MESSAGE_ELEMENT) {
		if (gst_structure_has_name(st, "GstUDPSrcTimeout")) {
			//printf("no data\n");

			// Sends a null pixmap.
			emit (user_data->instance)->setData(pixmap);

			g_signal_handler_disconnect(G_OBJECT(bus), user_data->signal_handler_id);

			pad = gst_element_get_static_pad(user_data->udp_source, "src");
			gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, (GstPadProbeCallback) udp_source_buffer_pad_probe_callback, user_data, NULL);
			gst_object_unref(pad);
		}
	}
}

void AcquisitionType::bus_error_callback(GstBus* bus, GstMessage* message, gstreamer_data* user_data) {
	(void) bus;
	(void) user_data;
	GError* err;
	gchar* debug_info;


	gst_message_parse_error(message, &err, &debug_info);
	g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(message->src), err->message);
	g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
	g_clear_error(&err);
	g_free(debug_info);

	exit(-1);
}

AcquisitionType::AcquisitionType(char const* address, gint port, guint64 udp_timeout) {
	GstStateChangeReturn ret;
	GstBus* bus;


	gst_init(NULL, NULL);


	data.instance = this;

	data.udp_source = gst_element_factory_make("udpsrc", "udp_source");
    if (!data.udp_source) {
        g_printerr("Failed to create element 'udpsrc'\n");
        exit(-1);
    }

	g_object_set(G_OBJECT(data.udp_source),
		"address", address,
		"port", port,
		"caps", gst_caps_new_simple("application/x-rtp", "payload", G_TYPE_INT, 127, NULL),
		"timeout", udp_timeout,
		NULL);

	data.rtp_decoder = gst_element_factory_make("rtph264depay", "rtp_decoder");
    if (!data.rtp_decoder) {
        g_printerr("Failed to create element 'rtph264depay'\n");
        exit(-1);
    }

	data.video_decoder = gst_element_factory_make("avdec_h264", "video_decoder");
    if (!data.video_decoder) {
        g_printerr("Failed to create element 'avdec_h264'\n");
        exit(-1);
    }

	data.video_converter = gst_element_factory_make("videoconvert", "video_converter");
    if (!data.video_converter) {
        g_printerr("Failed to create element 'videoconvert'\n");
        exit(-1);
    }

	data.app_sink = gst_element_factory_make("appsink", "app_sink");
    if (!data.app_sink) {
        g_printerr("Failed to create element 'appsink'\n");
        exit(-1);
    }

	g_object_set(G_OBJECT(data.app_sink),
		"emit-signals", true,
    "max-buffers", G_GUINT64_CONSTANT(1),
		"drop", true,
		"sync", false,
		"caps", gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGBx", NULL),
		NULL);

	g_signal_connect(data.app_sink, "new-sample", (GCallback) new_sample_callback, &data);

	data.pipeline = gst_pipeline_new("pipeline");
    if (!data.pipeline) {
        g_printerr("Failed to create pipeline\n");
        exit(-1);
    }


	gst_bin_add_many(
		GST_BIN(data.pipeline),
		data.udp_source,
		data.rtp_decoder,
		data.video_decoder,
		data.video_converter,
		data.app_sink,
		NULL);


	if (gst_element_link_many(
		data.udp_source,
		data.rtp_decoder,
		data.video_decoder,
		data.video_converter,
		data.app_sink,
		NULL) != TRUE)
		{
			g_printerr("Elements could not be linked.\n");
			gst_object_unref(data.pipeline);
			exit(-1);
		}


	bus = gst_element_get_bus(data.pipeline);
	gst_bus_add_signal_watch(bus);
	g_signal_connect(G_OBJECT(bus), "message::error", (GCallback) bus_error_callback, &data);
	data.signal_handler_id = g_signal_connect(G_OBJECT(bus), "message::element", (GCallback) udp_source_timeout_callback, &data);
	gst_object_unref(bus);


	ret = gst_element_set_state(data.pipeline, GST_STATE_PLAYING);
	if (ret == GST_STATE_CHANGE_FAILURE) {
		g_printerr("Unable to set the pipeline to the playing state.\n");
		gst_object_unref(data.pipeline);
		exit(-1);
	}
}

AcquisitionType::~AcquisitionType() {
	GstBus* bus;


	gst_element_set_state(data.pipeline, GST_STATE_NULL);

	bus = gst_element_get_bus(data.pipeline);
	gst_bus_remove_signal_watch(bus);
	gst_object_unref(bus);

	gst_object_unref(data.pipeline);
}

