#include <glib.h>

#include <gst/gst.h>

GstElement *_pipeline, *_fakesink, *_remotesink, *_mpoint, *_queue1, *_queue2,
    *_videobin, *_pipe, *gdppay, *tee, *rst;

GstBus *bus;

gboolean
bus_call (GstBus * bus, GstMessage * msg, gpointer data)
{
  switch (GST_MESSAGE_TYPE (msg)) {

    case GST_MESSAGE_SEGMENT_START:
    {
      g_print ("SEGMENT_START\n");
      break;
    }

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

      gst_message_parse_error (msg, &error, &debug);

      g_print ("ERROR! %s\n", error->message);
      g_print ("DEBUG %s\n", debug);
      g_free (debug);
      g_error_free (error);
    }
    default:
      break;
  }
  return TRUE;
}

void
create_pipeline_bin ()
{
  GstElement *first, *queue1, *queue2, *valve, *xvimagesink, *audiosink;
  _pipeline = gst_element_factory_make ("playbin", NULL);
  tee = gst_element_factory_make ("tee", NULL);
  rst = gst_element_factory_make ("resettime", NULL);
  xvimagesink = gst_element_factory_make ("ximagesink", NULL);
  GstPad *pad;
  _videobin = gst_bin_new ("videobin");
  _queue1 = queue1 = gst_element_factory_make ("queue", NULL);
  _queue2 = queue2 = gst_element_factory_make ("queue", NULL);
  valve = gst_element_factory_make ("valve", NULL);
  _fakesink = gst_element_factory_make ("fakesink", NULL);
  audiosink = gst_element_factory_make ("fakesink", NULL);
  gst_object_ref (_fakesink);
  _mpoint = valve;
  first = rst;
  gst_bin_add_many (GST_BIN (_videobin), rst, tee, queue1, queue2,
      valve, xvimagesink, _fakesink, NULL);
  gst_element_link (rst, tee);
  gst_element_link_many (tee, queue1, xvimagesink, NULL);
  gst_element_link_many (tee, queue2, valve, _fakesink, NULL);
  pad = gst_element_get_static_pad (first, "sink");
  gst_element_add_pad (_videobin, gst_ghost_pad_new ("sink", pad));
  gst_object_unref (GST_OBJECT (pad));
  g_object_set (G_OBJECT (_pipeline), "uri",
      "file:///home/alex/test.mp4", NULL);
  g_object_set (G_OBJECT (_pipeline), "video-sink", _videobin, NULL);
  g_object_set (G_OBJECT (_pipeline), "audio-sink", audiosink, NULL);
  bus = gst_pipeline_get_bus (GST_PIPELINE (_pipeline));
  gst_bus_add_watch (bus, bus_call, NULL);
  gst_object_unref (bus);
  gst_element_set_state (_pipeline, GST_STATE_PLAYING);
}

void
create_remote_bin ()
{
  GstElement *videomixer, *ffmpegcolorspace;
  GstPad *pad;
  videomixer = gst_element_factory_make ("videomixer", NULL);
  ffmpegcolorspace = gst_element_factory_make ("ffmpegcolorspace", NULL);
  _pipe = gst_element_factory_make ("xvimagesink", NULL);
  _remotesink = gst_bin_new ("vmixerbin");
  gst_bin_add_many (GST_BIN (_remotesink), videomixer,
      ffmpegcolorspace, _pipe, NULL);
  gst_element_link_many (videomixer, ffmpegcolorspace, _pipe, NULL);
  pad = gst_element_get_request_pad (videomixer, "sink_%d");
  gst_element_add_pad (_remotesink, gst_ghost_pad_new ("sink", pad));
  gst_object_unref (GST_OBJECT (pad));
  gst_object_ref (_remotesink);
}

void
connect_remote_client ()
{
  g_print ("connectClient\n");
  if (_remotesink && _mpoint && _videobin) {
    g_print ("connecting remote bin...\n");
    g_object_set(G_OBJECT (_mpoint), "drop", TRUE, NULL);
//     g_object_set(G_OBJECT (rst), "reset", TRUE, NULL);

//     GstPad *pad;
//     g_print ("GstClient: blocking pad...\n");
//     pad = gst_element_get_static_pad (tee, "src1");
//     gst_pad_set_blocked (pad, TRUE);
    g_print ("unlinking...\n");
    gst_element_unlink (_mpoint, _fakesink);
    gst_bin_remove (GST_BIN (_videobin), _fakesink);
    gst_element_set_state (_fakesink, GST_STATE_NULL);
    g_print ("linking...\n");
    gst_bin_add (GST_BIN (_videobin), _remotesink);
    gst_element_link (_mpoint, _remotesink);
    gst_element_sync_state_with_parent (_remotesink);
    gst_element_set_state (_pipeline, GST_STATE_PLAYING);
//     gst_pad_set_blocked (pad, FALSE);
//     gst_object_unref (pad);

    g_object_set(G_OBJECT (_mpoint), "drop", FALSE, NULL);
//     g_object_set(G_OBJECT (rst), "reset", TRUE, NULL);
    g_print ("END connecting remote bin...\n");
  }
}

void
disconnect_remote_client ()
{
  g_print ("disconnectClient\n");
  if (_remotesink && _mpoint && _videobin && _fakesink) {
    g_print ("disconnecting remote bin...\n");
    g_object_set(G_OBJECT (_mpoint), "drop", TRUE, NULL);
//     g_object_set(G_OBJECT (rst), "reset", TRUE, NULL);
//     GstPad *pad;
//     g_print ("GstClient: blocking pad...\n");
//     pad = gst_element_get_static_pad (tee, "src1");
//     gst_pad_set_blocked (pad, TRUE);
    g_print ("unlinking...\n");
    gst_element_unlink (_mpoint, _remotesink);
    gst_bin_remove (GST_BIN (_videobin), _remotesink);
    gst_element_set_state (_remotesink, GST_STATE_NULL);
    g_print ("linking...\n");
    gst_bin_add (GST_BIN (_videobin), _fakesink);
    gst_element_link (_mpoint, _fakesink);
    gst_element_sync_state_with_parent (_fakesink);
    gst_element_set_state (_pipeline, GST_STATE_PLAYING);
//     gst_pad_set_blocked (pad, FALSE);
//     gst_object_unref (pad);
    g_object_set(G_OBJECT (_mpoint), "drop", FALSE, NULL);
//     g_object_set(G_OBJECT (rst), "reset", TRUE, NULL);
    g_print ("END disconnecting remote bin...\n");
  }
}

void
pause_pipeline ()
{
  gst_element_set_state (_pipeline, GST_STATE_PAUSED);
}

void
play_pipeline ()
{
  gst_element_set_state (_pipeline, GST_STATE_PLAYING);
}

gboolean
connect_callback (gpointer data)
{
  g_print ("connect callback called\n");
  connect_remote_client ();
  return FALSE;
}

gboolean
disconnect_callback (gpointer data)
{
  g_print ("disconnect callback called\n");
  disconnect_remote_client ();
  return FALSE;
}

gboolean
pause_callback (gpointer data)
{
  g_print ("pause callback called\n");
  pause_pipeline ();
  return FALSE;
}

gboolean
play_callback (gpointer data)
{
  g_print ("play callback called\n");
  play_pipeline ();
  return FALSE;
}

int
main (int argc, char *argv[])
{
  gst_init (&argc, &argv);
  GMainLoop *loop;
  loop = g_main_loop_new (NULL, FALSE);
  create_remote_bin ();
  create_pipeline_bin ();
  g_timeout_add (7000, connect_callback, NULL);
  g_timeout_add (15000, pause_callback, NULL);
  g_timeout_add (17000, disconnect_callback, NULL);
  g_timeout_add (22000, play_callback, NULL);
  g_print ("Running...\n");
  g_main_loop_run(loop);
}
