#include <unistd.h>
#include <iostream>
#include <stdlib.h>

#include <gst/gst.h>
#include <gst/gstutils.h>
#include <gst/app/gstappsink.h>

// Gstreamer Objects
static GstBus     * m_pBus;
static GstElement * m_pPipeline;
static GstElement * m_pSource;
static GstElement * m_pDecodeBin;
static GstElement * m_pVideoSink;
static GstElement * m_pVideoSink2;
static GstBuffer  * m_pBuffer = NULL;
static gint64 m_nSeekPos = 0;
gint64 streamDuration = 0;
static int m_nNoPass = 0;


static void cbNewDecodedPad(  GstElement  *bin,
                              GstPad      *pad,
                              gboolean    islast,
                              gpointer    user_data )
{
  GstPad *sinkpad;
  GstCaps *caps;
  GstStructure *str;

  caps = gst_pad_get_caps (pad);
  str = gst_caps_get_structure (caps, 0);

  if (g_strrstr (gst_structure_get_name (str), "video"))
  {
    sinkpad = gst_element_get_pad (m_pVideoSink, "sink");
    gst_caps_unref (caps);
    gst_pad_link (pad, sinkpad);
    gst_object_unref (sinkpad);
  }
}


static void interval_seek_timeline( GstElement *pipeline,
                                    gint64      time_seconds )
{
  gint64 seek_pos = time_seconds*1000000000;

  while(true)
  {
    // wait until current buffer is decoded
    gst_element_get_state (m_pPipeline, NULL, NULL, GST_CLOCK_TIME_NONE);

    if( !gst_element_seek(  pipeline,
                            1.0f,
                            GST_FORMAT_TIME,
                            (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
                            GST_SEEK_TYPE_SET,
                            seek_pos,
                            GST_SEEK_TYPE_NONE,
                            GST_CLOCK_TIME_NONE ) )
    {
      printf("Seek failed!\n");
      usleep(10000);
      continue;
    }
    else
    {
      //printf("Seek succeeded!\n");
    }
    seek_pos += time_seconds*1000000000;

    if(streamDuration != 0 && streamDuration <= seek_pos)
      break;

    //usleep(100000);
  }
}


static gboolean cbBusMsg (GstBus * bus, GstMessage * msg, gpointer data)
{
  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:
      g_print ("cbBusMsg> end of stream\n");
      break;
    default:
      break;
  }

  return TRUE;
}


void cbNewPreroll(GstAppSink *appsink,
                  gpointer    user_data)
{
  GstBuffer *buffer = gst_app_sink_pull_preroll(appsink);
  g_print("cbNewPreroll> caps: %s\n", gst_caps_to_string(buffer->caps));
}

int main( int argc, char *argv[] )
{
  // init GStreamer
  gst_init (&argc, &argv);

  GMainLoop * pLoopPass1 = g_main_loop_new (NULL, FALSE);

  // make sure we have input
  if (argc != 2)
  {
    g_print ("Usage: %s <filename>\n", argv[0]);
    return -1;
  }

  // Create the elements
  m_pPipeline   = gst_pipeline_new (NULL);
  m_pSource     = gst_element_factory_make ("filesrc", "filesrc");
  m_pDecodeBin  = gst_element_factory_make ("decodebin", "decodebin");
  m_pVideoSink  = gst_element_factory_make ("appsink", "videosink");

  if (!m_pPipeline || !m_pSource || !m_pDecodeBin || !m_pVideoSink)
  {
    g_print ("Unable to create complete pipeline. Exiting.\n");
    return -1;
  }

  // setup
  g_object_set( G_OBJECT(m_pSource), "location",  argv[1], NULL);

  // we add a message handler
  m_pBus = gst_pipeline_get_bus (GST_PIPELINE (m_pPipeline));
  gst_bus_add_watch (m_pBus, cbBusMsg, NULL);
  gst_object_unref (m_pBus);

  // add items to bin
  gst_bin_add_many( GST_BIN(m_pPipeline),
                    m_pSource,
                    m_pDecodeBin,
                    m_pVideoSink, NULL );

  // setup callbacks
  g_signal_connect( m_pDecodeBin,
                    "new-decoded-pad",
                    G_CALLBACK(cbNewDecodedPad), NULL);

  // setup sink
  g_object_set( m_pVideoSink, "emit-signals", TRUE, NULL);
  g_object_set( m_pVideoSink, "max-buffers", 5, NULL);

  g_signal_connect( m_pVideoSink, "new-preroll", G_CALLBACK(cbNewPreroll), NULL);

  //link items within bin
  gst_element_link (m_pSource, m_pDecodeBin);

  // run
  gst_element_set_state (m_pPipeline, GST_STATE_PAUSED);
  gst_element_get_state (m_pPipeline, NULL, NULL, GST_CLOCK_TIME_NONE);

  // get stream duration
  GstFormat format   = GST_FORMAT_TIME;
  if(gst_element_query_duration(m_pPipeline, &format, &streamDuration))
  {
    int seconds = streamDuration / 1000000000;
    int minutes = seconds / 60;
    int hours   = minutes / 60;

    seconds = seconds % 60;
    minutes = minutes % 60;

    g_print("main> duration: %.2d:%.2d:%.2d\n", hours, minutes, seconds);
  }
  else
  {
    g_print("main> duration query could NOT be performed.\n");
  }

  // go
  interval_seek_timeline(m_pPipeline,30);

  // cleanup
  gst_element_set_state (m_pPipeline, GST_STATE_NULL);
  gst_object_unref (GST_OBJECT (m_pPipeline));

  return 0;
}
