#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>

#define CONTAINER "video/mpegts,systemstream=true,packetsize=188"
#define AUDIO "audio/mpeg,mpegversion=1,layer=2"
#define VIDEO "video/mpeg,mpegversion=2,systemstream=false"
#define VIDEO_RESTRICTION "video/x-raw-yuv,framerate=(fraction)25/1,width=720,height=576"

typedef struct {
    GstElement *encoder;
    GMainLoop *loop;
    GstElement *pipeline;
    gboolean linked_something;
} MyData;

static void
on_no_more_pads (GstElement *element, gpointer user_data)
{
    MyData *data = (MyData *)user_data;

    if (!data->linked_something) {
        g_print ("Couldn't link decoder and encoder. Bailing out!\n");

        g_main_loop_quit (data->loop);
    }
}

static void
on_pad_added (GstElement *element, GstPad *new_pad, gpointer user_data)
{
    GstPad *sinkpad;
    MyData *data = (MyData *)user_data;

    sinkpad = gst_element_get_compatible_pad (data->encoder, new_pad, NULL);
    if (sinkpad == NULL) {
        GstCaps *caps = gst_pad_get_caps_reffed (new_pad);
        g_signal_emit_by_name (data->encoder, "request-pad", caps, &sinkpad);
        gst_caps_unref (caps);
    }

    if (sinkpad == NULL) {
        g_print ("Couldn't find compatible encodebin pad for pad '%s'. Ignoring...\n",
                 gst_pad_get_name (new_pad));

        return;
    }

    if (gst_pad_link (new_pad, sinkpad) != GST_PAD_LINK_OK) {
        g_print ("Failed to link pad '%s' to pad '%s'\n",
                 gst_pad_get_name (new_pad),
                 gst_pad_get_name (sinkpad));
        return;
    }

    data->linked_something = TRUE;
}

static gboolean
on_autoplug_continue (GstElement *element, GstPad *new_pad, GstCaps *_caps, gpointer user_data)
{
    GstPad *sinkpad;
    MyData *data = (MyData *)user_data;
    GstCaps *caps;

    caps = gst_pad_get_caps_reffed (new_pad);
    g_signal_emit_by_name (data->encoder, "request-pad", caps, &sinkpad);
    gst_caps_unref (caps);

    if (sinkpad == NULL) {
        return TRUE;
    }

    return FALSE;
}

static GstElement *
setup_encodebin ()
{
    GstElement *encodebin;
    GstCaps *audio, *video, *restriction, *container;
    GstEncodingAudioProfile *audio_profile;
    GstEncodingVideoProfile *video_profile;
    GstEncodingContainerProfile *container_profile;

    encodebin = gst_element_factory_make ("encodebin", NULL);

    container = gst_caps_from_string (CONTAINER);
    audio = gst_caps_from_string (AUDIO);
    video = gst_caps_from_string (VIDEO);
    restriction = gst_caps_from_string (VIDEO_RESTRICTION);

    audio_profile = gst_encoding_audio_profile_new (audio, NULL, NULL, 1);
    video_profile = gst_encoding_video_profile_new (video, NULL, restriction, 1);
    container_profile = gst_encoding_container_profile_new (NULL, NULL, container, NULL);
    gst_encoding_container_profile_add_profile (container_profile, (GstEncodingProfile*) audio_profile);
    gst_encoding_container_profile_add_profile (container_profile, (GstEncodingProfile*) video_profile);

    g_object_set (encodebin, "profile", container_profile, NULL);

    return encodebin;
}

static gboolean
quit_loop (GMainLoop *loop) {
    g_main_loop_quit (loop);

    return FALSE;
}

static gboolean
on_bus (GstBus *bus, GstMessage *message, gpointer user_data)
{
    MyData *data = (MyData *)user_data;

    if (message->type == GST_MESSAGE_EOS) {
        g_idle_add ((GSourceFunc) quit_loop, data->loop);

        return FALSE;
    }

    if (message->type == GST_MESSAGE_ERROR ||
        message->type == GST_MESSAGE_WARNING) {
        GError *error = NULL;
        gchar *debug;

        gst_message_parse_error (message, &error, &debug);
        g_print ("Pipeline failed: %s: %s", error->message, debug);
        g_error_free (error);
        g_free (debug);

        g_idle_add ((GSourceFunc) quit_loop, data->loop);
        return FALSE;
    }
}

int main (int argc, char *argv[])
{
    GstElement *src, *decodebin, *encodebin, *sink, *pipeline;
    char *output_file;
    MyData data;
    GstBus *bus;

    g_type_init ();

    gst_init (&argc, &argv);

    if (argc != 2) {
        return 1;
    }

    output_file = g_strconcat (argv[1], ".mpeg", NULL);
    src = gst_element_factory_make ("filesrc", NULL);
    decodebin = gst_element_factory_make ("decodebin2", NULL);
    g_signal_connect (decodebin, "pad-added", G_CALLBACK (on_pad_added), &data);
    g_signal_connect (decodebin, "autoplug-continue", G_CALLBACK (on_autoplug_continue), &data);
    g_signal_connect (decodebin, "no-more-pads", G_CALLBACK (on_no_more_pads), &data);

    encodebin = setup_encodebin ();
    data.encoder = encodebin;
    sink = gst_element_factory_make ("filesink", NULL);
    g_object_set (src, "location", argv[1], NULL);
    g_object_set (sink, "location", output_file, NULL);
    g_free (output_file);

    pipeline = gst_pipeline_new ("transcoder");
    data.pipeline = pipeline;
    gst_bin_add_many (GST_BIN (pipeline), src, decodebin, encodebin, sink, NULL);
    gst_element_link (src, decodebin);
    gst_element_link (encodebin, sink);

    data.loop = g_main_loop_new (NULL, FALSE);

    bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
    gst_bus_add_watch (bus, on_bus, &data);
    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    g_main_loop_run (data.loop);

    return 0;
}
