#include <stdlib.h>
#include <gst/gst.h>

gboolean bus_call(GstBus *bus, GstMessage *msg, void *data)
{
    gchar           *debug;
    GError          *err;
    GMainLoop       *loop = (GMainLoop*)data;

    switch (GST_MESSAGE_TYPE(msg))
    {
        case GST_MESSAGE_APPLICATION:
            g_print("APP received on OBJ NAME %s\n",GST_OBJECT_NAME(msg->src));
            break;
        case GST_MESSAGE_EOS:
            g_print("EOS received on OBJ NAME %s\n",GST_OBJECT_NAME(msg->src));
            g_main_loop_quit (loop);
            break;
        case GST_MESSAGE_ERROR:
            gst_message_parse_error(msg, &err, &debug);
            g_free(debug);
            g_print("BUS CALL %s\n", err->message);
            g_error_free(err);
            g_main_loop_quit (loop);
            break;
        default:
            break;
    }

    return TRUE;
}

gboolean custom_event_probe (GstPad * pad, GstEvent * event, gpointer data)
{
    gboolean ret = TRUE;
    switch (GST_EVENT_TYPE (event)) 
    {
        case GST_EVENT_CUSTOM_DOWNSTREAM:
            g_print("Requested Encoding End: %d\n",__LINE__);
            break;
        default:
            break;
    }
  
    return ret;
}

/**
 * stop_encoding:
 * @data: main pipeline
 *
 * This is a glib function that gets called every 2 seconds, once called 3 times it will issue an EOS to the bin's sink pad
 *
 * Returns: FALSE to stop the event loop calling again, TRUE otherwise
 */
gboolean stop_encoding(gpointer *data)
{
    static int called = 0;
    called++;
    g_print("CALLED!! %d times\n",called);
    if(called == 2)
    {
        GstElement *pipeline = (GstElement*)data;
        GstElement *rec      = gst_bin_get_by_name(GST_BIN(pipeline),"viewing-file-source");
        GstEvent *event      = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,gst_structure_new("END-ENCODING",NULL));
        if(!event)
            g_print("EVENT CREATION FAILED\n");
            
        if(gst_element_send_event(pipeline,event))
            g_print("SENT EVENT SUCCESSFULLY\n");
        else
            g_print("CUSTOM EVENT FAILED TO SEND\n");

        if(gst_element_send_event(rec,event))
            g_print("SENT EVENT SUCCESSFULLY\n");
        else
            g_print("CUSTOM EVENT FAILED TO SEND\n");
    }
    
    return (called == 2)?FALSE:TRUE;
}

int main(int argc, char* argv[])
{
    GMainLoop *loop;
    gst_init(&argc,&argv);
    
    loop = g_main_loop_new (NULL, FALSE);
    
    GstElement  *pipeline, *vsource, *vtee, *vqueue, *tover, *xvsink, *evqueue, *vencoder, *muxer, *filesink;

    GstBin      *recording;
    GstBus      *bus;
    GstPad      *srcpad,*sinkpad;

  // Create gstreamer elements
    pipeline   = gst_pipeline_new ("eos-test-player");
    vsource    = gst_element_factory_make ("videotestsrc",     "viewing-file-source");
    vtee       = gst_element_factory_make ("tee",              "viewing-tee");
    vqueue     = gst_element_factory_make ("queue2",           "viewing-queue");
    tover      = gst_element_factory_make ("timeoverlay",      "viewing-overlay");
    xvsink     = gst_element_factory_make ("xvimagesink",      "viewing-xvsink");

#ifdef INCLUDE_AUDIO
    GstElement  *asource, *atee, *aqueue, *aequeue, *aencoder, *asink;
    
    asource    = gst_element_factory_make ("audiotestsrc",     "viewing-audio-source");
    atee       = gst_element_factory_make ("tee",              "viewing-audio-tee");
    aqueue     = gst_element_factory_make ("queue2",           "viewing-audio-queue");
    asink      = gst_element_factory_make ("pulsesink",        "viewing-audio-sink");
    aequeue    = gst_element_factory_make ("queue2",           "encoding-audio-queue");
    aencoder   = gst_element_factory_make ("lamemp3enc",       "encoding-audio-encoder");
#endif

    recording  = GST_BIN(gst_bin_new("recording-bin"));
    evqueue    = gst_element_factory_make ("queue2",           "encoding-queue");
    vencoder   = gst_element_factory_make ("ffenc_mpeg4",      "encoding-encoder");
    muxer      = gst_element_factory_make ("mp4mux",           "encoding-muxer");
    filesink   = gst_element_factory_make ("filesink",         "encoding-filesink");

    if(!pipeline || !vsource || !xvsink || !tover )
    {
        g_print("Unable to create all necessary elements\n");
        return -1;
    }

    bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
    gst_bus_add_watch (bus, bus_call, loop);
    gst_object_unref (bus);

    g_object_set (G_OBJECT(vsource),  "num-buffers",       300,               NULL);
    g_object_set (G_OBJECT(filesink), "location",          "/tmp/output.mp4", NULL);    
    g_object_set (G_OBJECT (tover),   "halign",            "right",           NULL);
    g_object_set (G_OBJECT (tover),   "valign",            "top",             NULL);
    g_object_set (G_OBJECT (tover),   "shaded-background", TRUE,              NULL);

  /* create the recording bin */

#ifdef INCLUDE_AUDIO
    gst_bin_add_many (recording, aequeue, aencoder, evqueue, vencoder, muxer, filesink, NULL);
#else
    gst_bin_add_many (recording, evqueue, vencoder, muxer, filesink, NULL);
#endif

    sinkpad       = gst_element_get_static_pad(evqueue,"sink");
    GstPad *ghost = gst_ghost_pad_new("vsink",sinkpad);

    if(ghost == NULL)
        g_error("Unable to create ghostpad!\n");

    gst_element_add_pad(GST_ELEMENT(recording),ghost);
    gst_object_unref(GST_OBJECT(sinkpad));

    gst_element_link_many(evqueue,vencoder,muxer,filesink,NULL);

#ifdef INCLUDE_AUDIO
    sinkpad = gst_element_get_static_pad(aequeue,"sink");
    gst_element_add_pad(GST_ELEMENT(recording),gst_ghost_pad_new("asink",sinkpad));
    gst_object_unref(GST_OBJECT(sinkpad));

    gst_element_link_many(aequeue,aencoder,muxer,NULL);
#endif

  /* we add all elements into the pipeline */
#ifdef INCLUDE_AUDIO
    gst_bin_add_many (GST_BIN (pipeline), asource, atee, aqueue, asink, vsource, vtee, vqueue, tover, xvsink, recording, NULL);
#else
    gst_bin_add_many (GST_BIN (pipeline), vsource, vtee, vqueue, tover, xvsink, recording, NULL);
#endif

    /* link video elements */
    gst_element_link_many(vsource,tover,vtee,NULL);
    
    srcpad  = gst_element_get_request_pad(vtee,"src0");
    sinkpad = gst_element_get_pad(vqueue,"sink");

    gst_pad_link(srcpad,sinkpad);
    
    gst_element_link(vqueue,xvsink);

    /* link the viewing pipeline into the bin */
    srcpad  = gst_element_get_request_pad(vtee,"src1");
    sinkpad = gst_element_get_pad(GST_ELEMENT(recording),"vsink");
    gst_pad_link(srcpad,sinkpad);

    /* Custom Event Probe */
    gst_pad_add_event_probe (srcpad, G_CALLBACK (custom_event_probe), pipeline);

#ifdef INCLUDE_AUDIO
    /* link audio elements */
    gst_element_link_many(asource,atee,NULL);
    
    srcpad  = gst_element_get_request_pad(atee,"src0");
    sinkpad = gst_element_get_pad(aqueue,"sink");

    gst_pad_link(srcpad,sinkpad);
    
    gst_element_link(aqueue,asink);

    /* link the viewing pipeline into the bin */
    srcpad  = gst_element_get_request_pad(atee,"src1");
    sinkpad = gst_element_get_pad(GST_ELEMENT(recording),"asink");
    gst_pad_link(srcpad,sinkpad);
    
    /* Custom Event Probe */
    gst_pad_add_event_probe (srcpad, G_CALLBACK (custom_event_probe), pipeline);
    
#endif

    /* Iterate */
    g_print ("Running...\n");

    gst_element_set_state(pipeline,GST_STATE_PLAYING);

    g_timeout_add_seconds(2,(GSourceFunc)stop_encoding,pipeline);

    g_main_loop_run (loop);

    /* Out of the main loop, clean up nicely */
    g_print ("Returned, stopping playback\n");
    gst_element_set_state (pipeline, GST_STATE_NULL);

    g_print ("Deleting pipeline\n");
    gst_object_unref (GST_OBJECT (pipeline));

    return 0;
}
