#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#if 0
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
#endif

#include <gst/gst.h>
#include <gst/video/video-info.h>
#include <gst/app/gstappsink.h>

GstElement *pipeline, *src, *rtpfilter, *depay, *decoder, *converter, *scaler, *sizefilter, *sink;
GstPad *rawVideoPad = NULL;
GMutex sample_mutex;
GstSample *sample = NULL;
int width = 320;
int height = 240;
guint major, minor,micro, nano;

Display *display;
Window win = 0;
GC gc;
int depth;



void showFrame(GstSample *sample)
{
    GstBuffer *buffer;
    GstCaps *caps;
    GstVideoFrame frame;
    GstVideoInfo vinfo;
    char *source_data;
    int stride;

	XImage *ximage;

    buffer = gst_sample_get_buffer(sample);
    caps = gst_sample_get_caps(sample);
    if(!gst_video_info_from_caps(&vinfo, caps)) {
        return;
    }
    if(gst_video_frame_map(&frame, &vinfo, buffer, GST_MAP_READ)) {         /* Map the video frame for copying */
        source_data = (char *)frame.data[0];
        if(minor == 8) {
            stride = vinfo.width + ((vinfo.width % 64) ? 64 - (vinfo.width % 64) : 0);
        } else {
            stride = vinfo.width + ((vinfo.width % 128) ? 128 - (vinfo.width % 128) : 0);
        }

		ximage = XCreateImage(display, CopyFromParent, depth, ZPixmap, 0, (char *)source_data, vinfo.width, vinfo.height, 32, stride * 4);
		XPutImage(display, win, gc, ximage, 0, 0, 0, 0, vinfo.width, vinfo.height);
		XFree(ximage);

        gst_video_frame_unmap(&frame);
    }
}

GstPadProbeReturn resizeCallback(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
{
    GstCaps *filter_caps;

    g_object_set(G_OBJECT(converter), "width", width, NULL);
    g_object_set(G_OBJECT(converter), "height", height, NULL);
    filter_caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "BGRx",
                                                     "width", G_TYPE_INT, width,
                                                     "height", G_TYPE_INT, height, NULL);
    g_object_set(G_OBJECT(sizefilter), "caps", filter_caps, NULL);
    gst_caps_unref(filter_caps);

    return GST_PAD_PROBE_REMOVE;
}

static void event_loop(GstElement *bin, Display *display)
{
    GstBus *bus;
    gboolean done = FALSE;
    GstMessage *message;
    gchar *gst_dbg_info = NULL;
    GError *gst_err = NULL;
    XEvent event;
    gint num_events, i;
    int dir = 1;
    int loop = 0;

    bus = gst_pipeline_get_bus(GST_PIPELINE(bin));

    while(!done) {
        message = gst_bus_timed_pop(bus, 16 * GST_MSECOND);
        if(message == NULL) {
			num_events = XEventsQueued(display, QueuedAfterFlush);
			if(num_events) {
				for(i = 0; i < num_events; i++) {
					XNextEvent(display, &event);
					if(event.type == KeyPress) {
						KeySym key = XLookupKeysym(&event.xkey, 0);
						if(key == XK_Escape) {
                            done = TRUE;
						}
						if(key == XK_Page_Up) {
                            if(width + 32 < 1024) {
                                width += 32;
                                height += 24;
                                gst_pad_add_probe(rawVideoPad, GST_PAD_PROBE_TYPE_IDLE, resizeCallback, NULL, NULL);
                            }
						}
						if(key == XK_Page_Down) {
                            if(width - 32 > 0) {
                                width -= 32;
                                height -= 24;
                                gst_pad_add_probe(rawVideoPad, GST_PAD_PROBE_TYPE_IDLE, resizeCallback, NULL, NULL);
                            }
						}
					}
				}
			}
            g_mutex_lock(&sample_mutex);
            if(sample) {
                showFrame(sample);
            }
            g_mutex_unlock(&sample_mutex);
            loop++;
            if((loop & 0x03) == 0) {
                if(dir) {
                    if(width + 32 < 1024) {
                        width += 32;
                        height += 24;
                        gst_pad_add_probe(rawVideoPad, GST_PAD_PROBE_TYPE_IDLE, resizeCallback, NULL, NULL);
                    } else {
                        dir = 0;
                    }
                } else {
                    if(width - 32 > 0) {
                        width -= 32;
                        height -= 24;
                        gst_pad_add_probe(rawVideoPad, GST_PAD_PROBE_TYPE_IDLE, resizeCallback, NULL, NULL);
                    } else {
                        dir = 1;
                    }
                }
            }
        } else {
            switch(GST_MESSAGE_TYPE(message)) {
                case GST_MESSAGE_ERROR:
                    g_print("Error occured\n");
                    g_print("%s :\n", GST_MESSAGE_SRC_NAME(message));
                    gst_message_parse_error(message, &gst_err, &gst_dbg_info);
                    if(gst_dbg_info) {
                        g_print("%s\n", gst_dbg_info);
                    }
                    if(gst_err) {
                        g_error_free(gst_err);
                    }
                    if(gst_dbg_info) {
                        g_free(gst_dbg_info);
                    }
                    break;
                case GST_MESSAGE_EOS:
                    g_print("End Of Stream\n");
                    done = TRUE;
                    break;
                default:
                    break;
            }
            gst_message_unref(message);
        }
    }
    gst_object_unref(bus);
}


GstFlowReturn NewBuffer(GstAppSink *sink, gpointer user_data)
{

    g_mutex_lock(&sample_mutex);
    if(sample) {
        gst_sample_unref(sample);
    }
    sample = gst_app_sink_pull_sample(GST_APP_SINK(sink));
/* Output direkt oder thread */

    g_mutex_unlock(&sample_mutex);

    return GST_FLOW_OK;
}

void PadAddedCallback(GstElement *element, GstPad *pad, GstElement *nextelement)
{
    GstCaps *caps;
    GstStructure *s;
    const gchar *media_type;
    gchar *el1, *el2;

    el1 = gst_element_get_name(element);
    el2 = gst_element_get_name(nextelement);
    caps = gst_pad_query_caps(pad, NULL);
    s = gst_caps_get_structure(caps, 0);
    media_type = gst_structure_get_name(s);

    if(g_str_has_prefix(media_type, "video/") ||
       g_str_has_prefix(media_type, "application/") ||
       g_str_has_prefix(media_type, "image/")) {
        rawVideoPad = pad;
        gst_object_ref(rawVideoPad);
        if(gst_element_link(element, nextelement) == FALSE) {
            fprintf(stderr, "PadAddedCallback : Element linking failed (%s) %s ! %s\n", media_type, el1, el2);
            fflush(stderr);
        } else {
            fprintf(stderr, "Linking (%s) %s ! %s\n", media_type, el1, el2);
            fflush(stderr);
        }
    } else {
        fprintf(stderr, "PadAddedCallback : Unsupported media type %s\n", media_type);
        fflush(stderr);
    }
    gst_caps_unref(caps);
    g_free(el1);
    g_free(el2);

    return;
}

int create_pipeline()
{
    GstCaps *caps;
    GstAppSinkCallbacks callbacks = {NULL, NULL, NewBuffer};

    pipeline = gst_pipeline_new("pipeline");

    src = gst_element_factory_make("udpsrc", "udpsrc");
    gst_bin_add(GST_BIN(pipeline), src);
    g_object_set(G_OBJECT(src), "address", "localhost", NULL);
    rtpfilter = gst_element_factory_make("capsfilter", "rtpcapsfilter");
    gst_bin_add(GST_BIN(pipeline), rtpfilter);
    caps = gst_caps_new_simple("application/x-rtp", "encodeing-name", G_TYPE_STRING, "H264", NULL);
    g_object_set(G_OBJECT(rtpfilter), "caps",caps, NULL);
    gst_caps_unref(caps);
    depay = gst_element_factory_make("rtph264depay", "rtph264depay");
    gst_bin_add(GST_BIN(pipeline), depay);

    decoder = gst_element_factory_make("decodebin", "decodebin");
    gst_bin_add(GST_BIN(pipeline), decoder);

    converter = gst_element_factory_make("vaapipostproc", "vaapipostproc");
    g_object_set(G_OBJECT(converter), "width", width, NULL);
    g_object_set(G_OBJECT(converter), "height", height, NULL);
    gst_bin_add(GST_BIN(pipeline), converter);

    sizefilter = gst_element_factory_make("capsfilter", "capsfilter");
    gst_bin_add(GST_BIN(pipeline), sizefilter);
    caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "BGRx",
                                              "width", G_TYPE_INT, width,
                                              "height", G_TYPE_INT, height, NULL);
    g_object_set(G_OBJECT(sizefilter), "caps",caps, NULL);
    gst_caps_unref(caps);

    sink = gst_element_factory_make("appsink", "appsink");
    gst_bin_add(GST_BIN(pipeline), sink);
    g_object_set(G_OBJECT(sink), "max-lateness", 2000000000, NULL);
    gst_app_sink_set_max_buffers(GST_APP_SINK(sink), 10);
    gst_app_sink_set_drop(GST_APP_SINK(sink), TRUE);
    gst_app_sink_set_callbacks(GST_APP_SINK(sink), &callbacks, NULL, NULL);

    g_signal_connect(decoder, "pad-added", (GCallback)PadAddedCallback, converter);
    gst_element_link_many(src, rtpfilter, depay, decoder, NULL);
    gst_element_link_many(converter, sizefilter, sink, NULL);

    return 0;
}

void usage(char *name)
{
    fprintf(stderr, "Usage : %s\n", name);
    fprintf(stderr, "Change the size with PageUp and PageDown.\n");
    fprintf(stderr, "\n");
}

int main(int argc, char **argv)
{

	int x_size, y_size;
	int screen_num;
    pid_t pid;

    gst_init(&argc, &argv);

    gst_version (&major, &minor, &micro, &nano);
    printf("Using GStreamer %d.%d.%d\n", major, minor, micro);
    g_mutex_init(&sample_mutex);

/*
 * Create X11 window
 */
	x_size = 1024;
	y_size = 768;

	if((display = XOpenDisplay(NULL)) == NULL)
	{
		fprintf(stderr, "Couldn't connect to X11 server\n");
		exit(1);
	}
	screen_num = DefaultScreen(display);
	win = XCreateSimpleWindow(display, RootWindow(display, screen_num), 0, 0, x_size, y_size, 0, BlackPixel(display, screen_num), WhitePixel(display, screen_num));
	gc = XCreateGC(display, win, 0, 0);
	depth = DefaultDepth(display, screen_num);
	XSelectInput(display, win, StructureNotifyMask | KeyPressMask);
	XMapRaised(display, win);
	XFlush(display);
/*
 * Create source stream
 */
    switch(pid = fork()) {
        case 0:
            if(minor == 6) {
                execl("/usr/bin/gst-launch-1.0", "gst-launch-1.0", "videotestsrc", "!", "video/x-raw,width=1920,height=1080", "!", "vaapiencode_h264", "!", "h264parse", "config-interval=2", "!", "rtph264pay", "!", "udpsink", NULL);
            } else {
                execl("/usr/bin/gst-launch-1.0", "gst-launch-1.0", "videotestsrc", "!", "video/x-raw,width=1920,height=1080", "!", "vaapih264enc", "!", "h264parse", "config-interval=2", "!", "rtph264pay", "!", "udpsink", NULL);
            }
            break;
        case -1:
            fprintf(stderr, "Fork failed\n");
            exit(1);
        default:
            break;
    }
    create_pipeline();

    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    event_loop(pipeline, display);

    gst_element_set_state(pipeline, GST_STATE_NULL);

    if(rawVideoPad) {
        gst_object_unref(rawVideoPad);
    }

    gst_object_unref(pipeline);

    kill(pid, SIGINT);
	XFreeGC(display, gc);
	XUnmapWindow(display, win);
	XDestroyWindow(display, win);

	return 0;
}

