/*
 * compilation:
 *
 * gcc -Wall -ggdb `pkg-config --cflags --libs gstreamer-1.0 gstreamer-video-1.0` test.c -o test
 */

#include <gst/gst.h>
#include <gst/video/navigation.h>

static gboolean
bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
{
  GMainLoop *loop = user_data;

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ELEMENT:
    {
      GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
      if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
        GstEvent *ev = NULL;

        if (gst_navigation_message_parse_event (msg, &ev)) {
          GstNavigationEventType type = gst_navigation_event_get_type (ev);
          if (type == GST_NAVIGATION_EVENT_KEY_PRESS) {
            const gchar *key;

            if (gst_navigation_event_parse_key_event (ev, &key)) {
              if (key[0] == 'q')
                g_main_loop_quit (loop);
            }
          }
        }
        if (ev)
          gst_event_unref (ev);
      }

      break;
    }
    case GST_MESSAGE_WARNING:
    {
      GError *err = NULL;

      gst_message_parse_warning (msg, &err, NULL);
      gst_printerrln ("WARNING: (%s) %s", GST_MESSAGE_SRC_NAME (msg),
          err->message);
      g_error_free (err);
      break;
    }
    case GST_MESSAGE_ERROR:
    {
      GError *err = NULL;

      gst_message_parse_error (msg, &err, NULL);
      gst_printerrln ("ERROR: (%s) %s", GST_MESSAGE_SRC_NAME (msg),
          err->message);
      g_error_free (err);
      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

static GstElement *
build_recv ()
{
  GstCaps *caps;
  GstElement *pipeline, *src, *sink;
  GError *err = NULL;

  pipeline = gst_parse_launch ("udpsrc name=src ! rtph264depay ! h264parse ! "
      "vaapih264dec ! vaapipostproc ! vaapisink name=sink", &err);
  if (err) {
    gst_printerrln ("Error: %s", err->message);
    g_error_free (err);
    return NULL;
  }

  caps = gst_caps_new_simple ("application/x-rtp",
      "clock-rate", G_TYPE_INT, 90000,
      "encoding-name", G_TYPE_STRING, "H264",
      "payload", G_TYPE_INT, 96,
      "sprop-parameter-sets=", G_TYPE_STRING, "Z0LAIJZ0AoAt2AoEAAAu3AAK/IDRgALcgAC3HXvfB4RCNQ\\=\\=\\,aN48gA\\=\\=",
      NULL);

  src = gst_bin_get_by_name (GST_BIN (pipeline), "src");
  g_object_set (src, "caps", caps, "address", "127.0.0.1", "port", 51110,
      "buffer-size", 64 * 1024, NULL);
  gst_object_unref (src);

  gst_caps_unref (caps);

  sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
  g_object_set (sink, "sync", FALSE, NULL);
  gst_object_unref (sink);

  return pipeline;
}

static GstElement *
build_send ()
{
  GstCaps *caps;
  GstElement *pipeline, *src, *enc, *rtp, *sink, *filter;
  GError *err = NULL;

  pipeline = gst_parse_launch ("videotestsrc name=src ! capsfilter name=caps ! "
      "timeoverlay ! vaapipostproc name=vpp ! tee name=t ! "
      "vaapih264enc name=enc ! h264parse ! rtph264pay name=rtp ! "
      "udpsink name=sink t. ! vaapisink name=preview", &err);
  if (err) {
    gst_printerrln ("Error: %s", err->message);
    g_error_free (err);
    return NULL;
  }

  src = gst_bin_get_by_name (GST_BIN (pipeline), "src");
  g_object_set (src, "is-live", TRUE, "pattern", 0, NULL);
  gst_object_unref (src);

  caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, "I420",
      "width", G_TYPE_INT, 1920, "height", G_TYPE_INT, 1080,
      "framerate", GST_TYPE_FRACTION, 30, 1, NULL);

  filter = gst_bin_get_by_name (GST_BIN (pipeline), "caps");
  g_object_set (filter, "caps", caps, NULL);
  gst_object_unref (filter);

  gst_caps_unref (caps);

  enc = gst_bin_get_by_name (GST_BIN (pipeline), "enc");
  g_object_set (enc, "bitrate", 3 * 1000, "rate-control", 2, "quality-level", 7,
      "cabac", TRUE, "dct8x8", TRUE, "init-qp", 32, "min-qp", 21, "aud", TRUE,
      NULL);
  gst_object_unref (enc);

  rtp = gst_bin_get_by_name (GST_BIN (pipeline), "rtp");
  g_object_set (rtp, "config-interval", -1, "pt", 96, "mtu", 1250, NULL);
  gst_object_unref (rtp);

  sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
  g_object_set (sink, "host", "127.0.0.1", "port", 51110, "bind-port", 50110, NULL);
  gst_object_unref (sink);

  sink = gst_bin_get_by_name (GST_BIN (pipeline), "preview");
  g_object_set (sink, "sync", FALSE, NULL);
  gst_object_unref (sink);

  return pipeline;
}

static gboolean
change_resolution (gpointer data)
{
  static gint count = 0;
  GstElement *vpp, *send = data;
  const gint resolution_table[][2] = {
    {320, 240}, {640, 480}, {1920, 1080}, {1280, 720}, {960, 540}, {800, 600},
    {848, 480}, {800, 448}, {640, 360}, {480, 270}, {424, 240}, {352, 288},
    {320, 180}, {176, 144}, {160, 120}
  };
  gint w, h;

  w = resolution_table[count % 15][0];
  h = resolution_table[count % 15][1];
  gst_println ("setting resolution [%dx%d]", w, h);

#if 1
  vpp = gst_bin_get_by_name (GST_BIN (send), "vpp");
  g_assert (vpp);
  g_object_set (vpp, "width", w, "height", h, NULL);
  gst_object_unref (vpp);
#else
  vpp = gst_bin_get_by_name (GST_BIN (send), "caps");
  g_assert (vpp);
  {
    GstCaps *caps =  gst_caps_new_simple ("video/x-raw",
      "format", G_TYPE_STRING, "I420", "width", G_TYPE_INT, w,
      "height", G_TYPE_INT, h, "framerate", GST_TYPE_FRACTION, 30, 1, NULL);
    g_object_set (vpp, "caps", caps, NULL);
    gst_caps_unref (caps);
  }
  gst_object_unref (vpp);
#endif

  count++;

  return G_SOURCE_CONTINUE;
}

gint
main (gint argc, gchar *argv[])
{
  GstStateChangeReturn ret;
  GstElement *recv, *send;
  GMainLoop *loop;
  guint res_id, recv_id, send_id;

  g_assert (gst_init_check (&argc, &argv, NULL));

  recv = build_recv ();
  send = build_send ();

  g_assert (recv && send);

  ret = gst_element_set_state (recv, GST_STATE_PLAYING);
  g_assert (ret != GST_STATE_CHANGE_FAILURE);
  ret = gst_element_set_state (send, GST_STATE_PLAYING);
  g_assert (ret != GST_STATE_CHANGE_FAILURE);

  loop = g_main_loop_new (NULL, FALSE);

  recv_id = gst_bus_add_watch (GST_ELEMENT_BUS (recv), bus_msg, loop);
  send_id = gst_bus_add_watch (GST_ELEMENT_BUS (send), bus_msg, loop);

  res_id = g_timeout_add_seconds (5, change_resolution, send);

  g_main_loop_run (loop);

  ret = gst_element_set_state (recv, GST_STATE_NULL);
  g_assert (ret != GST_STATE_CHANGE_FAILURE);
  ret = gst_element_set_state (send, GST_STATE_NULL);
  g_assert (ret != GST_STATE_CHANGE_FAILURE);

  g_source_remove (res_id);
  g_source_remove (recv_id);
  g_source_remove (send_id);

  gst_object_unref (recv);
  gst_object_unref (send);

  g_main_loop_unref (loop);

  return 0;
}
