#include <gst/gst.h>

typedef struct {
  GMutex* lock;
  GCond* cond;
  gint percent;
  gboolean async_done;
} PlayerState;

GST_DEBUG_CATEGORY_STATIC (bug_744399);
#define GST_CAT_DEFAULT bug_744399

static GstBusSyncReply sync_handler(GstBus* bus, GstMessage* msg, gpointer user_data)
{
  PlayerState* state = (PlayerState*)user_data;
  if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_BUFFERING) {
    gint percent = 0;
    guint64 time = 0;
    gst_message_parse_buffering(msg, &percent);
    g_object_get(GST_MESSAGE_SRC(msg), "current-level-time", &time, NULL);
    g_mutex_lock(state->lock);
    GST_LOG("Buffering message, %" GST_TIME_FORMAT " buffered (roughly %d%%)", GST_TIME_ARGS(time), percent);
    if (percent == 100 && time == 0) {
      GST_ERROR("FAILURE 1! The queue reported 100 percent buffering, though no time can be reported. 100 percent is caused probably by EOS");
      if (state->async_done) {
        GST_ERROR("FAILURE 2! although the queue did not output valid buffers that fall in the current segment boundaries (if it would, then it would have a valid srctime and sinktime, and would not report current-level-time=0), the sink has prerolled. This is caused by the decoder clipping buffers to segment.");
      } /* else -> you've modified the pipeline */
    }
    state->percent = percent;
    g_cond_broadcast(state->cond);
    g_mutex_unlock(state->lock);
  } else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ASYNC_DONE) {
    g_mutex_lock(state->lock);
    GST_LOG("Prerolled!");
    state->async_done = 1;
    g_cond_broadcast(state->cond);
    g_mutex_unlock(state->lock);
  }

  gst_message_unref(msg);
  return GST_BUS_DROP;
}

int main(int a, char** b) {
  PlayerState state;
  GstBus* bus = NULL;
  GstElement* pipeline = NULL;

  gst_init(&a, &b);
  GST_DEBUG_CATEGORY_INIT (bug_744399, "bug_744399", 0, "Debug cat for https://bugzilla.gnome.org/show_bug.cgi?id=744399");

  state.lock = g_mutex_new();
  state.cond = g_cond_new();
  state.percent = 0;
  state.async_done = 0;

  /* Why is pipeline constructed this way:
   * 1) the first h264parse + capsfilter ensure that we operate on the raw h264 stream,
   *    with stream-format=byte-stream. If you remove the first h264parse and capsfilter from the
   *    picture, then you will be able to reproduce FAILURE_1, but not FAILURE_2.
   * 2) capssetter with framerate=0/1 is required for reproducing the bug on the byte-stream
   *    h264 stream. If you remove the capssetter, then the decoder will know framerate, and will
   *    not clip buffers to segment incorrectly.
   * 3) The queue has only time limit. Till the queue outputs first valid buffer (in-segment),
   *    it will think it has 0 buffered, and will literally eat all your stream in no time.
   * */
  pipeline = gst_parse_launch(
    "filesrc "
        "location=./bbb-small.mp4 "
    "! qtdemux "
    "! h264parse "
    "! video/x-h264,stream-format=\"byte-stream\",alignment=\"au\" "
    "! queue2 "
        "max-size-buffers=0 "
        "max-size-bytes=0 "
        "use-buffering=true "
        "use-rate-estimate=false "
        "low-percent=1 "
        "high-percent=50 "
        "max-size-time=5000000000 "
    "! capssetter "
        "caps=\"video/x-h264, framerate=(fraction)0/1\" "
    "! h264parse "
    "! avdec_h264 "
    "! autovideosink",
    NULL);
  g_assert(pipeline);
  g_object_ref_sink(pipeline);

  bus = gst_pipeline_get_bus(GST_PIPELINE_CAST(pipeline));
  g_assert(bus);

  gst_bus_set_sync_handler(bus, sync_handler, &state, NULL);

  GST_INFO("Setting initial pipeline state to PAUSED...");
  gst_element_set_state(pipeline, GST_STATE_PAUSED);
  if (GST_STATE_CHANGE_SUCCESS != gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE)) {
    GST_ERROR("Failed to set PAUSED state");
    goto cleanup;
  }
  GST_INFO("Pipeline paused");

  g_mutex_lock(state.lock);
  state.async_done = 0;
  g_mutex_unlock(state.lock);

  GST_INFO("Seeking the pipeline");
  if (!gst_element_seek(pipeline,
                  1.0,
                  GST_FORMAT_TIME,
                  GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
                  GST_SEEK_TYPE_SET,
                  GST_SECOND*2,
                  GST_SEEK_TYPE_NONE,
                  GST_CLOCK_TIME_NONE)) {
    GST_ERROR("Failed to seek the pipeline");
    goto cleanup;
  }

  int seek_finished = 0;
  g_mutex_lock(state.lock);
  do {
    seek_finished = state.async_done;
    if (!seek_finished) {
      GST_INFO("Awaiting seek completion");
      g_cond_wait(state.cond, state.lock);
    }
  } while (!seek_finished);
  g_mutex_unlock(state.lock);

  GST_INFO("Seek complete now! Awaiting 50 percent buffered for unpausing");

  int buffering_finished = 0;
  g_mutex_lock(state.lock);
  do {
    GST_INFO("Buffered %d%%...", state.percent);
    if (state.percent >= 50) {
      GST_INFO("Unpausing pipeline!");
      buffering_finished = 1;
    } else {
      g_cond_wait(state.cond, state.lock);
    }
  } while (!buffering_finished);
  g_mutex_unlock(state.lock);

  GST_INFO("Setting PLAYING state");
  gst_element_set_state(pipeline, GST_STATE_PLAYING);
  if (GST_STATE_CHANGE_SUCCESS != gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE)) {
    GST_ERROR("Failed to set PLAYING state");
    goto cleanup;
  }

  GST_INFO("Test finished now, congratulations!");
  GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "bug_744399_pipeline");

cleanup:
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(bus);
  gst_object_unref(pipeline);

  g_mutex_free(state.lock);
  g_cond_free(state.cond);
  return 0;
}
