/*
 * main.c
 *
 *  Created on: Feb 20, 2014
 *      Author: memphis
 *
 * gcc -Wall $(pkg-config --cflags gstreamer-gl-1.0) main.c -o main $(pkg-config --libs gstreamer-gl-1.0 gstreamer-vaapi-glx-1.0) -lX11
 */
#include <glib.h>
#include "gst/gst.h"
#include "X11/Xlib.h"

#include <gst/gl/x11/gstgldisplay_x11.h>
#include <gst/gl/x11/gstglcontext_glx.h>
#include <gst/vaapi/gstvaapidisplay_glx.h>

static gchar* fileLocation = NULL;
static gint simultaneousDisplay = 1;
static gint displayCount = -1;
static GMutex gMutex;

static Display *x11_display = NULL;

typedef struct _DisplayData DisplayData;
struct _DisplayData {
	GstElement* pipeline;
	GstBus* bus;
	GMainLoop* loop;
	GMainContext* context;
	GstGLDisplay *gl_display;
	GstVaapiDisplay *vaapi_display;
	GstContext *x11_context;
};

static void
demux_pad_added(GstElement* object, GstPad* arg0, gpointer user_data) {
	DisplayData* data = (DisplayData*) user_data;

	GstCaps* caps = gst_pad_query_caps(arg0, NULL);
	GstStructure* struc = gst_caps_get_structure(caps, 0);
	const gchar* name = gst_structure_get_name(struc);
	if (g_str_has_suffix(name, "h264")) {
		GstElement* bin = gst_bin_new("decodebin");
		GstElement* h264parse = gst_element_factory_make("h264parse", NULL);
		GstElement* decode = gst_element_factory_make("vaapidecode", NULL);
		GstElement* glColorScale = gst_element_factory_make("glcolorscale", NULL);
//		GstElement* capsFilter = gst_element_factory_make("capsfilter", NULL);
//		GstElement* displaySink = gst_element_factory_make("xvimagesink", NULL);
		GstElement* displaySink = gst_element_factory_make("glimagesink", NULL);

//		GstCaps* capsFilterProp = gst_caps_from_string("video/x-raw, height=500, width=500");
//		g_object_set(capsFilter, "caps", capsFilterProp, NULL);
//		gst_caps_unref(capsFilterProp);

		gst_bin_add_many(GST_BIN(bin), h264parse, decode, glColorScale, displaySink, NULL);
		gst_element_link_many(h264parse, decode, glColorScale, displaySink, NULL);
		//Create the ghost pad
		GstPad* pad = gst_element_get_static_pad (h264parse, "sink");
		gst_element_add_pad (GST_ELEMENT(bin), gst_ghost_pad_new ("sink", pad));
		gst_object_unref (GST_OBJECT (pad));

		//add and sync the decode bin
		gst_bin_add(GST_BIN(data->pipeline), bin);
		GstPad* binSinkPad = gst_element_get_static_pad(bin, "sink");
		gst_pad_link(arg0, binSinkPad);
		gst_object_unref(binSinkPad);
		gst_element_set_state(bin, GST_STATE_PLAYING);


	} else {
		gchar* capsStr = gst_caps_to_string(caps);
		g_message("The following caps isn't supported by this test application : %s", capsStr);
		g_free(capsStr);

		GstElement* fakesink = gst_element_factory_make("fakesink", NULL);
		g_object_set(fakesink, "async", FALSE, "sync", FALSE, NULL);
		gst_bin_add(GST_BIN(data->pipeline), fakesink);

		GstPad* fakesinkPad = gst_element_get_static_pad(fakesink, "sink");
		if (GST_PAD_LINK_FAILED(gst_pad_link(arg0, fakesinkPad)))
			g_warning("Failed to link to the fake sink !!! :S");
		gst_object_unref(fakesinkPad);

		gst_element_set_state(fakesink, GST_STATE_PLAYING);
	}
	gst_caps_unref(caps);
}

static void
display_create_pipeline(DisplayData* data) {

	GstElement* filesrc = gst_element_factory_make("filesrc", NULL);
	GstElement* demux = gst_element_factory_make("matroskademux", NULL);

	g_object_set(filesrc, "location", fileLocation, NULL);
	g_signal_connect(demux, "pad-added", G_CALLBACK(demux_pad_added), data);
	gst_bin_add_many(GST_BIN(data->pipeline), filesrc, demux, NULL);
	gst_element_link(filesrc, demux);
}

static gboolean
display_bus(GstBus* bus, GstMessage* message, gpointer user_data) {
	DisplayData* data = (DisplayData*) user_data;

	switch (GST_MESSAGE_TYPE (message)) {
		case GST_MESSAGE_EOS:
			g_print("Receive EOS on the BUS\n");
			g_main_loop_quit(data->loop);
			return G_SOURCE_REMOVE;
		  break;
	case GST_MESSAGE_ERROR: {
		gchar  *debug;
		GError *error;

		gst_message_parse_error (message, &error, &debug);
		g_free (debug);

		g_printerr ("Error: %s\n", error->message);
		g_error_free (error);

		g_main_loop_quit (data->loop);
		break;
	}
	default:
		break;
	}

	return G_SOURCE_CONTINUE;
}

static void
_open_x11 ()
{
//  GstGLContext *context;

  if (!x11_display)
    x11_display = XOpenDisplay (NULL);

//  if (!gl_display)
//    gl_display = GST_GL_DISPLAY (gst_gl_display_x11_new_with_display (x11_display));

//  context = gst_gl_context_glx_new ();

//  gst_object_unref (context);
}

static gboolean
sync_bus_call (GstBus     *bus,
          GstMessage *msg,
          gpointer    user_data)
{
  DisplayData *data = (DisplayData *) user_data;

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_NEED_CONTEXT:
    {
      const gchar *context_type;
      GstContext *context = NULL;
      gboolean free_context = TRUE;
      
      gst_message_parse_context_type (msg, &context_type);
      g_print("got need context %s\n", context_type);

      if (g_strcmp0 (context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
      
        _open_x11 ();

        if (!data->gl_display)
          data->gl_display = GST_GL_DISPLAY (gst_gl_display_x11_new_with_display (x11_display));
          //gl_display = GST_GL_DISPLAY (gst_gl_display_x11_new (":0"));

        context = gst_context_new (GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
        gst_context_set_gl_display (context, data->gl_display);

        gst_element_set_context (GST_ELEMENT (msg->src), context);
        data->x11_context = context;
        free_context = FALSE;
      }
#if 0
       else if (g_strcmp0 (context_type, "gst.vaapi.Display") == 0) {
        GstStructure *s;

        _open_x11 ();

        if (!data->vaapi_display)
          data->vaapi_display = gst_vaapi_display_glx_new_with_display (x11_display);

        context = gst_context_new ("gst.vaapi.Display", TRUE);
        s = gst_context_writable_structure (context);
        gst_structure_set (s, "display", GST_VAAPI_TYPE_DISPLAY_TYPE,
            data->vaapi_display, NULL);

        gst_element_set_context (GST_ELEMENT (msg->src), context);
      }
#endif      
      if (context && free_context)
        gst_context_unref (context);
      break;
    }
    default:
      break;
  }

  return FALSE;
}

static void
display_run() {
	DisplayData* data = g_new0(DisplayData, 1);
	//Create Main object
	data->context = g_main_context_new();
	g_main_context_push_thread_default(data->context);
	data->loop = g_main_loop_new(data->context,FALSE);
	data->pipeline = gst_pipeline_new("display");
	data->bus = gst_element_get_bus (data->pipeline);
	gst_bus_enable_sync_message_emission (data->bus);
	g_signal_connect (data->bus, "sync-message", G_CALLBACK (sync_bus_call), data);

	GSource* busSource = gst_bus_create_watch(data->bus);
	g_source_attach(busSource, data->context);
	g_source_set_callback (busSource, (GSourceFunc) display_bus, data, NULL);
	gst_object_unref(data->bus);

	display_create_pipeline(data);

	gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
	g_main_loop_run(data->loop);
	g_main_context_dispatch(data->context);
	gst_element_set_state(data->pipeline, GST_STATE_NULL);
	gst_object_unref(data->pipeline);
	gst_context_unref (data->x11_context);

	g_main_loop_unref(data->loop);
	g_main_context_pop_thread_default(data->context);
	g_main_context_unref(data->context);

	g_source_destroy(busSource);
	g_source_unref(busSource);

	g_free(data);
}

static gpointer
run_display(gpointer user_data) {

	g_mutex_lock(&gMutex);
	while ((displayCount != 0)) {
		displayCount--;
		g_mutex_unlock(&gMutex);
		display_run();
		g_mutex_lock(&gMutex);
	}
	g_mutex_unlock(&gMutex);

	return NULL;
}

static void
queue_clean(gpointer user_data) {
	GThread* th = (GThread*) user_data;
	g_thread_join(th);
}

int main(int argc, char* argv[]) {
	gst_init(NULL, NULL);

	XInitThreads();

	fileLocation = g_strdup("/home/matt/Projects/gstreamer/gst-plugins-gl/tests/724806/h264_1080p_hp_4.1_10mbps_dts_unstyled_subs_monsters.mkv");
	GQueue* queue = g_queue_new();
	g_mutex_init(&gMutex);

	gint i=0;
	for (i=0; i< simultaneousDisplay; i++) {
		GThread* thread = g_thread_new("controlTh", run_display, NULL);
		g_queue_push_tail(queue, thread);
	}

	g_queue_free_full(queue, queue_clean);


	gst_deinit();
	g_free(fileLocation);
	g_mutex_clear(&gMutex);

	return 0;
}
