/*
  g++ -g `pkg-config --cflags gstreamer-1.0`    ./test-case.c \
         `pkg-config --libs   gstreamer-1.0` -o ./test-case
*/

#include <unistd.h>
#include <gst/gst.h>


/* element creation helpers */

GstPad* NewGhostPad(GstElement*  a_bin       , GstElement*  an_element   ,
                    const gchar* template_id , const gchar* public_pad_id)
{
  const gchar*  private_id = template_id ;
  const gchar*  public_id  = public_pad_id ;
  GstPad       *private_pad , *public_pad ;

  bool is_err = !(private_pad = gst_element_get_static_pad(an_element , private_id )) ||
                !(public_pad  = gst_ghost_pad_new         (public_id  , private_pad)) ||
                !gst_pad_set_active(public_pad , TRUE)                                 ;
  gst_object_unref(private_pad) ;

  is_err = is_err || a_bin == NULL || !gst_element_add_pad(a_bin , public_pad) ;

  if (is_err) gst_object_unref(public_pad) ;

  return (!is_err) ? public_pad : NULL ;
}


/* pipeline configuration */

bool BuildScreencapBin(GstElement* screencap_bin)
{
  GstElement *source ;

  // instantiate elements
  if (!(source = gst_element_factory_make("videotestsrc" , "screen-faux-source")))
    return false ;

  // configure elements
  g_object_set(G_OBJECT(source) , "is_live" , (gboolean)true , NULL) ;
  g_object_set(G_OBJECT(source) , "pattern" , (guint  )0     , NULL) ;

  // link elements
  if (!gst_bin_add(GST_BIN(screencap_bin) , source)                ||
      !NewGhostPad(screencap_bin , source , "src" , "screen-source"))
    return false ;

  return true ;
}

bool BuildCameraBin(GstElement* camera_bin)
{
  GstElement *source ;

  // instantiate elements
  if (!(source = gst_element_factory_make("videotestsrc" , "camera-faux-source")))
    return false ;

  // configure elements
  g_object_set(G_OBJECT(source) , "is_live" , (gboolean)true , NULL) ;
  g_object_set(G_OBJECT(source) , "pattern" , (guint  )1     , NULL) ;

  // link elements
  if (!gst_bin_add(GST_BIN(camera_bin) , source)                ||
      !NewGhostPad(camera_bin , source , "src" , "camera-source"))
    return false ;

  return true ;
}

bool BuildCompositorBin(GstElement* compositor_bin)
{
  GstElement *fullscreen_queue , *overlay_queue                                ,
             *compositor       , *capsfilter           , *converter            ,
             *composite_tee    , *composite_sink_queue , *composite_thru_queue ;
  GstPad     *fullscreen_sinkpad     , *overlay_sinkpad        ,
             *tee_thru_srcpad        , *tee_monitor_srcpad     ,
             *fullscreen_thru_srcpad , *overlay_thru_srcpad    ,
             *composite_thru_sinkpad , *composite_sink_sinkpad ;

#define CAPS "video/x-raw, "                      \
            "width=(int)640, "                   \
            "height=(int)480, "                  \
            "framerate=(fraction)8/1, "          \
            "format=I420, "                      \
            "pixel-aspect-ratio=(fraction)1/1, " \
            "interlace-mode=(string)progressive"
  GstCaps* caps = gst_caps_from_string(CAPS) ;

  // instantiate elements
  if (!(fullscreen_queue     = gst_element_factory_make("queue"        , "compositor-fullscreen-queue")) ||
      !(overlay_queue        = gst_element_factory_make("queue"        , "compositor-overlay-queue"   )) ||
      !(compositor           = gst_element_factory_make("compositor"   , "compositor"                 )) ||
      !(capsfilter           = gst_element_factory_make("capsfilter"   , "compositor-capsfilter"      )) ||
      !(converter            = gst_element_factory_make("videoconvert" , "compositor-converter"       )) ||
      !(composite_tee        = gst_element_factory_make("tee"          , "compositor-tee"             )) ||
      !(composite_sink_queue = gst_element_factory_make("queue"        , "compositor-sink-queue"      )) ||
      !(composite_thru_queue = gst_element_factory_make("queue"        , "compositor-thru-queue"      ))  )
    return false ;

  // configure elements
  g_object_set(G_OBJECT(fullscreen_queue    ) , "max-size-bytes"   , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(fullscreen_queue    ) , "max-size-time"    , (guint64)0 , NULL) ;
  g_object_set(G_OBJECT(fullscreen_queue    ) , "max-size-buffers" , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(overlay_queue       ) , "max-size-bytes"   , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(overlay_queue       ) , "max-size-time"    , (guint64)0 , NULL) ;
  g_object_set(G_OBJECT(overlay_queue       ) , "max-size-buffers" , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(compositor          ) , "background"       , (guint  )3 , NULL) ;
  g_object_set(G_OBJECT(capsfilter          ) , "caps"             , caps       , NULL) ;
  g_object_set(G_OBJECT(composite_sink_queue) , "max-size-bytes"   , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(composite_sink_queue) , "max-size-time"    , (guint64)0 , NULL) ;
  g_object_set(G_OBJECT(composite_sink_queue) , "max-size-buffers" , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(composite_thru_queue) , "max-size-bytes"   , (guint  )0 , NULL) ;
  g_object_set(G_OBJECT(composite_thru_queue) , "max-size-time"    , (guint64)0 , NULL) ;
  g_object_set(G_OBJECT(composite_thru_queue) , "max-size-buffers" , (guint  )0 , NULL) ;

  // link elements
  if (!gst_bin_add     (GST_BIN(compositor_bin) , fullscreen_queue    ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , overlay_queue       ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , compositor          ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , capsfilter          ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , converter           ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , composite_tee       ) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , composite_sink_queue) ||
      !gst_bin_add     (GST_BIN(compositor_bin) , composite_thru_queue) ||
      !gst_element_link(compositor , capsfilter   )                     ||
      !gst_element_link(capsfilter , converter    )                     ||
      !gst_element_link(converter  , composite_tee)                      )
    return false ;

  // instantiate request pads
  if (!NewGhostPad(compositor_bin , fullscreen_queue , "sink" , "fullscreen-sink")   ||
      !NewGhostPad(compositor_bin , overlay_queue    , "sink" , "overlay-sink"   )   ||
      !(fullscreen_sinkpad = gst_element_get_request_pad(compositor    , "sink_%u")) ||
      !(overlay_sinkpad    = gst_element_get_request_pad(compositor    , "sink_%u")) ||
      !(tee_thru_srcpad    = gst_element_get_request_pad(composite_tee , "src_%u" )) ||
      !(tee_monitor_srcpad = gst_element_get_request_pad(composite_tee , "src_%u" ))  )
    return false ;

  // configure request pads
  g_object_set(G_OBJECT(fullscreen_sinkpad) , "width"  , 640 , NULL) ;
  g_object_set(G_OBJECT(fullscreen_sinkpad) , "height" , 480 , NULL) ;
  g_object_set(G_OBJECT(fullscreen_sinkpad) , "xpos"   , 0   , NULL) ;
  g_object_set(G_OBJECT(fullscreen_sinkpad) , "ypos"   , 0   , NULL) ;
  g_object_set(G_OBJECT(overlay_sinkpad   ) , "width"  , 160 , NULL) ;
  g_object_set(G_OBJECT(overlay_sinkpad   ) , "height" , 120 , NULL) ;
  g_object_set(G_OBJECT(overlay_sinkpad   ) , "xpos"   , 480 , NULL) ;
  g_object_set(G_OBJECT(overlay_sinkpad   ) , "ypos"   , 360 , NULL) ;

  // link ghost pads and request pads
  if (!(fullscreen_thru_srcpad = gst_element_get_static_pad(fullscreen_queue     , "src" )) ||
      !(overlay_thru_srcpad    = gst_element_get_static_pad(overlay_queue        , "src" )) ||
      !(composite_thru_sinkpad = gst_element_get_static_pad(composite_thru_queue , "sink")) ||
      !(composite_sink_sinkpad = gst_element_get_static_pad(composite_sink_queue , "sink")) ||
      gst_pad_link(fullscreen_thru_srcpad , fullscreen_sinkpad    ) != GST_PAD_LINK_OK      ||
      gst_pad_link(overlay_thru_srcpad    , overlay_sinkpad       ) != GST_PAD_LINK_OK      ||
      gst_pad_link(tee_thru_srcpad        , composite_thru_sinkpad) != GST_PAD_LINK_OK      ||
      gst_pad_link(tee_monitor_srcpad     , composite_sink_sinkpad) != GST_PAD_LINK_OK      ||
      !NewGhostPad(compositor_bin , composite_thru_queue , "src" , "compositor-source")     ||
      !NewGhostPad(compositor_bin , composite_sink_queue , "src" , "preview-source"   )      )
    return false ;

  return true ;
}


/* main entry point */

int main(int argc , char* argv[])
{
  GstElement *pipeline , *screencap_bin , *camera_bin , *compositor_bin ;

  // initialize gStreamer
  gst_init(NULL , NULL) ;

  // instantiate pipeline
  if (!(pipeline       = gst_pipeline_new("pipeline"      )) ||
      !(screencap_bin  = gst_bin_new     ("screencap-bin" )) ||
      !(camera_bin     = gst_bin_new     ("camera-bin"    )) ||
      !(compositor_bin = gst_bin_new     ("compositor-bin"))  )
    return 255 ;

  // configure pipeline
  if (!gst_bin_add(GST_BIN(pipeline) , screencap_bin ) ||
      !gst_bin_add(GST_BIN(pipeline) , camera_bin    ) ||
      !gst_bin_add(GST_BIN(pipeline) , compositor_bin)  )
    return 255 ;

  // configure bins
  if (!BuildScreencapBin (screencap_bin ) ||
      !BuildCameraBin    (camera_bin    ) ||
      !BuildCompositorBin(compositor_bin)  )
    return 255 ;

  // link bins
  if (!gst_element_link(screencap_bin , compositor_bin) ||
      !gst_element_link(camera_bin    , compositor_bin)  )
    return 255 ;

  // set rolling
  if (gst_element_set_state(pipeline , GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE)
    return 255 ;

#define WAIT_FOR_SEGFAULT (1)
#if WAIT_FOR_SEGFAULT
  usleep(10000000) ;
#endif

  if (gst_element_set_state(pipeline , GST_STATE_NULL) != GST_STATE_CHANGE_FAILURE)
    gst_object_unref(pipeline) ;
  return 0 ;
}
