#include <stdio.h>
#include <glib.h>
#include <gst/gst.h>
#include <unistd.h>
#include <assert.h>

/* Globals */
GstElement *pipeline;
GstElement *audioSrc;
GstElement *audioSrcConvert;
GstElement *audioSrcResample;
GstElement *audioSink;
GstElement *adder;
GstCaps *caps;

/* Start callbacks for decodebin/decodebin2 */
void gstNewDecodedPad(GstElement *object, GstPad *pad, gboolean arg1, gpointer data)
{
    //gchar *padname = gst_pad_get_name(pad);
    //printf("gstNewDecodedPad: GstElement: %p, GstPad: %p, bool: %d, data: %p, NAME: %s PADNAME: %s\n",
    //       object, pad, arg1, data, GST_ELEMENT_NAME(object), padname);
    //g_free(padname);
    printf("gstNewDecodedPad: GstElement: %p, GstPad: %p, bool: %d, data: %p\n",
           object, pad, arg1, data);

    GstElement *playfileBin = (GstElement*)gst_element_get_parent(object);
    GstElement *audioConvert = gst_bin_get_by_name(GST_BIN(playfileBin), "playfile_audioconvert");
    gst_element_link(object, audioConvert);
    gst_object_unref(audioConvert);
    gst_object_unref(playfileBin);
}

void gstRemovedDecodedPad(GstElement *object, GstPad *pad, gpointer data)
{
    printf("gstRemovedDecodedPad: GstElement: %p, GstPad: %p, data: %p\n",
           object, pad, data);
}

void gstUnknownType(GstElement *object, GstPad *pad, GstCaps *caps, gpointer data)
{
    printf("gstUnknownType: GstElement: %p, GstPad: %p, GstCaps: %p, data: %p\n",
           object, pad, caps, data);
}

/* Decodebin2 only */
gboolean gstAutoplugContinue(GstElement *object, GstCaps *caps, gpointer data)
{
    printf("gstAutoplugContinue: GstElement: %p, GstCaps: %p, data: %p\n",
           object, caps, data);
    return TRUE;
}

/* Decodebin2 only */
gboolean gstAutoplugSort(GstElement *object, GstCaps *caps, gpointer arg1, gpointer data)
{
    printf("gstAutoplugSort: GstElement: %p, GstCaps: %p, pointerarg: %p, data: %p\n",
           object, caps, arg1, data);
    return TRUE;
}
/* End callbacks for decodebin2 */

void delayProgress(int secs)
{
    for (int i = 0; i < secs; ++i)
    {
        printf(".");
        fflush(NULL);
        sleep(1);
    }
}

void playFile(char *filename)
{
    GstElement *playfileBin;
    GstElement *fileSrc;
    GstElement *decodeBin;
    GstElement *fileAudioConvert;
    GstElement *fileAudioResample;

    printf("Trying to play file: %s\n", filename);

    /* Remove a previous playfile bin if it exists */
    playfileBin = gst_bin_get_by_name(GST_BIN(pipeline), "playfile_bin");

    if (playfileBin)
    {
        /* Need to send an EOS event to the adder if the current file is
         * still playing, otherwise the adder stops working.
         */
        GstEvent *eos = gst_event_new_eos();
        GstPad *pad = gst_element_get_static_pad(playfileBin, "src");
        assert(pad != NULL);

        gst_element_set_state(playfileBin, GST_STATE_NULL);
        assert(gst_pad_push_event(pad, eos) == TRUE);
        gst_object_unref(pad);

        /* Decrease playfileBin refcount so it gets disposed. */
        assert(gst_bin_remove(GST_BIN(pipeline), playfileBin) == TRUE);
        while (GST_OBJECT_REFCOUNT(playfileBin) >= 1)
        {
            gst_object_unref(playfileBin);
        }
    }

    /* Build a new playfile bin */
    playfileBin = gst_bin_new("playfile_bin");
    fileSrc = gst_element_factory_make("filesrc", "playfile_filesrc");
    decodeBin = gst_element_factory_make("decodebin", "playfile_decodebin");
    fileAudioConvert = gst_element_factory_make("audioconvert", "playfile_audioconvert");
    fileAudioResample = gst_element_factory_make("audioresample", "playfile_audioresample");

    gst_bin_add_many(GST_BIN(playfileBin),
                     fileSrc,
                     decodeBin,
                     fileAudioConvert,
                     fileAudioResample,
                     NULL);

    assert(gst_element_add_pad(playfileBin,
           gst_ghost_pad_new("src", gst_element_get_pad(fileAudioResample, "src"))) == TRUE);

    g_signal_connect(G_OBJECT(decodeBin), "new-decoded-pad", G_CALLBACK(gstNewDecodedPad), NULL);
    g_signal_connect(G_OBJECT(decodeBin), "removed-decoded-pad", G_CALLBACK(gstRemovedDecodedPad), NULL);
    g_signal_connect(G_OBJECT(decodeBin), "unknown-type", G_CALLBACK(gstUnknownType), NULL);
    /* Uncomment for decodebin2
    g_signal_connect(G_OBJECT(decodeBin), "autoplug-continue", G_CALLBACK(gstAutoplugContinue), NULL);
    g_signal_connect(G_OBJECT(decodeBin), "autoplug-sort", G_CALLBACK(gstAutoplugSort), NULL);
    */

    assert(gst_element_link(fileSrc, decodeBin) == TRUE);
    assert(gst_element_link(fileAudioConvert, fileAudioResample) == TRUE);

    g_object_set(G_OBJECT(fileSrc),
                 "location", filename,
                 NULL);

    assert(gst_bin_add(GST_BIN(pipeline), playfileBin) == TRUE);

    assert(gst_element_link_filtered(playfileBin, adder, caps) == TRUE);

    gst_element_sync_state_with_parent(playfileBin);
}

int main(int argc, char *argv[])
{
    if (argc == 2)
    {
        printf("Audio file to play: %s\n", argv[1]);
    }
    else
    {
        printf("Missing audio file argument.\n");
        return -1;
    }

    gst_init(NULL, NULL);

    pipeline = gst_pipeline_new(NULL);

    audioSrc = gst_element_factory_make("alsasrc", NULL);
    audioSrcConvert = gst_element_factory_make("audioconvert", NULL);
    audioSrcResample = gst_element_factory_make("audioresample", NULL);
    audioSink = gst_element_factory_make("alsasink", NULL);
    adder = gst_element_factory_make("adder", NULL);

    gst_bin_add_many(GST_BIN(pipeline),
                     audioSrc,
                     audioSrcConvert,
                     audioSrcResample,
                     audioSink,
                     adder,
                     NULL);

    assert(gst_element_link(audioSrc, audioSrcConvert) == TRUE);
    assert(gst_element_link(audioSrcConvert, audioSrcResample) == TRUE);

    caps = gst_caps_new_simple ("audio/x-raw-int",
                                "rate", G_TYPE_INT, 8000,
                                "channels", G_TYPE_INT, 1,
                                "width", G_TYPE_INT, 16,
                                "depth", G_TYPE_INT, 16,
                                "signed", G_TYPE_BOOLEAN, TRUE,
                                NULL);

    assert(gst_element_link_filtered(audioSrcResample, adder, caps) == TRUE);

    assert(gst_element_link(adder, audioSink) == TRUE);

    g_object_set(G_OBJECT(audioSrc),
                 "is-live", TRUE,
                 "blocksize", 320,
                 NULL);
    g_object_set(G_OBJECT(audioSink),
                 "sync", FALSE,
                 NULL);

    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    delayProgress(5);

    while (1)
    {
        playFile(argv[1]);
        delayProgress(3);
    }

    printf("\ndone\n");
    return 0;
}
