#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static GstStaticPadTemplate subsinktemplate =
    GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
    GST_STATIC_CAPS
    ("text/plain; text/x-pango-markup; video/x-dvd-subpicture"));

typedef struct _App App;

struct _App
{
  GstElement *playbin;
  GstElement *appsink;
  GstElement *subtitlebin;
  GstElement *dvdsubdec;

  GstPad *ghostpad;

  GstPadBufferAllocFunction ghost_pad_buffer_alloc;

  GMainLoop *loop;
  GstCaps *prev_caps;
};

App s_app;

static gboolean
bus_message (GstBus * bus, GstMessage * message, App * app)
{
  GST_DEBUG ("got message %s",
      gst_message_type_get_name (GST_MESSAGE_TYPE (message)));

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_error (message, &gerror, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
      g_error_free (gerror);
      g_free (debug);

      g_main_loop_quit (app->loop);
      break;
    }
    case GST_MESSAGE_WARNING:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_warning (message, &gerror, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
      g_error_free (gerror);
      g_free (debug);
      break;
    }
    case GST_MESSAGE_EOS:
      g_message ("received EOS");
      g_main_loop_quit (app->loop);
      break;
    default:
    {
	gchar *sourceName;
	GstObject *source;

      source = GST_MESSAGE_SRC (message);
      sourceName = gst_object_get_name (source);
      if (gst_message_get_structure (message)) {
        gchar *string =
            gst_structure_to_string (gst_message_get_structure (message));
        g_print ("eServiceMP3::gst_message from %s: %s\n", sourceName, string);
        g_free (string);
      } else
        g_print ("eServiceMP3::gst_message from %s: %s (without structure)\n",
            sourceName, GST_MESSAGE_TYPE_NAME (message));
      break;
    }
  }
  return TRUE;
}

static void
have_subtitle (GstElement * appsink, App * app)
{
  GstBuffer *buffer;

  /* get the buffer, we can also wakeup the mainloop to get the subtitle from
   * appsink in the mainloop */
  g_signal_emit_by_name (appsink, "pull-buffer", &buffer);

  if (buffer) {
    guint8 *data;
    guint size;
    GstFormat format;
    gint64 position;
    GstClock *clock;
    GstClockTime base_time, running_time;

    format = GST_FORMAT_TIME;
    gst_element_query_position (appsink, &format, &position);

    clock = gst_element_get_clock (appsink);
    base_time = gst_element_get_base_time (appsink);

    running_time = gst_clock_get_time (clock) - base_time;

    gst_object_unref (clock);

    g_message ("received a subtitle at position %" GST_TIME_FORMAT
        ", running_time %" GST_TIME_FORMAT " caps=%s", GST_TIME_ARGS (position),
        GST_TIME_ARGS (running_time), gst_caps_to_string(GST_BUFFER_CAPS(buffer)));

    data = GST_BUFFER_DATA (buffer);
    size = GST_BUFFER_SIZE (buffer);
    
    if ( size > 255 )
	    size = 255;

    gst_util_dump_mem (data, size);
  }
}

GstCaps *
gstGhostpadGetCAPS (GstPad * pad)
{
  g_print ("gstGhostpadGetCAPS");
  return gst_static_pad_template_get_caps (&subsinktemplate);
//      return gst_pad_get_pad_template_caps(pad);
//      return get_pad_template_caps(pad);
//      GstCaps* caps = gst_caps_from_string("text/plain; text/x-pango-markup; video/x-dvd-subpicture");
//      return caps;
}

gboolean
gstGhostpadAcceptCAPS (GstPad * pad, GstCaps * caps)
{
		g_print ("gstGhostpadAcceptCAPS\n");
  GstCaps *templ_caps = gst_static_pad_template_get_caps (&subsinktemplate);
  gboolean ret = gst_caps_can_intersect (templ_caps, caps);

  gst_caps_unref (templ_caps);

  return ret;
}

void
do_link (App * app, GstCaps * caps)
{
  GstStructure *s;
  GstPad *sinkpad;

  // FIXME: Need to cache events from the ghostpad and pass them forward
  // now... and keep track of the segment and pass newsegment events
  // downstream.
  s = gst_caps_get_structure (caps, 0);
  
  gst_ghost_pad_set_target(app->ghostpad, NULL);
  gst_element_unlink(app->dvdsubdec, app->appsink);

  if (gst_structure_has_name (s, "video/x-dvd-subpicture")) {
    sinkpad = gst_element_get_static_pad (app->dvdsubdec, "sink");
    int ret = gst_element_link_pads (app->dvdsubdec, "src", app->appsink, "sink");
      g_print ("Linking ghostpad: dvdsubdec+appsink returns %i\n", ret);
  } else {
    sinkpad = gst_element_get_static_pad (app->appsink, "sink");
    g_print ("Linking ghostpad: appsink\n");
  }

  gst_ghost_pad_set_target (GST_GHOST_PAD (app->ghostpad), sinkpad);
}

GstFlowReturn
gstGhostpadBufferAlloc (GstPad *pad, guint64 offset, guint size, GstCaps *caps, GstBuffer **buf)
{
		g_print ("gstGhostpadBufferAlloc...\n");
  App *app = g_object_get_data (G_OBJECT (pad), "application-instance");

  if (!GST_PAD_CAPS (pad) || !gst_caps_is_equal (app->prev_caps, caps))
    do_link (app, caps);

  return app->ghost_pad_buffer_alloc (pad, offset, size, caps, buf);
}

void
gstGhostpadHasCAPS (GstPad *pad, GParamSpec * unused, App *app)
{
	g_print ("gstGhostpadHasCAPS...\n");
  GstCaps *caps;

  g_object_get (G_OBJECT (pad), "caps", &caps, NULL);
  if (!caps)
    return;

  if (!GST_PAD_CAPS (pad) || !gst_caps_is_equal (app->prev_caps, caps))
    do_link (app, caps);
  app->prev_caps = gst_caps_copy(caps);

  gst_caps_unref (caps);
}

static gboolean switch_stream (App *app)
{
	int current_pid, new_pid = 0;
	g_object_get (G_OBJECT (app->playbin), "current-text", &current_pid, NULL);
	
	if ( current_pid == 0 )
		new_pid = 1;
	else
		new_pid = 0;

	g_print ("switching from stream %i to stream %i...\n", current_pid, new_pid);
	g_object_set (G_OBJECT (app->playbin), "current-text", new_pid, NULL);
	g_object_get (G_OBJECT (app->playbin), "current-text", &current_pid, NULL);
	g_print ("switched to stream stream %i...\n", current_pid);
	
	return TRUE;
}

int
main (int argc, char *argv[])
{
  App *app = &s_app;
  GstBus *bus;

  gst_init (&argc, &argv);

  if (argc < 2) {
    g_print ("usage: %s <filename>\n", argv[0]);
    return -1;
  }

  /* create a mainloop to get messages */
  app->loop = g_main_loop_new (NULL, TRUE);

  app->playbin = gst_element_factory_make ("playbin2", NULL);
  g_assert (app->playbin);

  /* set appsink to get the subtitles */
  app->appsink = gst_element_factory_make ("appsink", "subtitle_sink");
  g_object_set (G_OBJECT (app->appsink), "emit-signals", TRUE, NULL);
  g_object_set (G_OBJECT (app->appsink), "ts-offset", 0 * GST_SECOND, NULL);
  g_signal_connect (app->appsink, "new-buffer", G_CALLBACK (have_subtitle),
      app);

  app->dvdsubdec =
      gst_element_factory_make ("dvdsubdec", "vobsubtitle_decoder");

  app->subtitlebin = gst_bin_new ("subtitle_bin");

  gst_bin_add_many (GST_BIN (app->subtitlebin), app->appsink, app->dvdsubdec,
      NULL);

  GstPadTemplate *templ;
  templ = gst_static_pad_template_get (&subsinktemplate);

  app->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
  gst_element_add_pad (app->subtitlebin, app->ghostpad);

  GstCaps *caps =
      gst_caps_from_string ("text/plain; text/x-pango-markup; video/x-raw-rgb");
  g_object_set (G_OBJECT (app->appsink), "caps", caps, NULL);
  gst_caps_unref (caps);

  g_object_set_data (G_OBJECT (app->ghostpad), "application-instance", app);
  g_signal_connect (G_OBJECT (app->ghostpad), "notify::caps", G_CALLBACK (gstGhostpadHasCAPS), app);
  gst_pad_set_getcaps_function (app->ghostpad, gstGhostpadGetCAPS);
  gst_pad_set_acceptcaps_function (app->ghostpad, gstGhostpadAcceptCAPS);
  app->ghost_pad_buffer_alloc = GST_PAD_BUFFERALLOCFUNC (app->ghostpad);
  gst_pad_set_bufferalloc_function (app->ghostpad, gstGhostpadBufferAlloc);

  g_object_set (G_OBJECT (app->playbin), "flags", 0x45, NULL);
  g_object_set (G_OBJECT (app->playbin), "text-sink", app->subtitlebin, NULL);

  g_object_set (G_OBJECT (app->dvdsubdec), "singlebuffer", TRUE, NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (app->playbin));

  /* add watch for messages */
  gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);

  g_object_set (app->playbin, "uri", argv[1], NULL);

  if (argc == 3) {
    g_message ("using external subtitles %s", argv[2]);
    g_object_set (app->playbin, "suburi", argv[2], NULL);
  }

  /* go to playing and wait in a mainloop. */
  gst_element_set_state (app->playbin, GST_STATE_PLAYING);
  
  g_timeout_add_seconds(20, switch_stream, app);

  /* this mainloop is stopped when we receive an error or EOS */
  g_main_loop_run (app->loop);

  g_message ("stopping");

  gst_element_set_state (app->playbin, GST_STATE_NULL);

  gst_object_unref (bus);
  g_main_loop_unref (app->loop);

  return 0;
}

