#include <gst/gst.h>
#include <gst/base/gstbasesink.h>

#define GST_TYPE_TEST_SINK \
  (gst_test_sink_get_type())
#define GST_TEST_SINK(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TEST_SINK,GstTestSink))
#define GST_TEST_SINK_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TEST_SINK,GstTestSinkClass))
#define GST_IS_TEST_SINK(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TEST_SINK))
#define GST_IS_TEST_SINK_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TEST_SINK))
#define GST_TEST_SINK_CAST(obj) ((GstTestSink *)obj)

typedef struct _GstTestSink GstTestSink;
typedef struct _GstTestSinkClass
    GstTestSinkClass;
struct _GstTestSink
{
  GstBaseSink element;

  gint nbuffers;
  gint nallocs;
};

struct _GstTestSinkClass
{
  GstBaseSinkClass parent_class;
};

GType gst_test_sink_get_type (void);

GST_BOILERPLATE (GstTestSink,
    gst_test_sink, GstBaseSink, GST_TYPE_BASE_SINK);

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

static GstFlowReturn
gst_test_sink_buffer_alloc (GstBaseSink * bsink,
    guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf)
{
  GstTestSink *sink =
      GST_TEST_SINK (bsink);

  sink->nallocs++;

  *buf = gst_buffer_new_and_alloc (size);
  gst_buffer_set_caps (*buf, caps);

  return GST_FLOW_OK;
}

static GstFlowReturn
gst_test_sink_render (GstBaseSink * bsink,
    GstBuffer * buffer)
{
  GstTestSink *sink =
      GST_TEST_SINK (bsink);

  sink->nbuffers++;

  return GST_FLOW_OK;
}

static void
gst_test_sink_base_init (gpointer g_class)
{
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_set_details_simple (gstelement_class,
      "Test Sink",
      "Sink",
      "Some test sink", "Thiago Santos <thiago.sousa.santos@collabora.co.uk>");
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&sinktemplate));
}

static void
gst_test_sink_class_init (GstTestSinkClass
    * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseSinkClass *gstbase_sink_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gstelement_class = GST_ELEMENT_CLASS (klass);
  gstbase_sink_class = GST_BASE_SINK_CLASS (klass);

  gstbase_sink_class->buffer_alloc =
      GST_DEBUG_FUNCPTR (gst_test_sink_buffer_alloc);
  gstbase_sink_class->render =
      GST_DEBUG_FUNCPTR (gst_test_sink_render);
}

static void
gst_test_sink_init (GstTestSink * sink,
    GstTestSinkClass * g_class)
{
  sink->nbuffers = 0;
  sink->nallocs = 0;
}

static GMainLoop *loop;

static void
bus_message (GstBus * bus, GstMessage * message,
    gpointer data)
{
  GError *err = NULL;
  gchar *debug;

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:
      gst_message_parse_error (message, &err, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), err, debug);
      g_error_free (err);
      g_free (debug);
      g_assert_not_reached ();
      break;
    case GST_MESSAGE_WARNING:
      gst_message_parse_warning (message, &err, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), err, debug);
      g_error_free (err);
      g_free (debug);
      g_assert_not_reached ();
      break;
    case GST_MESSAGE_EOS:
      g_main_loop_quit (loop);
      break;
    default:
      break;
  }
}


int main() {
  GstElement *csp;
  GstElement *vscale;
  GstElement *vcrop;
  GstElement *csp2;
  GstTestSink *sink;
  GstElement *pipe;
  GstElement *src;
  GstBus *bus;

  gst_init (NULL, NULL);

  loop = g_main_loop_new (NULL, FALSE);

  pipe = gst_pipeline_new (NULL);

  src = gst_element_factory_make ("videotestsrc", NULL);
  csp = gst_element_factory_make ("ffmpegcolorspace", NULL);
  vscale = gst_element_factory_make ("videoscale", NULL);
  vcrop = gst_element_factory_make ("videocrop", NULL);
  csp2 = gst_element_factory_make ("ffmpegcolorspace", NULL);
  sink = g_object_new (GST_TYPE_TEST_SINK, NULL);

  g_object_set (src, "num-buffers", 100000, NULL);
  g_object_set (sink, "sync", FALSE, NULL);

  gst_bin_add_many (GST_BIN (pipe), src, csp, vscale, vcrop, csp2, sink, NULL);
  gst_element_link_many (src, csp, vscale, vcrop, csp2, sink, NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipe));
  gst_bus_add_watch (bus, (GstBusFunc) bus_message, NULL);
  gst_object_unref (bus);

  gst_element_set_state (pipe, GST_STATE_PLAYING);
  g_main_loop_run (loop);
  gst_element_set_state (pipe, GST_STATE_NULL);

  /* the stats */
  g_print ("Allocs:  %d\n", sink->nallocs);
  g_print ("Buffers: %d\n", sink->nbuffers);

  gst_object_unref (pipe);
  g_main_loop_unref (loop);
  return 0;
}
