#include <gst/gst.h>

GstElement *pipe, *f;
GstStateChangeReturn ret_sc;
GstPadLinkReturn ret_pl;


static GstStaticPadTemplate sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate src_template =
GST_STATIC_PAD_TEMPLATE ("src%d",
    GST_PAD_SRC,
    GST_PAD_REQUEST,
    GST_STATIC_CAPS_ANY);

#define GST_TYPE_FILTERS_BIN             (gst_filters_bin_get_type())
#define GST_FILTERS_BIN(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FILTERS_BIN,GstFiltersBin))
#define GST_FILTERS_BIN_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FILTERS_BIN,GstFiltersBinClass))
#define GST_IS_FILTERS_BIN(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FILTERS_BIN))
#define GST_IS_FILTERS_BIN_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FILTERS_BIN))

typedef struct _GstFiltersBin GstFiltersBin;
typedef struct _GstFiltersBinClass GstFiltersBinClass;

/**
 * GstFiltersBin:
 *
 */
struct _GstFiltersBin
{
  GstBin bin;                   /* we extend GstBin */
  
  GstElement *tee_main;
  GHashTable *pads;
};

struct _GstFiltersBinClass
{
  GstBinClass parent_class;

};

static void gst_filters_bin_class_init (GstFiltersBinClass * klass);
static void gst_filters_bin_init (GstFiltersBin * filters_bin);
static void gst_filters_bin_dispose (GObject * object);
static void gst_filters_bin_finalize (GObject * object);
static GstPad *do_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar* name);

static GstElementClass *parent_class;

static const GstElementDetails gst_filters_bin_details =
GST_ELEMENT_DETAILS ("Filtersr Bin",
    "Generic/Bin/Filtersr",
    "blah blah bla",
    "");

static GType
gst_filters_bin_get_type (void)
{
  static GType gst_filters_bin_type = 0;

  if (!gst_filters_bin_type) {
    static const GTypeInfo gst_filters_bin_info = {
      sizeof (GstFiltersBinClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_filters_bin_class_init,
      NULL,
      NULL,
      sizeof (GstFiltersBin),
      0,
      (GInstanceInitFunc) gst_filters_bin_init,
      NULL
    };

    gst_filters_bin_type =
        g_type_register_static (GST_TYPE_BIN, "GstFiltersBin",
        &gst_filters_bin_info, 0);
  }

  return gst_filters_bin_type;
}

static void
gst_filters_bin_class_init (GstFiltersBinClass * klass)
{
  GObjectClass *gobject_klass;
  GstElementClass *gstelement_klass;
  GstBinClass *gstbin_klass;

  gobject_klass = (GObjectClass *) klass;
  gstelement_klass = (GstElementClass *) klass;
  gstbin_klass = (GstBinClass *) klass;

  parent_class = g_type_class_peek_parent (klass);

  gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_filters_bin_dispose);
  gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_filters_bin_finalize);

  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&sink_template));
  gst_element_class_add_pad_template (gstelement_klass,
      gst_static_pad_template_get (&src_template));
  
  gst_element_class_set_details (gstelement_klass, &gst_filters_bin_details);

  gstelement_klass->request_new_pad = do_request_new_pad;
}

static void
gst_filters_bin_init (GstFiltersBin * filters_bin)
{
  GstElement *first_el;
  GstPad *sinkpad, *p1;
  
  g_message ("init");

  first_el = gst_element_factory_make ("typefind", "");
  gst_bin_add (GST_BIN (filters_bin), first_el);
  p1 = gst_element_get_static_pad (first_el, "sink");

  sinkpad = gst_ghost_pad_new ("sink", p1);
  gst_pad_set_active (sinkpad, TRUE);
  gst_element_add_pad (GST_ELEMENT (filters_bin), sinkpad);

  filters_bin->tee_main = gst_element_factory_make ("tee", "tee-main");
  gst_bin_add (GST_BIN (filters_bin), filters_bin->tee_main);

  gst_element_link (GST_ELEMENT (first_el), filters_bin->tee_main);

  filters_bin->pads = g_hash_table_new (g_str_hash, g_str_equal);
}

static GstPadLinkReturn    
link_f (GstPad *pad,
	GstPad *peer) {
  GstElement *bin;
  GstState state, pending;
  GstFiltersBin *filters_bin;
  GstPad *rpad;
  
  filters_bin = GST_FILTERS_BIN (GST_PAD_PARENT (pad));
  
  g_message ("LINK FUNCTION");
  
  bin = gst_parse_bin_from_description ("queue2 ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity ! identity", TRUE, NULL);

  gst_element_get_state (GST_ELEMENT (filters_bin), &state, &pending, -1);
  
  rpad = gst_element_get_request_pad (filters_bin->tee_main, "src0");
  if (state == GST_STATE_PLAYING) {
    g_message ("BLOCKING");
    gst_pad_set_blocked (rpad, TRUE);
    g_message ("BLOCKED");
  }
  gst_bin_add (GST_BIN (filters_bin), bin);

  g_message ("link rpad");
  ret_pl = gst_pad_link (rpad, gst_element_get_static_pad (bin, "sink"));
  g_assert (ret_pl == GST_PAD_LINK_OK);

  gst_ghost_pad_set_target (GST_GHOST_PAD (pad), gst_element_get_static_pad (bin, "src"));
        
  g_message ("go to play");

  ret_sc = gst_element_set_state (bin, GST_STATE_PLAYING);
  g_message ("state change: %d", ret_sc);
  if (state == GST_STATE_PLAYING) {
    g_message ("unblocking");
    gst_pad_set_blocked (rpad, FALSE);
    g_message ("unblocked");
  }    
  
  g_message ("ret ok");
  
  return GST_PAD_LINK_OK;  
}

static GstPad *
do_request_new_pad (GstElement *element, 
		    GstPadTemplate *templ, 
		    const gchar* name) {
  GstPad *pad;
  GstFiltersBin *filters_bin;
  
  filters_bin = GST_FILTERS_BIN (element);

  g_message ("do request new pad: %s", name);
  
  pad = g_hash_table_lookup (filters_bin->pads, name);
  if (pad != NULL)
    return pad;

  pad = gst_ghost_pad_new_no_target (name, GST_PAD_SRC);
  gst_pad_set_active (pad, TRUE);
  gst_element_add_pad (element, pad);
  
  g_hash_table_insert (filters_bin->pads, g_strdup (name), pad);

  gst_pad_set_link_function (pad, (GstPadLinkFunction) link_f);

  return pad;
}

static void
gst_filters_bin_dispose (GObject * object)
{
  GstFiltersBin *filters_bin;

  filters_bin = GST_FILTERS_BIN (object);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gst_filters_bin_finalize (GObject * object)
{
  //GstFiltersBin *filters_bin = GST_FILTERS_BIN (object);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "ayfiltersbin", GST_RANK_NONE,
      GST_TYPE_FILTERS_BIN);
}

#define PACKAGE "asd"

GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    "filtersbin",
    "filters bin", plugin_init, "1.0", "GPL", "LALA",
    "")























static gboolean
test_bus_event (GstBus     *bus,
		GstMessage *msg,
		gpointer udata) {
  switch (GST_MESSAGE_TYPE (msg)) {
  case GST_MESSAGE_EOS:
    g_print ("End of stream\n");
    break;

  case GST_MESSAGE_ERROR: {
    gchar  *debug;
    GError *error;

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

    g_printerr ("Error: %s\n", error->message);
    g_error_free (error);
    break;
  }
  default:
    break;
  }

  return TRUE;
}

static gboolean
probe_sink (GstPad *pad,
	    GstBuffer *buffer,
	    gpointer udata) {
  gchar *name = gst_pad_get_name (pad);

  g_message ("BUFFER ON %s", name);
  
  return TRUE;  
}

static gboolean
test_request (gchar *pname) {
  GstPad *pad, *pad2, *bpad;
  GstElement *elf, *b;
  
  g_message ("Test request %s", pname);
  
  pad = gst_element_get_request_pad (f, pname);
  g_assert (pad != NULL);
  
  b = gst_parse_bin_from_description ("queue ! fakesink name=f", TRUE, NULL);
  
  elf = gst_bin_get_by_name (GST_BIN (b), "f");
  pad2 = gst_element_get_static_pad (elf, "sink");
  gst_pad_add_buffer_probe (pad2, G_CALLBACK (probe_sink), NULL);
  
  g_message ("Create bin");
  
  gst_bin_add (GST_BIN (pipe), b);
  ret_sc = gst_element_set_state (b, GST_STATE_READY);
  g_message ("state change: %d", ret_sc);
  
  g_message ("Set state");
  bpad = gst_element_get_static_pad (b, "sink");
  ret_pl = gst_pad_link (pad, bpad);
  g_assert (ret_pl == GST_PAD_LINK_OK);
  
  g_message ("link FIXME write ret");
  ret_sc = gst_element_set_state (b, GST_STATE_PLAYING);
  g_message ("state change: %d", ret_sc);
  g_message ("Set state (end of test_request)");
  g_message ("------------------------------");

  return FALSE;
}

int
main (int argc,
      gchar **argv) {
  GstBus *bus;
  gint i;
  GMainLoop *loop;
  
  g_thread_init (NULL);
  gst_init (NULL, NULL);
  
  pipe = gst_parse_launch ("videotestsrc ! video/x-raw-yuv,framerate=20/1 ! ayfiltersbin name=f f.src0 ! queue ! fakesink", NULL);
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipe));
  gst_bus_add_watch (bus, (GstBusFunc)test_bus_event, NULL);

  ret_sc = gst_element_set_state (pipe, GST_STATE_PLAYING);
  g_message ("state change: %d", ret_sc);

  f = gst_bin_get_by_name (GST_BIN (pipe), "f");
  
  
  for (i=1; i<5; i++) {
    gchar *pname = g_strdup_printf ("src%d", i);
    g_timeout_add (i*1000, (GSourceFunc)test_request, pname);
  }
  
  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);
  ret_sc = gst_element_set_state (pipe, GST_STATE_NULL);
  g_message ("state change: %d", ret_sc);
  g_object_unref (pipe);
  
  return 0;
}
