#include <string.h>
#include <stdio.h>
   
#include <gtk/gtk.h>
#include <gst/gst.h>
/*#include <gst/interfaces/xoverlay.h>*/
   
#include <gdk/gdk.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#elif defined (GDK_WINDOWING_QUARTZ)
#include <gdk/gdkquartz.h>
#endif


/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstElement *playbin2;           /* Our one and only pipeline */
   
  GstState state;                 /* Current state of the pipeline */
  gint64 duration;                /* Duration of the clip, in nanoseconds */ 
  
  gint32 cnt;
  
  GstElement *main; 		/* pipeline */
  
    GstElement *src; 		 /*("uvch264src", "src") */
    
    GstElement *prevQueue; 	/*("queue", "prevQueue") */  
    GstElement *vfcaps; 	/*("capsfilter", "vfcaps") */
    GstElement *preview_sink; 	/*( "autovideosink", "previewsink") */
    
    GstElement *vidQueue; 	 /*( "queue", "vidQueue") */  
    GstElement *vidcaps; 	 /*("capsfilter", "vidcaps") */
    GstElement *t; 		 /*( "tee", "t") */
    
    GstElement *srcQueue; 	 /*( "queue", "srcQueue") */       
    GstElement *recBin1; 	 /*"recoding bin 1") */
    GstElement *fileQueue1; 	 /*( "queue", "fileQueue1") */
    GstElement *ph264_1; 	 /* ("h264parse", "ph264_1") */
    GstElement *mux_1; 		 /*("mp4mux", "mux1") */
    GstElement *filesink1; 	 /*( "filesink", "filesink1") */
	
    GstElement *recBin2; 	 /*("recoding bin 2") */
    GstElement *fileQueue2; 	 /*( "queue", "fileQueue2") */
    GstElement *ph264_2; 	 /* ("h264parse", "ph264_2") */
    GstElement *mux_2; 		 /*("mp4mux", "mux2") */
    GstElement *filesink2; 	 /*( "filesink", "filesink2") */
	
	
} CustomData;

static void createElements(CustomData *data){
    data-> cnt = 0;
    
    data->main = gst_pipeline_new("main");
    data->src = gst_element_factory_make("uvch264src", "src");
    
    data->prevQueue = gst_element_factory_make("queue", "prevQueue");  
    data->vfcaps = gst_element_factory_make("capsfilter", "vfcaps");
    data->preview_sink = gst_element_factory_make( "autovideosink", "previewsink");
    
    data->vidQueue = gst_element_factory_make( "queue", "vidQueue");  
    data->vidcaps = gst_element_factory_make("capsfilter", "vidcaps");
    data->t = gst_element_factory_make( "tee", "t");
    
    data->srcQueue = gst_element_factory_make( "queue", "srcQueue");       
    data->recBin1 = gst_bin_new("recoding bin 1");
    data->fileQueue1 = gst_element_factory_make( "queue", "fileQueue1");
    data->ph264_1 = gst_element_factory_make ("h264parse", "ph264_1");
    data->mux_1 = gst_element_factory_make("mp4mux", "mux1");
    data->filesink1 = gst_element_factory_make( "filesink", "filesink1");
	
    data->recBin2 = gst_bin_new("recoding bin 2");
    data->fileQueue2 = gst_element_factory_make( "queue", "fileQueue2");
    data->ph264_2 = gst_element_factory_make ("h264parse", "ph264_2");
    data->mux_2 = gst_element_factory_make("mp4mux", "mux2");
    data->filesink2 = gst_element_factory_make( "filesink", "filesink2");
	
}

static void linkMainPipeline(CustomData *data){   
    GstPad *srcP2;
    GstPad *tP2;
    
    GstPad *srcP;
    GstPad *tP;
    
    gst_bin_add(GST_BIN (data->main), data->src);
    
    gst_bin_add(GST_BIN (data->main), data->prevQueue);  
    gst_bin_add(GST_BIN (data->main), data->vfcaps);
    gst_bin_add(GST_BIN (data->main), data->preview_sink);
    
    gst_bin_add(GST_BIN (data->main), data->vidQueue);  
    gst_bin_add(GST_BIN (data->main), data->vidcaps);
    gst_bin_add(GST_BIN (data->main), data->t);
    
    gst_bin_add(GST_BIN (data->main), data->srcQueue);


    /* self.log.debug("link self.elements in main pipeline"); */
    /* self.log.debug("1. linking preview branch..."); */
    srcP2 = gst_element_get_static_pad(data->src,"vfsrc");
    tP2 = gst_element_get_static_pad(data->prevQueue, "sink");
    gst_pad_link(srcP2, tP2);
    gst_object_unref (GST_OBJECT (srcP2));
    gst_object_unref (GST_OBJECT (tP2));
    
    gst_element_link(data->prevQueue, data->vfcaps);
    gst_element_link(data->vfcaps, data->preview_sink);
    
    
    /* self.log.debug("2. linking H264 branch until tee...") */
    srcP = gst_element_get_static_pad(data->src, "vidsrc");
    tP = gst_element_get_static_pad(data->vidQueue, "sink");
    gst_pad_link(srcP, tP);
    gst_object_unref (GST_OBJECT (srcP));
    gst_object_unref (GST_OBJECT (tP));    
        
    gst_element_link(data->vidQueue, data->vidcaps);
    gst_element_link(data->vidcaps, data->t);    
}

static void linkRecBin1(CustomData *data){
    GstPad *pad;
    
    gst_bin_add(GST_BIN (data->recBin1), data->fileQueue1);
    gst_bin_add(GST_BIN (data->recBin1), data->ph264_1);
    gst_bin_add(GST_BIN (data->recBin1), data->mux_1);
    gst_bin_add(GST_BIN (data->recBin1), data->filesink1);
    
    /* self.log.debug("link elements in recBin1")  */
    gst_element_link(data->fileQueue1, data->ph264_1);
    gst_element_link(data->ph264_1, data->mux_1);
    gst_element_link(data->mux_1, data->filesink1);
    
    /* self.log.debug("create ghost pad for recBin1") */
    
    pad = gst_element_get_static_pad (data->fileQueue1, "sink");
    gst_element_add_pad(data->recBin1, gst_ghost_pad_new("sink",pad));
    gst_object_unref (GST_OBJECT (pad));    
}

static void linkRecBin2(CustomData *data){
    GstPad *pad;
    
    gst_bin_add(GST_BIN (data->recBin2), data->fileQueue2);
    gst_bin_add(GST_BIN (data->recBin2), data->ph264_2);
    gst_bin_add(GST_BIN (data->recBin2), data->mux_2);
    gst_bin_add(GST_BIN (data->recBin2), data->filesink2);
    
    /* self.log.debug("link elements in recBin2")  */
    gst_element_link(data->fileQueue2, data->ph264_2);
    gst_element_link(data->ph264_2, data->mux_2);
    gst_element_link(data->mux_2, data->filesink2);
    
    /* self.log.debug("create ghost pad for recBin2") */
    
    
    pad = gst_element_get_static_pad (data->fileQueue2, "sink");
    gst_element_add_pad(data->recBin2, gst_ghost_pad_new("sink",pad));
    gst_object_unref (GST_OBJECT (pad));    
}

static void linkRecBin1ToMain(CustomData *data){
    GstPad *pad;
    gst_bin_add(GST_BIN (data->main), data->recBin1);          
          
    /* self.log.debug("link srcQueue --> recBin to tee") */
    pad = gst_element_get_request_pad(data->t, "src_%u");
    gst_pad_link(pad, gst_element_get_static_pad(data->srcQueue, "sink"));
    gst_element_link(data->srcQueue, data->recBin1);
    gst_object_unref (GST_OBJECT (pad));    
}

static void updateFilesinkLocation(CustomData *data, GstElement* fs){
    char loc[57];
    sprintf(loc, "/run/media/peter/Elements/peter/data/tmp-20130801/%d.mp4", (int)data->cnt);
    puts(loc);
    g_object_set(G_OBJECT(fs), "location", loc, NULL);
}

static void setProperties(CustomData *data){
     updateFilesinkLocation(data, data->filesink1);
        
    printf("set source propertiess\n");
    g_object_set(G_OBJECT(data->src), "auto-start", 1, NULL);
    g_object_set(G_OBJECT(data->src), "fixed-framerate", 1, NULL);
    g_object_set(G_OBJECT(data->src), "async-handling", 0, NULL);
    g_object_set(G_OBJECT(data->src), "iframe-period", 30, NULL);
//     g_object_set(G_OBJECT(data->src), "num-clock-samples", -1, NULL);
    g_object_set(G_OBJECT(data->src), "device", "/dev/video1", NULL);
}

static void setCaps(CustomData *data){
    GstCaps *caps;
    GstCaps *caps2;
    
    caps = gst_caps_from_string("video/x-h264,width=1920,height=1080,framerate=30/1,profile=constrained-baseline");
    g_object_set(G_OBJECT(data->vidcaps), "caps", caps, NULL);
    caps2 = gst_caps_from_string("video/x-raw,width=320,height=240,framerate=15/1");
    g_object_set(G_OBJECT(data->vfcaps), "caps", caps2, NULL);
    
    gst_object_unref (GST_OBJECT (caps)); 
    gst_object_unref (GST_OBJECT (caps2));    
}


/* swapping functions */
static void resetBin(GstElement *bin){
    gst_element_set_state(GST_ELEMENT(bin), GST_STATE_NULL);
}

static GstPadProbeReturn preparePipeline(GstPad* pad, GstPadProbeInfo *probeInfo, gpointer *userData){    
    CustomData *data;
    GstPad* pC;
    GstElement* binC;
    GstElement* binN;
    GstElement* fs;
    GstElement* mux;
    GstElement* fqN;
    GstElement* fqC;
    
    printf("preparePipeline\n");
    
    data = (CustomData *)userData;
    
    gst_pad_remove_probe(pad, probeInfo->id);
       

    data->cnt += 1;
    pC = gst_pad_get_peer(pad);
    
    binC = gst_pad_get_parent_element(pC);
    
    if (binC == data->recBin1){
        printf("replace recBin1 by recBin2\n") ;
 
        binN = data->recBin2;
        fs = data->filesink2;
        mux = data->mux_2;
        fqN = data->fileQueue2;
        fqC = data->fileQueue1;
        
    }else{
        printf("replace recBin2 by recBin1\n") ;   
	
        binN = data->recBin1;
        fs = data->filesink1;
        mux = data->mux_1;
        fqN = data->fileQueue1;
        fqC = data->fileQueue2;
    }
    
    printf("prepare next recBin\n");
    resetBin(binN);
    
    updateFilesinkLocation(data, fs);
    
    printf("remove current recBin from main and prepare catch\n");
    gst_element_unlink(data->srcQueue, fqC);    
    gst_bin_remove(GST_BIN(data->main), binC);
    
    GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->main),GST_DEBUG_GRAPH_SHOW_ALL, "main-1");
         
    printf("send EOS to current recBin\n");
    gst_element_send_event(binC, gst_event_new_eos ());
    
    printf("prepare next recBin\n");
    gst_element_set_state(GST_ELEMENT(binN), GST_STATE_NULL);
    
    printf("add and link next recBin to main\n");
    gst_bin_add(GST_BIN(data->main), binN);
    gst_element_link(data->srcQueue, binN);
    
//     for pad in mux.pads:
//         if pad.get_name().startswith("video"):
//             pad.push_event(Gst.Event.new_reconfigure())
            
    gst_element_set_state(GST_ELEMENT(binN), GST_STATE_PLAYING);
    
    return GST_PAD_PROBE_OK;
}

static GstPadProbeReturn blockActiveQueuePad(GstPad* pad, GstPadProbeInfo *probeInfo, gpointer userData){
    GstBuffer *buffer;
    GstPad* tp;
    
    CustomData *data = userData;
    
    buffer = GST_PAD_PROBE_INFO_BUFFER (probeInfo);
    if (!GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT)){
	gst_pad_remove_probe(pad, probeInfo->id);
        
        GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(data->main), GST_DEBUG_GRAPH_SHOW_ALL, "main_blockActiveQueuePad");
        tp = gst_element_get_static_pad(data->srcQueue, "src");
	gst_pad_add_probe(tp, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM, (GstPadProbeCallback) preparePipeline, data, NULL);
	
	
	gst_object_unref (GST_OBJECT (tp)); 
        return GST_PAD_PROBE_DROP;
    }else{
	printf("no keyframe yet");
        return GST_PAD_PROBE_OK;
    }
}

static int blockOnNextKeyframe(CustomData *data){                
    GstPad *tp;
    GstEvent* event;
    
    tp = gst_element_get_static_pad(data->srcQueue, "sink");
    event = (GstEvent*) gst_video_event_new_upstream_force_key_unit(GST_CLOCK_TIME_NONE, TRUE, data->cnt);
    gst_element_send_event(data->main, event);  
    gst_pad_add_probe(tp, GST_PAD_PROBE_TYPE_BUFFER, (GstPadProbeCallback) blockActiveQueuePad, data, NULL);
	    
    gst_object_unref (GST_OBJECT (tp)); 
    return 1;
}



/* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->main, GST_STATE_PLAYING);
}

/* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->main, GST_STATE_PAUSED);
}

/* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) {
    blockOnNextKeyframe(data);
}

/* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
  stop_cb (NULL, data);
  gtk_main_quit();
}


static void createUI(CustomData *data){
  GtkWidget *main_window;  /* The uppermost window, containing all other windows */
  GtkWidget *video_window; /* The drawing area where the video will be shown */
  GtkWidget *main_box;     /* VBox to hold main_hbox and the controls */
  GtkWidget *main_hbox;    /* HBox to hold the video_window and the stream info text widget */
  GtkWidget *controls;     /* HBox to hold the buttons and the slider */
  GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
   
  main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
   
  video_window = gtk_drawing_area_new ();
  gtk_widget_set_double_buffered (video_window, FALSE);
   
  play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
  g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);
         
  pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
  g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);
   
  stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
  g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);
   
  controls = gtk_box_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
  gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
  gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
   
  main_hbox = gtk_box_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
   
  main_box = gtk_box_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
  gtk_container_add (GTK_CONTAINER (main_window), main_box);
  gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);
   
  gtk_widget_show_all (main_window);
}

int main(int argc, char *argv[]) {
    CustomData data;
    GstStateChangeReturn ret;
    GstBus *bus;
    
    /* Initialize GTK */
    gtk_init (&argc, &argv);
    
    /* Initialize GStreamer */
    gst_init (&argc, &argv);
    
    /* Initialize our data structure */
    memset (&data, 0, sizeof (data));
    data.duration = GST_CLOCK_TIME_NONE;
    
    /* create and link pipeline */
    printf("createElements\n");
    createElements(&data);
    printf("linkMainPipeline\n");
    linkMainPipeline(&data);
    printf("linkRecBin1\n");
    linkRecBin1(&data);
    printf("linkRecBin2\n");
    linkRecBin2(&data);
    printf("linkRecBin1ToMain\n");
    linkRecBin1ToMain(&data);
    printf("setProperties\n");
    setProperties(&data);
    printf("setCaps\n");
    setCaps(&data);
    
    
    /* set GTK up */   
    printf("create ui\n");
    createUI(&data);
    
    /* Start playing */
    printf("start playing\n");
    ret = gst_element_set_state (data.main, 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.main);
	return -1;
    }
	
    /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
    gtk_main ();
    
    /* Free resources */
    gst_element_set_state (data.main, GST_STATE_NULL);
    gst_object_unref (data.main);
    return 0;
}