#include <gst/gst.h>

#undef PAD_PUSH


static GstPad *
get_src_pad(GstElement    * element)
{
    const GList   * pads;

    g_return_val_if_fail(element != NULL, NULL);

    for (pads = gst_element_get_pad_list(element);
         pads;
         pads = g_list_next(pads)) {
        GstPad    * pad = pads->data;

        if (GST_PAD_IS_SRC(pad)) {
            return pad;
        }
    }

    return 0;
}


static gboolean
seek_src_pad(GstElement   * element,
             GstSeekType    seekType,
             gint64         seekTime)
{
    GstPad    * pad;
    GstEvent  * seek;

    if ((pad  = get_src_pad(element))) {
        seek = gst_event_new_seek(seekType, seekTime);
#ifdef PAD_PUSH
        gst_pad_push(pad, GST_DATA(seek));
        return TRUE;
#else
        return gst_pad_send_event(pad, seek);
#endif
    }

    g_print("element doesn't have a src pad\n");
    return FALSE;
}


int
main(int        argc,
     char    ** argv)
{
    GstElement    * pipeline;
    GstElement    * source;
    GstElement    * decoder;
    GstElement    * sink;
    GstSeekType     seekType;
    GstFormat       format;
    gint64          timePlayed;
    gint64          timeAfterSeek;
    gboolean        ret;
    int             i;
    gint64          seekTo;
    gint64          playTo;

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

    /* check input arguments */
    if (argc != 3 && argc != 4) {
        g_print("Usage: %s <mp3 filename> <seekToSecs> [<playToSecs>]\n",
                argv[0]);
        return -1;
    }

    if (sscanf(argv[2], "%d", &i) != 1) {
        g_print("can't parse seekTo value: %s\n", argv[2]);
        return -1;
    }
    seekTo = ((gint64)i) * GST_SECOND;

    if (argc == 4) {
        if (sscanf(argv[3], "%d", &i) != 1) {
            g_print("can't parse playTo value: %s\n", argv[3]);
            return -1;
        } else {
            playTo = ((gint64)i) * GST_SECOND;
        }
    } else {
        playTo = -1LL;
    }

    /* create elements */
    seekType = (GstSeekType) (GST_FORMAT_TIME |
                              GST_SEEK_METHOD_SET |
                              GST_SEEK_FLAG_FLUSH);

    pipeline = gst_pipeline_new("audio-player");
    source   = gst_element_factory_make("filesrc", "source");
    decoder  = gst_element_factory_make("mad", "decoder");
    sink     = gst_element_factory_make("alsasink", "alsa-output");

    g_object_set(G_OBJECT(source), "location", argv[1], NULL);

    gst_element_link_many(source, decoder, sink, NULL);
    gst_bin_add_many(GST_BIN(pipeline), source, decoder, sink, NULL);

    gst_element_set_state(sink, GST_STATE_READY);
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* iterate on the piplinee until the played time becomes more than 0
     * as the seek even will only be taken into consideration after that
     * by gstreamer */
    for (timePlayed = 0;
         timePlayed == 0 && gst_bin_iterate(GST_BIN(pipeline)); ) {

        format = GST_FORMAT_TIME;
        gst_element_query(sink, GST_QUERY_POSITION, &format, &timePlayed);
    }

    // so, seek now
    timeAfterSeek = -1LL;
    if (!seek_src_pad(decoder, seekType, seekTo)) {
        g_print("seek returned FALSE\n");
    }

    // iterate until playTo is reached
    while (gst_bin_iterate(GST_BIN(pipeline))) {
        format = GST_FORMAT_TIME;
        gst_element_query(sink, GST_QUERY_POSITION, &format, &timePlayed);

        if (timeAfterSeek == -1LL && timePlayed > seekTo) {
            timeAfterSeek = timePlayed;
        }

        if (playTo > 0 && timePlayed > playTo) {
            break;
        }
    }

    g_print("tried to seek to:    %16" G_GINT64_FORMAT "\n", seekTo);
    g_print("time after seek:     %16" G_GINT64_FORMAT "\n", timeAfterSeek);
    g_print("seek error:          %16" G_GINT64_FORMAT "\n",
            timeAfterSeek - seekTo);
    g_print("played until:        %16" G_GINT64_FORMAT "\n", timePlayed);
    g_print("total time played:   %16" G_GINT64_FORMAT "\n",
            timePlayed - timeAfterSeek);
    if (playTo > 0) {
        g_print("intended playtime:   %16" G_GINT64_FORMAT "\n",
                playTo - seekTo);
        g_print("playtime error:      %16" G_GINT64_FORMAT "\n",
                (timePlayed - timeAfterSeek) - (playTo - seekTo));
    } else {
        gint64  total;

        format = GST_FORMAT_TIME;
        gst_element_query(sink, GST_QUERY_TOTAL, &format, &total);

        g_print("intended playtime:   %16" G_GINT64_FORMAT "\n",
                total - seekTo);
        g_print("playtime error:      %16" G_GINT64_FORMAT "\n",
                (timePlayed - timeAfterSeek) - (total - seekTo));
        g_print("file total playtime: %16" G_GINT64_FORMAT "\n",
                total);
    }

    /* clean up nicely */
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT (pipeline));

    return 0;   
}

