/*
 * GStreamer hlsmux_block_testcase.c
 * Copyright 2015 Andreas Frisch <fraxinas@opendreambox.org>
 *
 * This program is licensed under the Creative Commons
 * Attribution-NonCommercial-ShareAlike 3.0 Unported
 * License. To view a copy of this license, visit
 * http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
 * Creative Commons,559 Nathan Abbott Way,Stanford,California 94305,USA.
 *
 * This program is NOT free software. It is open source, you are allowed
 * to modify it (if you keep the license), but it may not be commercially
 * distributed other than under the conditions noted above.
 */

// gcc -Wall -g `pkg-config gstreamer-1.0 --cflags --libs` hlsblock_testcase.c -o hlsblock_testcase

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

GST_DEBUG_CATEGORY (hlsblocktest_debug);
#define GST_CAT_DEFAULT hlsblocktest_debug

#define HLS_FRAGMENT_DURATION 2

typedef struct {
	GMainLoop *loop;
	GstElement *pipeline;
	GstElement *asrc, *aflt, *aenc, *aparse;
	GstElement *tsmux, *tstee;
	GstElement *aq; 
	GstElement *hlsq, *hlssink;
	GstElement *fakeq, *fakesink;
	GMutex rtsp_mutex;
} App;

gboolean create_source_pipeline(App *app)
{
	GST_INFO_OBJECT(app, "create_source_pipeline");
	app->pipeline = gst_pipeline_new ("hlsmux_block_testcase");

	app->asrc = gst_element_factory_make ("audiotestsrc", NULL);
	
	app->aflt = gst_element_factory_make ("capsfilter", NULL);
	GstCaps *caps = gst_caps_new_simple ("audio/x-raw", "channels", G_TYPE_INT, 2, "rate", G_TYPE_INT, 48000, NULL);
	g_object_set (G_OBJECT (app->aflt), "caps", caps, NULL);
	gst_caps_unref (caps);
	
	app->aenc = gst_element_factory_make ("avenc_aac", NULL);
	g_object_set (G_OBJECT (app->aenc), "compliance", -2, NULL);
	
	app->aparse = gst_element_factory_make ("aacparse", NULL);
	app->aq = gst_element_factory_make ("queue", "aqueue");

	app->tsmux = gst_element_factory_make ("mpegtsmux", NULL);
        app->tstee = gst_element_factory_make ("tee", "tstee");

	if (!(app->asrc && app->aflt && app->aenc && app->aparse && app->aq && app->tsmux && app->tstee))
	{
		g_error ("Failed to create source pipeline element(s)");
	}

	gst_bin_add_many (GST_BIN (app->pipeline), app->asrc, app->aflt, app->aenc, app->aparse, app->aq, app->tsmux, NULL);
	gst_bin_add (GST_BIN (app->pipeline), app->tstee);
	gst_element_link_many (app->asrc, app->aflt, app->aenc, app->aparse, app->aq, NULL);

	GstPad *sinkpad, *srcpad;
	GstPadLinkReturn ret;

	srcpad = gst_element_get_static_pad (app->aq, "src");
	sinkpad = gst_element_get_compatible_pad (app->tsmux, srcpad, NULL);
	ret = gst_pad_link (srcpad, sinkpad);
	if (ret != GST_PAD_LINK_OK)
		g_error ("couldn't link %" GST_PTR_FORMAT " ! %" GST_PTR_FORMAT "", srcpad, sinkpad);
	gst_object_unref (srcpad);
	gst_object_unref (sinkpad);

	if (!gst_element_link (app->tsmux, app->tstee))
		g_error ("couldn't link %" GST_PTR_FORMAT " ! %" GST_PTR_FORMAT "", app->tsmux, app->tstee);

	return FALSE;
}

static gboolean enable_fakesink (gpointer user_data)
{
	App *app = user_data;
	GST_INFO_OBJECT(app, "enable_fakesink");
	app->fakeq = gst_element_factory_make ("queue", NULL);
	app->fakesink = gst_element_factory_make ("fakesink", NULL);
	
	g_object_set (G_OBJECT (app->fakeq), "leaky", 2, "max-size-buffers", 400, "max-size-bytes", 0, "max-size-time", G_GINT64_CONSTANT(0), NULL);
	gst_bin_add_many (GST_BIN(app->pipeline), app->fakeq, app->fakesink, NULL);

	GstPadLinkReturn ret;
	GstPad *srcpad, *sinkpad;
	srcpad = gst_element_get_request_pad (app->tstee, "src_%u");
	sinkpad = gst_element_get_static_pad (app->fakeq, "sink");
	ret = gst_pad_link (srcpad, sinkpad);
	gst_object_unref (srcpad);
	gst_object_unref (sinkpad);
	if (ret != GST_PAD_LINK_OK)
	{
		GST_ERROR_OBJECT (app, "couldn't link %" GST_PTR_FORMAT " ! %" GST_PTR_FORMAT "", srcpad, sinkpad);
		return FALSE;
	}
	
	if (!gst_element_link (app->fakeq, app->fakesink)) {
		GST_ERROR_OBJECT (app, "couldn't link %" GST_PTR_FORMAT " ! %" GST_PTR_FORMAT "", app->fakeq, app->fakesink);
		return FALSE;
	}
	
	GstStateChangeReturn sret = gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
	GST_DEBUG_OBJECT(app, "gst_element_set_state (app->pipeline, GST_STATE_PLAYING) = %i", sret);
	GstState state;
	gst_element_get_state (GST_ELEMENT(app->pipeline), &state, NULL, 3*GST_SECOND);
	GST_INFO_OBJECT(app, "pipeline state=%s", gst_element_state_get_name (state));
	GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(app->pipeline),GST_DEBUG_GRAPH_SHOW_ALL,"enable_fakesink");
	
	return FALSE;
}

static gboolean enable_hls_server (gpointer user_data)
{
	App *app = user_data;
	GST_INFO_OBJECT(app, "enable_hls_server");
	if (!app->pipeline)
	{
		GST_ERROR_OBJECT (app, "failed to enable hls server because source pipeline is NULL!");
		return FALSE;
	}
	app->hlsq = gst_element_factory_make ("queue", "hlsqueue");
	app->hlssink = gst_element_factory_make ("hlssink", "hlssink");
	if (!(app->hlssink && app->hlsq))
	{
		g_error ("Failed to create HLS pipeline element(s):%s%s", app->hlssink?"":" hlssink", app->hlsq?"":" queue");
		return FALSE;
	}

	g_object_set (G_OBJECT (app->hlssink), "target-duration", HLS_FRAGMENT_DURATION, NULL);
	g_object_set (G_OBJECT (app->hlsq), "leaky", 0, "max-size-buffers", 0, "max-size-bytes", 0, "max-size-time", G_GINT64_CONSTANT(5)*GST_SECOND, NULL);

	gst_bin_add_many (GST_BIN (app->pipeline), app->hlsq, app->hlssink,  NULL);
	gst_element_link (app->hlsq, app->hlssink);

	GstPad *teepad, *sinkpad;
	GstPadLinkReturn ret;
	teepad = gst_element_get_request_pad (app->tstee, "src_%u");
	sinkpad = gst_element_get_static_pad (app->hlsq, "sink");
	ret = gst_pad_link (teepad, sinkpad);
	if (ret != GST_PAD_LINK_OK)
	{
		GST_ERROR_OBJECT (app, "couldn't link %" GST_PTR_FORMAT " ! %" GST_PTR_FORMAT "", teepad, sinkpad);
		return FALSE;
	}
	gst_object_unref (teepad);
	gst_object_unref (sinkpad);

	GstStateChangeReturn sret = gst_element_set_state (app->pipeline, GST_STATE_PLAYING);
	GST_DEBUG_OBJECT(app, "gst_element_set_state (app->pipeline, GST_STATE_PLAYING) = %i", sret);
	GstState state;
	gst_element_get_state (GST_ELEMENT(app->pipeline), &state, NULL, 3*GST_SECOND);
	GST_INFO_OBJECT(app, "pipeline state=%s", gst_element_state_get_name (state));
	GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(app->pipeline),GST_DEBUG_GRAPH_SHOW_ALL,"enable_hls_server");

	return FALSE;
}


int main (int argc, char *argv[])
{
	App app;

	gst_init (0, NULL);
	
	if (argc < 2) {
		g_print ("usage: %s n]\n"
		"\tn=0: hlssink goes PLAYING first\n"
		"\tn>0: fakesink goes PLAYING first, hlssink after n ms\n", argv[0]);
		return -1;
	}

	GST_DEBUG_CATEGORY_INIT (hlsblocktest_debug, "hlsblocktest",
			GST_DEBUG_BOLD | GST_DEBUG_FG_YELLOW | GST_DEBUG_BG_BLUE,
			"hlssink playing block");
		
	memset (&app, 0, sizeof(app));
	g_mutex_init (&app.rtsp_mutex);

	create_source_pipeline(&app);
	
	app.loop = g_main_loop_new (NULL, FALSE);

	int n = atoi(argv[1]);
	if (n == 0)
	{
		g_print ("hlssink goes into PLAYING first");
		g_timeout_add (1, enable_hls_server, &app);
	}
	else
	{
		g_print ("fakesink goes into PLAYING first, hlssink will go into PLAYING after %i ms", n);
		g_timeout_add (1, enable_fakesink, &app);
		g_timeout_add (n, enable_hls_server, &app);
	}

	g_main_loop_run (app.loop);

	g_main_loop_unref (app.loop);
	g_mutex_clear (&app.rtsp_mutex);

	return 0;
}
