/* gcc -g -Wall -lm `pkg-config --cflags --libs gtk+-2.0 gstreamer-0.8` -o main main.c */

#include <math.h>

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

#define clip_8_bit(val)              \
{                                    \
  if (G_UNLIKELY (val < 0))          \
    val = 0;                         \
  else if (G_UNLIKELY (val > 255))   \
    val = 255;                       \
}

static void show_frame (guchar  *rgb);

static GstElement *pipeline;
static GstElement *filesrc;
static GstElement *decodebin;
static GstElement *sink;

gdouble framerate = 0.0;
gint    width = 0;
gint    height = 0;

gboolean
do_seek (int frame_number)
{
  GstSeekType type = (GstSeekType)(GST_FORMAT_TIME  |
				   GST_SEEK_METHOD_SET |
				   GST_SEEK_FLAG_FLUSH);
  guint64     time;

  gst_element_set_state (pipeline, GST_STATE_PAUSED);
  g_print ("seeking to second %u, frame num. %d\n",
	   (unsigned int)floor (frame_number / framerate), frame_number);
  time = floor (GST_SECOND * frame_number / framerate);

  if (!gst_element_seek(sink, type, time))
    {
      g_print ("not good...\n");
      return FALSE;
    }
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  return TRUE;
}

void
cb_new_pad (GstElement *decodebin,
	    GstPad     *pad,
	    gboolean    last,
	    gpointer    data)
{
  GstCaps *caps;
  GstStructure *str;
  const gchar *mimetype;

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

  printf ("\nNew Pad : %s\n", gst_structure_to_string (str));

  if (g_str_has_prefix (mimetype, "video/"))
    {
      GstPad       *videopad;
      GstStructure *str;
      
      videopad = gst_element_get_pad (sink, "sink");
      gst_pad_link (pad, videopad);
      gst_bin_sync_children_state (GST_BIN (pipeline));
      
      videopad = gst_element_get_pad (gst_pad_get_real_parent (pad),
				      "sink");
      str = gst_caps_get_structure (GST_PAD_CAPS (videopad), 0);
      if (str)
	{
	  gst_structure_get_int (str, "width", &width);
	  gst_structure_get_int (str, "height", &height);
	  
	  gst_structure_get_double (str, "framerate", &framerate);
	  
	  g_print ("Width......: %d\n", width);
	  g_print ("Height.....: %d\n", height);
	  g_print ("Framerate..: %f\n", framerate);
	}
    }
}  

void
cb_no_more_pad (GstElement *decodebin,
		gpointer    data)
{
  gst_element_set_state (pipeline, GST_STATE_PAUSED);
}

void
cb_handoff(GObject * obj, GstBuffer *buffer, GstPad * pad, void * data)
{
  gint i, j;
  unsigned char *Y;
  unsigned char *U;
  unsigned char *V;
  gint offset_Y, offset_U;
  guchar  *rgb;

  g_print ("handoff\n");

  if (GST_BUFFER_TIMESTAMP (buffer) / GST_SECOND < 7)
  {
    g_print ("skipping frame with timestamp %.3fs\n",
	     (gdouble) GST_BUFFER_TIMESTAMP (buffer) / GST_SECOND);
    return;
  }

  g_print ("====== handoff: got frame =====\n");


  g_print ("frame timestamp = %.3fs\n",
	   (gdouble) GST_BUFFER_TIMESTAMP (buffer) / GST_SECOND);
  g_print ("frame offset    = %" G_GINT64_FORMAT "\n",
	   (gint64) GST_BUFFER_OFFSET (buffer));

  rgb = g_malloc0 (width * height * 3);

  
  /* the video is in I420 */
  Y = buffer->data;
  U = Y + width * height;
  V = U + ((width * height) >> 2);

  for (j = 0 ; j < height ; j++)
    for (i = 0 ; i < width ; i++)
      {
	gint  r, g, b;
	
	offset_Y = j*width + i;
	offset_U = ((j*width) >> 2) + (i >> 1);

	r = 1.164 * (Y[offset_Y] - 16) + 1.596 * (V[offset_U] - 128);
	g = 1.164 * (Y[offset_Y] - 16) - 0.813 * (V[offset_U] - 128) - 0.391 * (U[offset_U] - 128);
	b = 1.164 * (Y[offset_Y] - 16) + 2.018 * (U[offset_U] - 128);
	
	clip_8_bit (r);
	clip_8_bit (g);
	clip_8_bit (b);
	
	rgb[(j * width + i) * 3 + 0] = r;
	rgb[(j * width + i) * 3 + 1] = g;
	rgb[(j * width + i) * 3 + 2] = b;
      }

  gst_element_set_state (pipeline, GST_STATE_PAUSED);

  
  /*
    We display the image
   */
  show_frame (rgb);
}

int
main (int argc, char *argv[])
{
  char *filename;
  gint frame_nbr;
  gint i;
  gint id_new;
  gint id_nomore;

  if (argc < 3)
    {
      g_print ("Usage   : main /path/to/file frame_number\n");
      g_print ("Example : main /mnt/win_d/provi/blood.avi 1000\n");
      return 0;
    }

  gst_init (&argc, &argv);

  filename = argv[1];
  frame_nbr = atoi (argv[2]);

  pipeline = gst_pipeline_new ("pipeline");

  filesrc = gst_element_factory_make ("filesrc", NULL);
  g_object_set (G_OBJECT (filesrc), "location", filename, NULL);

  decodebin = gst_element_factory_make ("decodebin", NULL);

  sink = gst_element_factory_make ("fakesink", NULL);
  g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
  g_signal_connect (G_OBJECT (sink),
		    "handoff",
		    G_CALLBACK (cb_handoff), NULL);

  gst_bin_add_many (GST_BIN (pipeline), filesrc, decodebin, sink, NULL);

  gst_element_link (filesrc, decodebin);

  id_new = g_signal_connect (G_OBJECT (decodebin),
			     "new-decoded-pad",
			     G_CALLBACK (cb_new_pad), NULL);
  id_nomore = g_signal_connect (G_OBJECT (decodebin),
				"no-more-pads",
				G_CALLBACK (cb_no_more_pad), NULL);
  
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  i = 0;
  while (gst_bin_iterate (GST_BIN (pipeline)) && (i<=40))
    {
      g_print ("I: %d\n", i);
      i++;
    }

  if (g_signal_handler_is_connected (G_OBJECT (decodebin), id_new))
    g_signal_handler_disconnect (G_OBJECT (decodebin), id_new);
  if (g_signal_handler_is_connected (G_OBJECT (decodebin), id_nomore))
    g_signal_handler_disconnect (G_OBJECT (decodebin), id_nomore);
  
  /* seek. Provide the frame number */
  if (!do_seek (frame_nbr))
    {
      g_print ("bad seek, exiting...\n");
      exit (0);
    }

  
  g_print ("beginning of handoff\n");
  gst_element_set_state (pipeline, GST_STATE_PLAYING);
  i = 0;
  while (gst_bin_iterate (GST_BIN (pipeline)))
    {
      g_print ("I: %d\n", i);
      i++;
    }

  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (GST_OBJECT (pipeline));

  return 1;
}


static void
show_frame (guchar  *rgb)
{
    GtkWidget *win, *img;
    GdkPixbuf *pix;

    gtk_init (NULL, NULL);

    win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    img = gtk_image_new ();
    gtk_container_add (GTK_CONTAINER (win), img);
    g_signal_connect (win, "delete-event", gtk_main_quit, NULL);

    pix = gdk_pixbuf_new_from_data (rgb,
				    GDK_COLORSPACE_RGB, FALSE,
				    8, width, height, 3 * width,
				    (GdkPixbufDestroyNotify)g_free,
				       rgb);
    gtk_image_set_from_pixbuf (GTK_IMAGE (img), pix);
    g_object_unref (pix);

    gtk_widget_show_all (win);

    gtk_main ();
}
