#include <stdio.h>
#include <string.h>
#include <gst/gst.h>

typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *sink;

  GMainLoop *loop;

  gboolean playing;  /* Playing or Paused */
  gdouble rate;      /* Current playback rate (can be negative) */

} CustomData;

static void
last_message_received (GObject * segment)
{
  gchar *last_message;

  g_object_get (segment, "last_message", &last_message, NULL);
  g_print ("last-message: %s\n", last_message);
  g_free (last_message);
}

static gboolean set_inital_pipe(CustomData * data) {
    data->pipeline = gst_parse_launch("filesrc location=/path/to/video/0.mkv ! matroskademux ! avdec_h264 name=vsrc2 ! imagefreeze ! identity ! videoconvert ! autovideosink name=snk ", NULL);

    gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
}

static void check_step_result(gboolean res) {
  if (res) {
    g_print("Made step \n");
  } else {
    g_print("Step failed \n");
  }
}

static void check_video_sink(CustomData *data){
  if (data->sink == NULL) {
    data->sink = gst_bin_get_by_name(data->pipeline, "snk");
  }
}

static void seeking(gint64 pos, CustomData *data) {
    GstEvent *seek_event;

    if (data->rate > 0) { // GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE for bouth
        seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH),
                                         GST_SEEK_TYPE_SET, pos, GST_SEEK_TYPE_NONE, 0);
    } else {
        seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH),
                                         GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, pos);
    }

    check_video_sink(data);

    /* Send the event */
    gst_element_send_event (data->sink, seek_event);
}

static void absolute_seek(CustomData *data, gint64 portion) {
  GstEvent *seek_event;
  gint64 position;
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

  gint64 new_pos = position + portion;

  g_print("Current position %" GST_TIME_FORMAT  ", new position %" GST_TIME_FORMAT "\n",
            GST_TIME_ARGS (position),  GST_TIME_ARGS (new_pos));

  seeking(new_pos, data);
}

/* Send seek event to change rate */
static void send_seek_event (CustomData *data) {
  gint64 position;

  /* Obtain the current position, needed for the seek event */
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

  g_print("Position %d \n", position);
  seeking(position, data);

  g_print ("Current rate: %g\n", data->rate);
}

/* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
  case 'p': //play / pause
    data->playing = !data->playing;
    gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
    g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
    break;
  case 'a': // absolute seek
    g_print("Absolute seek \n");
    if (g_ascii_isupper (str[0])) {
        absolute_seek(data, 1000000000);
    } else {
        absolute_seek(data, -1000000000);
    }
    break;
  case 'q':
    g_main_loop_quit (data->loop);
    break;
  default:
    break;
  }

  g_free (str);

  return TRUE;
}

int main(int argc, char *argv[]) {
  CustomData data;
  GstStateChangeReturn ret;
  GIOChannel *io_stdin;

  /* Initialize GStreamer */
  gst_init (&argc, &argv);

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));

  /* Print usage map */
  g_print (
    "USAGE: Choose one of the following options, then press enter:\n"
    " 'a' backward for 1s, 'A' forward for 1s \n"
    " 'p' to toggle between PAUSE and PLAY\n"
    " 'S' to increase playback speed, 's' to decrease playback speed\n"
    " 't' to move to next frame (in the current direction, better in PAUSE, will use GST_FORMAT_TIME)\n"
    " 'b' switching video \n"
    " 'q' to quit\n");

  set_inital_pipe(&data);

  /* Managing user input */
  io_stdin = g_io_channel_unix_new (fileno (stdin));
  g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);

  /* Start playing */
  ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }
  data.playing = TRUE;
  data.rate = 1.0;

  data.loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.loop);

  /* Free resources */
  g_main_loop_unref (data.loop);
  g_io_channel_unref (io_stdin);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  if (data.sink != NULL)
    gst_object_unref (data.sink);
  gst_object_unref (data.pipeline);
  return 0;
}

