#include <gst/gst.h>
#include <gst/sdp/sdp.h>
#include <gst/webrtc/webrtc.h>

#include <string.h>

static GMainLoop *loop;
static GstElement *pipe1, *webrtc1, *webrtc2;
static GstBus *bus1;

static gboolean
_bus_watch (GstBus * bus, GstMessage * msg, GstElement * pipe)
{
  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_STATE_CHANGED:
      if (GST_ELEMENT (msg->src) == pipe) {
        GstState old, new, pending;

        gst_message_parse_state_changed (msg, &old, &new, &pending);

        {
          gchar *dump_name = g_strconcat ("state_changed-",
              gst_element_state_get_name (old), "_",
              gst_element_state_get_name (new), NULL);
          GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (msg->src),
              GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
          g_free (dump_name);
        }
      }
      break;
    case GST_MESSAGE_ERROR:{
      GError *err = NULL;
      gchar *dbg_info = NULL;

      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
          GST_DEBUG_GRAPH_SHOW_ALL, "error");

      gst_message_parse_error (msg, &err, &dbg_info);
      g_printerr ("ERROR from element %s: %s\n",
          GST_OBJECT_NAME (msg->src), err->message);
      g_printerr ("Debugging info: %s\n", (dbg_info) ? dbg_info : "none");
      g_error_free (err);
      g_free (dbg_info);
      g_main_loop_quit (loop);
      break;
    }
    case GST_MESSAGE_EOS:{
      GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipe),
          GST_DEBUG_GRAPH_SHOW_ALL, "eos");
      g_print ("EOS received\n");
      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

static void
_webrtc_pad_added (GstElement * webrtc, GstPad * new_pad, GstElement * pipe)
{
  GstElement *out = NULL;
  GstPad *sink = NULL;
  GstCaps *caps;
  GstStructure *s;
  const gchar *encoding_name;

  if (GST_PAD_DIRECTION (new_pad) != GST_PAD_SRC)
    return;

  caps = gst_pad_get_current_caps (new_pad);
  if (!caps)
    caps = gst_pad_query_caps (new_pad, NULL);
  GST_ERROR_OBJECT (new_pad, "caps %" GST_PTR_FORMAT, caps);
  g_assert (gst_caps_is_fixed (caps));
  s = gst_caps_get_structure (caps, 0);
  encoding_name = gst_structure_get_string (s, "encoding-name");
  if (g_strcmp0 (encoding_name, "VP8") == 0) {
    out = gst_parse_bin_from_description ("rtpvp8depay ! vp8dec ! "
        "videoconvert ! queue ! xvimagesink sync=false", TRUE, NULL);
  } else if (g_strcmp0 (encoding_name, "OPUS") == 0) {
    out = gst_parse_bin_from_description ("rtpopusdepay ! opusdec ! "
        "audioconvert ! audioresample ! audiorate ! queue ! autoaudiosink",
        TRUE, NULL);
  } else {
    g_critical ("Unknown encoding name %s", encoding_name);
    g_assert_not_reached ();
  }
  gst_bin_add (GST_BIN (pipe), out);
  gst_element_sync_state_with_parent (out);
  sink = out->sinkpads->data;

  gst_pad_link (new_pad, sink);

  gst_caps_unref (caps);
}

static void
_on_answer_received (GstPromise * promise, gpointer user_data)
{
  GstWebRTCSessionDescription *answer = NULL;
  const GstStructure *reply;
  gchar *desc;

  g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
  reply = gst_promise_get_reply (promise);
  gst_structure_get (reply, "answer",
      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
  gst_promise_unref (promise);
  desc = gst_sdp_message_as_text (answer->sdp);
  g_print ("Created answer:\n%s\n", desc);
  g_free (desc);

  g_signal_emit_by_name (webrtc1, "set-remote-description", answer, NULL);
  g_signal_emit_by_name (webrtc2, "set-local-description", answer, NULL);

  gst_webrtc_session_description_free (answer);
}

static void
_on_offer_received (GstPromise * promise, gpointer user_data)
{
  GstWebRTCSessionDescription *offer = NULL;
  const GstStructure *reply;
  gchar *desc;

  g_assert (gst_promise_wait (promise) == GST_PROMISE_RESULT_REPLIED);
  reply = gst_promise_get_reply (promise);
  gst_structure_get (reply, "offer",
      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
  gst_promise_unref (promise);
  desc = gst_sdp_message_as_text (offer->sdp);
  g_print ("Created offer:\n%s\n", desc);
  g_free (desc);

  g_signal_emit_by_name (webrtc1, "set-local-description", offer, NULL);
  g_signal_emit_by_name (webrtc2, "set-remote-description", offer, NULL);

  promise = gst_promise_new_with_change_func (_on_answer_received, user_data,
      NULL);
  g_signal_emit_by_name (webrtc2, "create-answer", NULL, promise);

  gst_webrtc_session_description_free (offer);
}

static void
_on_negotiation_needed (GstElement * element, gpointer user_data)
{
  GstPromise *promise;

  promise = gst_promise_new_with_change_func (_on_offer_received, user_data,
      NULL);
  g_signal_emit_by_name (webrtc1, "create-offer", NULL, promise);
}

static void
_on_ice_candidate (GstElement * webrtc, guint mlineindex, gchar * candidate,
    GstElement * other)
{
  g_signal_emit_by_name (other, "add-ice-candidate", mlineindex, candidate);
}

int
main (int argc, char *argv[])
{
  gst_init (&argc, &argv);

  loop = g_main_loop_new (NULL, FALSE);
  pipe1 =
      gst_parse_launch ("webrtcbin name=smpte webrtcbin name=ball "
      "videotestsrc pattern=smpte ! queue ! vp8enc ! rtpvp8pay ! queue ! "
      "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! smpte.sink_0 "
      "audiotestsrc ! opusenc ! rtpopuspay ! queue ! "
      "application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! smpte.sink_1 "
      "videotestsrc pattern=ball ! queue ! vp8enc ! rtpvp8pay ! queue ! "
      "application/x-rtp,media=video,payload=96,encoding-name=VP8 ! ball.sink_1 "
      "audiotestsrc wave=saw ! opusenc ! rtpopuspay ! queue ! "
      "application/x-rtp,media=audio,payload=97,encoding-name=OPUS ! ball.sink_0 ",
      NULL);
  bus1 = gst_pipeline_get_bus (GST_PIPELINE (pipe1));
  gst_bus_add_watch (bus1, (GstBusFunc) _bus_watch, pipe1);

  webrtc1 = gst_bin_get_by_name (GST_BIN (pipe1), "smpte");
  g_signal_connect (webrtc1, "on-negotiation-needed",
      G_CALLBACK (_on_negotiation_needed), NULL);
  g_signal_connect (webrtc1, "pad-added", G_CALLBACK (_webrtc_pad_added),
      pipe1);
  webrtc2 = gst_bin_get_by_name (GST_BIN (pipe1), "ball");
  g_signal_connect (webrtc2, "pad-added", G_CALLBACK (_webrtc_pad_added),
      pipe1);
  g_signal_connect (webrtc1, "on-ice-candidate",
      G_CALLBACK (_on_ice_candidate), webrtc2);
  g_signal_connect (webrtc2, "on-ice-candidate",
      G_CALLBACK (_on_ice_candidate), webrtc1);

  g_print ("Starting pipeline\n");
  gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_PLAYING);

  g_main_loop_run (loop);

  gst_element_set_state (GST_ELEMENT (pipe1), GST_STATE_NULL);
  g_print ("Pipeline stopped\n");

  gst_object_unref (webrtc1);
  gst_object_unref (webrtc2);
  gst_bus_remove_watch (bus1);
  gst_object_unref (bus1);
  gst_object_unref (pipe1);

  gst_deinit ();

  return 0;
}
