#include <gst/gst.h>

#define fail_unless(foo1, foo2, foo3) foo1
#define fail_if(foo) if ((foo)) { g_printerr("failing at line: %d\n", __LINE__); exit(1); }
#define ASSERT_OBJECT_REFCOUNT
#define ASSERT_OBJECT_REFCOUNT_BETWEEN

typedef struct _Segment {
  gdouble	rate;
  GstFormat	format;
  gint64	start, stop, position;
}	Segment;

typedef struct _CollectStructure {
  GstElement	*comp;
  GstElement	*sink;
  guint64	last_time;
  GList		*expected_segments;
}	CollectStructure;

static GstElement *
gst_element_factory_make_or_warn (const gchar * factoryname, const gchar * name)
{
  GstElement * element;

  element = gst_element_factory_make (factoryname, name);
  fail_unless (element != NULL, "Failed to make element %s", factoryname);
  return element;
}

static void
autoplug_newpad(GstElement    * element,
                    GstPad        * pad,
                    gboolean        last,
                    gpointer        userData)
{
    GstCaps       * caps;
    GstStructure  * str;
    GstElement    * conv = (GstElement*) userData;
    GstPad        * convPad;

    g_return_if_fail(conv != NULL);

    GST_DEBUG("autoplugging %s:%s to %s",
              gst_element_get_name(element),
              gst_pad_get_name(pad),
              gst_element_get_name(conv));
    g_printerr("autoplugging %s:%s to %s in %s\n",
               gst_element_get_name(element),
               gst_pad_get_name(pad),
               gst_element_get_name(conv),
               gst_element_get_name(gst_element_get_parent(element)));

    /* only link once */
    convPad = gst_element_get_pad(conv, "sink");
    if (GST_PAD_IS_LINKED(convPad)) {
        g_object_unref(convPad);
        return;
    }

    /* check media type */
    caps = gst_pad_get_caps(pad);
    str  = gst_caps_get_structure(caps, 0);
    if (!g_strrstr(gst_structure_get_name(str), "audio")) {
        gst_caps_unref(caps);
        gst_object_unref(convPad);
        return;
    }
    gst_caps_unref(caps);

    /* link'n'play */
    gst_pad_link(pad, convPad);
fprintf(stderr,"decode linked to queue\n");
}



static GstElement *
create_sink()
{
GstElement    *sink;

    sink = gst_element_factory_make_or_warn("autovideosink", "output");
g_printerr("sink: %p\n", sink);
    return sink;
}

static void
composition_pad_added_cb (GstElement *composition, GstPad *pad, CollectStructure * collect)
{
/*   GstCaps *linkcaps; */

/*   /\* Connect new pad to fakesink sinkpad *\/ */
/*   linkcaps = gst_caps_from_string ("video/x-raw-yuv,framerate=(fraction)25/1"); */
  
/*   fail_if ((!(gst_element_link_filtered (composition, collect->sink, linkcaps)))); */

/*   gst_caps_unref (linkcaps); */
  fail_if (!(gst_element_link (composition, collect->sink)));
}

/* return TRUE to discard the Segment */
static gboolean
compare_segments (Segment * segment, GstEvent * event)
{
  gboolean update;
  gdouble rate;
  GstFormat format;
  gint64 start, stop, position;

  gst_event_parse_new_segment (event, &update, &rate, &format, &start, &stop, &position);

  GST_DEBUG ("Got NewSegment update:%d, rate:%f, format:%d, start:%"GST_TIME_FORMAT
	     ", stop:%"GST_TIME_FORMAT", position:%"GST_TIME_FORMAT,
	     update, rate, format, GST_TIME_ARGS (start),
	     GST_TIME_ARGS (stop),
	     GST_TIME_ARGS (position));

  GST_DEBUG ("Expecting rate:%f, format:%d, start:%"GST_TIME_FORMAT
	     ", stop:%"GST_TIME_FORMAT", position:%"GST_TIME_FORMAT,
	     segment->rate, segment->format,
	     GST_TIME_ARGS (segment->start),
	     GST_TIME_ARGS (segment->stop),
	     GST_TIME_ARGS (segment->position));

  if (update) {
    GST_DEBUG ("was update, ignoring");
    return FALSE;
  }
  fail_if (rate != segment->rate);
  fail_if (format != segment->format);
  fail_if (start != segment->start);
  fail_if (stop != segment->stop);
  fail_if (position != segment->position);

  GST_DEBUG ("Segment was valid, discarding expected Segment");

  return TRUE;
}

static gboolean
sinkpad_event_probe (GstPad * sinkpad, GstEvent * event, CollectStructure * collect)
{
  Segment * segment;
  
  if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) {
    fail_if (collect->expected_segments == NULL);
    segment = (Segment *) collect->expected_segments->data;

    if (compare_segments (segment, event)) {
      collect->expected_segments = g_list_next (collect->expected_segments);
      g_free (segment);
    }
  }

  return TRUE;
}

static GstElement *
audio_gnl_src (const gchar * name, guint64 start, gint64 duration, gint pattern, guint priority)
{
  GstElement * gnlsource = NULL;
  GstElement * bin;
  GstElement * source, * dec, * aconv;
  GstPad     * pad;
  gboolean     ret;

#ifndef USE_TEST_SRC
  source   = gst_element_factory_make_or_warn("filesrc", "filesrc");
  g_object_set(G_OBJECT(source), "location", "/tmp/test.mp3", NULL);
  dec  = gst_element_factory_make_or_warn("mad", "mad");
  aconv = gst_element_factory_make_or_warn("audioconvert", "aconv");

  bin = gst_bin_new("bin");

  gst_bin_add_many(GST_BIN(bin), source, dec, aconv, NULL);

  ret = gst_element_link_many(source, dec, aconv, NULL);
  g_printerr("audio_gnl_src #2, %d\n", ret);

  pad = gst_element_get_pad(aconv, "src");
  ret = gst_element_add_pad(bin, gst_ghost_pad_new("src", pad));
  g_printerr("audio_gnl_src #3, %d\n", ret);

#else
  bin = gst_element_factory_make_or_warn("audiotestsrc", "audiotestsrc");
#endif

  gnlsource = gst_element_factory_make_or_warn ("gnlsource", name);
  fail_if (gnlsource == NULL);

  g_object_set (G_OBJECT (gnlsource),
		"start", start,
		"duration", duration,
		"media-start", start,
		"media-duration", duration,
		"priority", priority,
		NULL);

  gst_bin_add(GST_BIN(gnlsource), bin);

  return gnlsource;
}

static GstElement *
videotest_gnl_src (const gchar * name, guint64 start, gint64 duration, gint pattern, guint priority)
{
  GstElement * gnlsource = NULL;
  GstElement * videotestsrc = NULL;
  GstElement * bin = NULL;
  GstPad     * pad;

#ifndef USE_TEST_SRC
  videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
  g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);

  bin = gst_bin_new("bin");
  gst_bin_add(GST_BIN(bin), videotestsrc);

  pad = gst_element_get_pad(videotestsrc, "src");
  gst_element_add_pad(bin, gst_ghost_pad_new("src", pad));
#else
  videotestsrc = gst_element_factory_make_or_warn ("videotestsrc", NULL);
  g_object_set (G_OBJECT (videotestsrc), "pattern", pattern, NULL);
  bin = videotestsrc;
#endif

  gnlsource = gst_element_factory_make_or_warn ("gnlsource", name);
  fail_if (gnlsource == NULL);

  g_object_set (G_OBJECT (gnlsource),
		"start", start,
		"duration", duration,
		"media-start", start,
		"media-duration", duration,
		"priority", priority,
		NULL);

  gst_bin_add (GST_BIN (gnlsource), bin);
  
  return gnlsource;
}

static Segment *
segment_new (gdouble rate, GstFormat format, gint64 start, gint64 stop, gint64 position)
{
  Segment * segment;

  segment = g_new0 (Segment, 1);

  segment->rate = rate;
  segment->format = format;
  segment->start = start;
  segment->stop = stop;
  segment->position = position;

  return segment;
}

#define check_start_stop_duration(object, startval, stopval, durval)	\
  { \
    g_object_get (object, "start", &start, "stop", &stop, "duration", &duration, NULL); \
    fail_if (start != startval); \
    fail_if (stop != stopval); \
    fail_if (duration != durval); \
  }


int
main (int argc, char **argv)
{
  GstElement *pipeline;
  GstElement *comp, *sink, *source1, *source2;
  CollectStructure *collect;
  GstBus *bus;
  GstMessage *message;
  gboolean	carry_on = TRUE;
  guint64 start, stop;
  gint64 duration;
  GstPad *sinkpad;

  gst_init (&argc, &argv);

  pipeline = gst_pipeline_new ("test_pipeline");
  comp = gst_element_factory_make_or_warn ("gnlcomposition", "test_composition");
  fail_if (comp == NULL);

  /*
    Source 1
    Start : 0s
    Duration : 2s
    Priority : 1
  */
g_printerr("main #0\n");
  source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 1, 1);
  //source1 = videotest_gnl_src ("source1", 0, 1 * GST_SECOND, 1, 1);
  fail_if (source1 == NULL);
  check_start_stop_duration(source1, 0, 1 * GST_SECOND, 1 * GST_SECOND);

  /*
    Source 2
    Start : 2s
    Duration : 2s
    Priority : 1
  */
  source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1);
  //source2 = videotest_gnl_src ("source2", 1 * GST_SECOND, 1 * GST_SECOND, 2, 1);
  fail_if (source2 == NULL);
  check_start_stop_duration(source2, 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND);

  /* Add one source */

  gst_bin_add (GST_BIN (comp), source1);
  check_start_stop_duration(comp, 0, 1 * GST_SECOND, 1 * GST_SECOND);

  ASSERT_OBJECT_REFCOUNT(source1, "source1", 1);

  /* Second source */

  gst_bin_add (GST_BIN (comp), source2);
  check_start_stop_duration(comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);

  ASSERT_OBJECT_REFCOUNT(source2, "source2", 1);

#ifdef EXTRA_TESTS
  /* Remove first source */

  gst_object_ref (source1);
  gst_bin_remove (GST_BIN (comp), source1);
  check_start_stop_duration(comp, 1 * GST_SECOND, 2 * GST_SECOND, 1 * GST_SECOND);

  ASSERT_OBJECT_REFCOUNT(source1, "source1", 1);
 
  /* Re-add first source */

  gst_bin_add (GST_BIN (comp), source1);
  check_start_stop_duration(comp, 0, 2 * GST_SECOND, 2 * GST_SECOND);
  gst_object_unref (source1);

  ASSERT_OBJECT_REFCOUNT(source1, "source1", 1);
#endif
 
g_printerr("main #1\n");
  sink = create_sink();
  //sink = gst_element_factory_make_or_warn ("autovideosink", "sink");
  fail_if (sink == NULL);

  gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL);

  /* Shared data */
  collect = g_new0 (CollectStructure, 1);
  collect->comp = comp;
  collect->sink = sink;
  
g_printerr("main #2\n");
  /* Expected segments */
  collect->expected_segments = g_list_append (collect->expected_segments,
					      segment_new (1.0, GST_FORMAT_TIME,
							   0, 1 * GST_SECOND, 0));
  collect->expected_segments = g_list_append (collect->expected_segments,
					      segment_new (1.0, GST_FORMAT_TIME,
							   1 * GST_SECOND, 2 * GST_SECOND,
							   1 * GST_SECOND));

  g_signal_connect (G_OBJECT (comp), "pad-added",
		    G_CALLBACK (composition_pad_added_cb), collect);

  sinkpad = gst_element_get_pad (sink, "sink");
  gst_pad_add_event_probe (sinkpad, G_CALLBACK (sinkpad_event_probe), collect);

  bus = gst_element_get_bus (GST_ELEMENT (pipeline));

  GST_DEBUG ("Setting pipeline to PLAYING");
  ASSERT_OBJECT_REFCOUNT(source1, "source1", 1);

  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);

  GST_DEBUG ("Let's poll the bus");

g_printerr("main #3\n");
  while (carry_on) {
    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 2);
    if (message) {
      g_printerr("main #3.0: got message: %s\n", GST_MESSAGE_TYPE_NAME(message));
      switch (GST_MESSAGE_TYPE (message)) {
      case GST_MESSAGE_EOS:
        /* we should check if we really finished here */
        GST_WARNING ("Got an EOS");
        carry_on = FALSE;
        break;
      case GST_MESSAGE_SEGMENT_START:
      case GST_MESSAGE_SEGMENT_DONE:
        /* We shouldn't see any segement messages, since we didn't do a segment seek */
        GST_WARNING ("Saw a Segment start/stop");
        fail_if (FALSE);
        break;
      case GST_MESSAGE_ERROR:
        g_printerr("main #3.1 ERROR\n");
        fail_if (FALSE);
      default:
        break;
      }
      gst_mini_object_unref (GST_MINI_OBJECT (message));
    }
  }

g_printerr("main #4\n");
  GST_DEBUG ("Setting pipeline to NULL");

#ifdef EXTRA_TESTS
  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);

  fail_if (collect->expected_segments != NULL);

  GST_DEBUG ("Resetted pipeline to READY");

  /* Expected segments */
  collect->expected_segments = g_list_append (collect->expected_segments,
					      segment_new (1.0, GST_FORMAT_TIME,
							   0, 1 * GST_SECOND, 0));
  collect->expected_segments = g_list_append (collect->expected_segments,
					      segment_new (1.0, GST_FORMAT_TIME,
							   1 * GST_SECOND, 2 * GST_SECOND,
							   1 * GST_SECOND));  

  GST_DEBUG ("Setting pipeline to PLAYING again");

  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE);

  carry_on = TRUE;
  while (carry_on) {
    message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 2);
    if (message) {
      switch (GST_MESSAGE_TYPE (message)) {
      case GST_MESSAGE_EOS:
	/* we should check if we really finished here */
	carry_on = FALSE;
	break;
      case GST_MESSAGE_SEGMENT_START:
      case GST_MESSAGE_SEGMENT_DONE:
	/* We shouldn't see any segement messages, since we didn't do a segment seek */
	GST_WARNING ("Saw a Segment start/stop");
	fail_if (FALSE);
	break;
      case GST_MESSAGE_ERROR:
	fail_if (FALSE);
      default:
	break;
      }
      gst_mini_object_unref (GST_MINI_OBJECT (message));
    }
  }

  fail_if (collect->expected_segments != NULL);
#endif

  fail_if (gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);

g_printerr("main #5\n");
  ASSERT_OBJECT_REFCOUNT_BETWEEN(pipeline, "main pipeline", 1, 2);
  gst_object_unref (pipeline);
  ASSERT_OBJECT_REFCOUNT_BETWEEN(bus, "main bus", 1, 2);
  gst_object_unref (bus);


  return 0;
}
