/* gcc -g -Wall `pkg-config --cflags --libs gtk+-2.0 gstreamer-0.10` -o gst_vis gst_vis.c */

#include <gst/gst.h>

GstElement *pipeline;
int         audio_stream_nbr = 0;

static gboolean
bus_call (GstBus     *bus,
	  GstMessage *msg,
	  gpointer    data)
{
  GMainLoop *loop = data;

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:
      g_print ("End-of-stream\n");
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_ERROR: {
      gchar *debug;
      GError *err;

      gst_message_parse_error (msg, &err, &debug);
      g_free (debug);

      g_print ("Error: %s\n", err->message);
      g_error_free (err);

      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

GstElement *
make_queue ()
{
  GstElement *queue = gst_element_factory_make ("queue", NULL);
  g_object_set (queue, 
      "max-size-time", (guint64) 3 * GST_SECOND, 
      "max-size-bytes", (guint64) 0, 
      "max-size-buffers", (guint64) 0, NULL);

  return queue;
}

static GstElement *
_em_audio_sink_create (int index)
{
  GstElement *bin;
  GstElement *tee;
  GstElement *queue;
  GstElement *conv;
  GstElement *resample;
  GstElement *vis;
  GstElement *cspace;
  GstElement *sink;
  GstPad     *audiopad;
  GstPad     *teepad;

  /* audio sink */
  bin = gst_bin_new (NULL);

  tee = gst_element_factory_make ("tee", NULL);

  /* audio part */
  queue = make_queue ();
  conv = gst_element_factory_make ("audioconvert", NULL);
  resample = gst_element_factory_make ("audioresample", NULL);
  g_print ("index %d\n", index);
  if (index == 1)
    sink = gst_element_factory_make ("alsasink", NULL);
  else
    sink = gst_element_factory_make ("fakesink", NULL);

  gst_bin_add_many (GST_BIN (bin), tee, queue, conv, resample, sink, NULL);
  gst_element_link_many (queue, conv, resample, sink, NULL);

  audiopad = gst_element_get_pad (queue, "sink");
  teepad = gst_element_get_request_pad (tee, "src%d");
  gst_pad_link (teepad, audiopad);
  gst_object_unref (teepad);
  gst_object_unref (audiopad);

  /* visualisation part */
   queue = make_queue ();
   conv = gst_element_factory_make ("audioconvert", NULL); 
   vis = gst_element_factory_make ("goom", NULL); 
   cspace = gst_element_factory_make ("ffmpegcolorspace", NULL); 
   sink = gst_element_factory_make ("ximagesink", NULL); 
   
   gst_bin_add_many (GST_BIN (bin), queue, conv, vis, cspace, sink, NULL); 
   gst_element_link_many (queue, conv, vis, cspace, sink, NULL); 

   audiopad = gst_element_get_pad (queue, "sink"); 
   teepad = gst_element_get_request_pad (tee, "src%d"); 
   gst_pad_link (teepad, audiopad); 
   gst_object_unref (teepad); 
   gst_object_unref (audiopad); 

  audiopad = gst_element_get_pad (tee, "sink");
  gst_element_add_pad (bin, gst_ghost_pad_new ("sink", audiopad));
  gst_object_unref (audiopad);

  return bin;
}

static void
new_decoded_pad_cb (GstElement *decodebin,
                    GstPad     *new_pad,
                    gboolean    last,
                    gpointer    user_data)
{
  GstCaps *caps;
  gchar   *str;

  caps = gst_pad_get_caps (new_pad);
  str = gst_caps_to_string (caps);

  g_print (" caps: %s\n\n", str);
  /* video stream */
  if (g_str_has_prefix (str, "video/")) {
    GstElement         *queue;
    GstElement         *cspace;
    GstElement         *sink;
    GstPad             *videopad;
    GstPadLinkReturn    res;
    GstElement         *vsinkbin;
    GstPad             *ghost;

    vsinkbin = gst_bin_new (NULL);

    queue = make_queue ();
    cspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
    sink = gst_element_factory_make ("ximagesink", NULL);

    gst_bin_add_many (GST_BIN (vsinkbin), queue, cspace, sink, NULL);
    gst_element_link_many (queue, cspace, sink, NULL);

    gst_bin_add (GST_BIN (pipeline), vsinkbin);

    videopad = gst_element_get_pad (queue, "sink");
    ghost = gst_ghost_pad_new ("sink", videopad);
    gst_object_unref (videopad);

    gst_element_add_pad (vsinkbin, ghost); 
    res = gst_pad_link (new_pad, ghost);

    g_print ("res : %d\n", res);
    gst_object_unref (videopad);

    gst_element_set_state (vsinkbin, GST_STATE_PAUSED);
  }
  /* audio stream */
  else if (g_str_has_prefix (str, "audio/")) {
    GstElement         *sink;
    GstPad             *audiopad;

    audio_stream_nbr++;
    sink = _em_audio_sink_create (audio_stream_nbr);
    gst_bin_add (GST_BIN (pipeline), sink);

    audiopad = gst_element_get_pad (sink, "sink");
    gst_pad_link(new_pad, audiopad);

    gst_element_set_state (sink, GST_STATE_PAUSED);
  }
}

gboolean
_init_pipeline (const char *filename)
{
  GstElement          *filesrc;
  GstElement          *decodebin;
  GstStateChangeReturn res;

  pipeline = gst_pipeline_new (NULL);
  filesrc = gst_element_factory_make ("filesrc", "filesrc");
  if (!filesrc) {
    gst_object_unref (GST_OBJECT (pipeline));
    return FALSE;
  }
  g_object_set (G_OBJECT (filesrc), "location", filename, NULL);

  decodebin = gst_element_factory_make ("decodebin", "decodebin");
  if (!decodebin) {
    gst_object_unref (GST_OBJECT (pipeline));
    return FALSE;
  }
  g_signal_connect (decodebin, "new-decoded-pad",
                    G_CALLBACK (new_decoded_pad_cb), NULL);

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

  res = gst_element_set_state (pipeline, GST_STATE_PAUSED);
  if (res == GST_STATE_CHANGE_FAILURE) {
    g_print ("Gstreamer ERROR: could not pause\n");
    gst_object_unref (GST_OBJECT (pipeline));
    return FALSE;
  }
  res = gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
  if (res != GST_STATE_CHANGE_SUCCESS) {
    g_print ("Gstreamer ERROR: could not complete pause\n");
    gst_object_unref (GST_OBJECT (pipeline));
    return FALSE;
  }

  {
    GstIterator *it;
    gpointer data;
    
    it = gst_element_iterate_src_pads (decodebin);
    while (gst_iterator_next (it, &data) == GST_ITERATOR_OK) {
      GstPad *pad = GST_PAD (data);
      GstCaps *caps;
      gchar *str;
      GstQuery *query;

      g_print ("stream %s:\n", GST_OBJECT_NAME (pad));
      
      caps = gst_pad_get_caps (pad);
      str = gst_caps_to_string (caps);
      g_print (" caps: %s\n\n", str);
      if (g_str_has_prefix (str, "video/"))
        {
          GstStructure *structure;
          guint32       fourcc;
          const GValue *val;

          gint    width;
          gint    height;
          int     fps_num = 1;
          int     fps_den = 1;

          structure = gst_caps_get_structure (GST_CAPS (caps), 0);
          gst_structure_get_int (structure, "width", &width);
          gst_structure_get_int (structure, "height", &height);
          val = gst_structure_get_value (structure, "framerate");
          if (val) {
            fps_num = gst_value_get_fraction_numerator (val);
            fps_den = gst_value_get_fraction_denominator (val);
          }
          gst_structure_get_fourcc (structure, "format", &fourcc);
          printf ("  size      : %dx%d\n", width, height);
          printf ("  framerate : %d/%d\n", fps_num, fps_den);
          //          printf ("  fourcc    : " GST_FOURCC_FORMAT "\n", GST_FOURCC_ARGS (fourcc));
        }
      g_free (str);
      
      query = gst_query_new_duration (GST_FORMAT_TIME);
      if (gst_pad_query (pad, query)) {
        gint64 duration;
        
        gst_query_parse_duration (query, NULL, &duration);
        
        g_print ("  duration  : %" GST_TIME_FORMAT "\n\n", GST_TIME_ARGS (duration));
      }
      gst_query_unref (query);
      
      gst_object_unref (pad);
    }
    gst_iterator_free (it);
  }

  return TRUE;
}

void
_shutdown_pipeline ()
{
  GstStateChangeReturn res;

  res = gst_element_set_state (pipeline, GST_STATE_NULL);
  if (res == GST_STATE_CHANGE_FAILURE) {
    g_print ("Gstreamer ERROR: could not go to NULL\n");
  }
  res = gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
  if (res != GST_STATE_CHANGE_SUCCESS) {
    g_print ("Gstreamer ERROR: could not complete NULL\n");
  }

  gst_object_unref (GST_OBJECT (pipeline));
  gst_deinit ();
}

int
main (int argc, char *argv[])
{
  GMainLoop *loop;
  GstBus *bus;
  
  if (argc < 2) {
    g_print ("Usage : %s filename\n", argv[0]);
    return -1;
  }

  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);
  
  if (!_init_pipeline (argv[1]))
    return 1;
  
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);
  
  g_print ("on joue..\n");
  gst_element_set_state (pipeline, GST_STATE_PLAYING);
  
  g_main_loop_run (loop);
  
  _shutdown_pipeline ();

  return 0;
}
