/***************************************************************************
 *   Copyright (C) 2011 by Fraxinas                                        *
 *   fraxinas@opendreambox.org                                             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

// gcc -Wall -g `pkg-config gstreamer-0.10 --cflags --libs` bdremux.c -o bdremux

#include <gst/gst.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gst/pbutils/encoding-profile.h>


typedef struct _App App;

struct _App
{
  GstElement *pipeline;
  GstElement *filesrc;
  GstElement *tsdemux;
  GstElement *encodebin;
  GstElement *filesink;
  GstIndex *index;
  gulong buffer_handler_id;
  guint video_pid;
  guint audio_pid;

  GMainLoop *loop;
};

App s_app;

static void promote_element(const gchar *name)
{
    GstRegistry *registry = NULL;
    GstElementFactory *factory = NULL;

    registry = gst_registry_get_default ();
    if (G_UNLIKELY (!registry)) {
        g_print ("failed retrieving default registry\n");
        goto beach;
    }

    factory = gst_element_factory_find (name);
    if (G_UNLIKELY (!factory)) {
        g_print ("unable to locate the %s factory\n", name);
        goto beach;
    }

    gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE (factory), GST_RANK_PRIMARY);
    g_print ("set %s to rank %i\n", name, gst_plugin_feature_get_rank (GST_PLUGIN_FEATURE (factory)));
    
beach:
    if (factory && registry) {
        gst_registry_add_feature (registry, GST_PLUGIN_FEATURE (factory));
    }

    return;
}

static GstEncodingProfile *
create_bluray_m2ts_profile(void)
{
 GstEncodingContainerProfile *prof;
 GstCaps *caps;

 caps = gst_caps_from_string("video/mpegts, packetsize = (int) 188, systemstream = (boolean) true");
 prof = gst_encoding_container_profile_new("Bluraymux",
    "BDMV standard compliant M2TS Multiplexer",
    caps, NULL);
 gst_caps_unref (caps);

 caps = gst_caps_from_string("video/x-h264, parsed=True");
 gst_encoding_container_profile_add_profile(prof, (GstEncodingProfile *) gst_encoding_video_profile_new(caps, NULL, NULL, 0));
 gst_caps_unref (caps);

 caps = gst_caps_from_string("audio/x-ac3");
 gst_encoding_container_profile_add_profile(prof, (GstEncodingProfile *) gst_encoding_audio_profile_new(caps, NULL, NULL, 0));
 gst_caps_unref (caps);

 return (GstEncodingProfile*) prof;
}

static gboolean
bus_message (GstBus * bus, GstMessage * message, App * app)
{
  GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(app->pipeline),GST_DEBUG_GRAPH_SHOW_ALL,"bdremux_graph");
  GST_DEBUG ("got message %s",
      gst_message_type_get_name (GST_MESSAGE_TYPE (message)));

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_error (message, &gerror, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
      g_error_free (gerror);
      g_free (debug);

      g_main_loop_quit (app->loop);
      break;
    }
    case GST_MESSAGE_WARNING:
    {
      GError *gerror;
      gchar *debug;

      gst_message_parse_warning (message, &gerror, &debug);
      gst_object_default_error (GST_MESSAGE_SRC (message), gerror, debug);
      g_error_free (gerror);
      g_free (debug);

//       g_main_loop_quit (app->loop);
      break;
    }
    case GST_MESSAGE_EOS:
      g_message ("received EOS");
      g_main_loop_quit (app->loop);
      break;
    default:
      break;
  }
  return TRUE;
}

static void
entry_added (GstIndex * index, GstIndexEntry * entry)
{
  switch (entry->type) {
    case GST_INDEX_ENTRY_ID:
      g_print ("id %d describes writer %s\n", entry->id,
          GST_INDEX_ID_DESCRIPTION (entry));
      break;
    case GST_INDEX_ENTRY_FORMAT:
      g_print ("%d: registered format %d for %s\n", entry->id,
          GST_INDEX_FORMAT_FORMAT (entry), GST_INDEX_FORMAT_KEY (entry));
      break;
    case GST_INDEX_ENTRY_ASSOCIATION:
    {
      gint i;
      if ( entry->id == 1 && GST_INDEX_NASSOCS (entry) == 2 )
      {
        g_print ("entrypoint: %" G_GINT64_FORMAT " ", GST_INDEX_ASSOC_VALUE (entry, 0));
	g_print ("%" G_GINT64_FORMAT "\n", GST_INDEX_ASSOC_VALUE (entry, 1));
      }
      else
      {
	g_print ("GST_INDEX_ENTRY_ASSOCIATION %p, %d: %08x ", entry, entry->id,
	    GST_INDEX_ASSOC_FLAGS (entry));
	for (i = 0; i < GST_INDEX_NASSOCS (entry); i++) {
	  g_print ("%d %" G_GINT64_FORMAT " ", GST_INDEX_ASSOC_FORMAT (entry, i),
	      GST_INDEX_ASSOC_VALUE (entry, i));
	}
	g_print ("\n");
      }
      break;
    }
    default:
      break;
  }
}

static void gstDemuxPadAdded (GstElement *element, GstPad * pad, App *app)
{
  GstPad *sinkpad;
  gchar *sink_pad_name;
  gchar *src_pad_name = gst_pad_get_name(pad);

  sinkpad = gst_element_get_compatible_pad (app->encodebin, pad, NULL);

  if (sinkpad == NULL) {
    GstCaps *caps;

    /* Ask encodebin for a compatible pad */
    caps = gst_pad_get_caps (pad);
    g_signal_emit_by_name (app->encodebin, "request-pad", caps, &sinkpad);
    if (caps)
      gst_caps_unref (caps);
  }
  if (sinkpad == NULL) {
    g_print ("Couldn't get an encoding channel for pad %s:%s\n",
        GST_DEBUG_PAD_NAME (pad));
    return;
  }

  if (G_UNLIKELY (gst_pad_link (pad, sinkpad) != GST_PAD_LINK_OK)) {
    g_print ("Couldn't link pads\n");
  }
  else
  {
    sink_pad_name = gst_pad_get_name(sinkpad);
    g_print ("linked demux pad %s to mux pad %s\n",src_pad_name, sink_pad_name);
    g_free(sink_pad_name);
  }

  g_free(src_pad_name);
  return;
}

int main (int argc, char *argv[])
{
  App *app = &s_app;
  GstBus *bus;
  const gchar *nano_str;
  guint major, minor, micro, nano;

  gst_init (&argc, &argv);

  app->video_pid = 4113;
  app->audio_pid = 4352;

  if (argc < 3) {
    g_print ("Usage: %s input_filename.ts output_filename.m2ts [video-pid (default=%i)] [audio-pid (default=%i)]\n", argv[0], app->video_pid, app->audio_pid);
    return -1;
  }

  if (argc >= 4)
  {
    if (g_ascii_strncasecmp(argv[3],"0x",2) == 0)
    {
      sscanf(argv[3]+2, "%x",&app->video_pid);
    }
    else
      app->video_pid = atoi(argv[3]);
  }
  if (argc == 5)
  {
    if (g_ascii_strncasecmp(argv[4],"0x",2) == 0)
    {
      sscanf(argv[4]+2, "%x",&app->audio_pid);
    }
    else
      app->audio_pid = atoi(argv[4]);
  }

  gst_version (&major, &minor, &micro, &nano);

  if (nano == 1)
    nano_str = "(GIT)";
  else if (nano == 2)
    nano_str = "(Prerelease)";
  else
    nano_str = "";

  g_print("%s is linked against GStreamer %d.%d.%d %s for dreambox\n",
      argv[0], major, minor, micro, nano_str);

  /* create a mainloop to get messages */
  app->loop = g_main_loop_new (NULL, TRUE);

  app->pipeline = gst_pipeline_new("bluraydisc stream remuxer");
  g_assert (app->pipeline);

  app->index = gst_index_factory_make ("memindex");
  if (app->index) {
    g_signal_connect (G_OBJECT (app->index), "entry_added", G_CALLBACK (entry_added), NULL);
    g_object_set (G_OBJECT (app->index), "resolver", 1, NULL);
  }

  promote_element("h264parse");

  app->filesrc = gst_element_factory_make("filesrc", "filesrc");
  app->tsdemux = gst_element_factory_make("mpegtsdemux", "tsdemux");
  app->encodebin = gst_element_factory_make("encodebin", "encodebin");
  app->filesink = gst_element_factory_make("filesink", "filesink");

  gst_bin_add_many (GST_BIN (app->pipeline), app->filesrc, app->tsdemux, app->encodebin, app->filesink, NULL);

  g_object_set (G_OBJECT (app->filesrc), "location", argv[1], NULL);

//   g_object_set (G_OBJECT (app->m2tsmux), "m2ts-mode", TRUE, NULL);
  g_object_set (G_OBJECT (app->encodebin), "profile", create_bluray_m2ts_profile(), NULL);

  g_object_set (G_OBJECT (app->filesink), "location", argv[2], NULL);

  gst_element_link (app->filesrc, app->tsdemux);

  gst_element_link (app->encodebin, app->filesink);

  g_signal_connect (app->tsdemux, "pad-added", G_CALLBACK (gstDemuxPadAdded), app);

  gst_element_set_index (app->encodebin, app->index);
  
  bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));

  gst_bus_add_watch (bus, (GstBusFunc) bus_message, app);

  gst_element_set_state (app->pipeline, GST_STATE_PLAYING);

  g_main_loop_run (app->loop);

  g_message ("stopping");

  gst_element_set_state (app->pipeline, GST_STATE_NULL);
  
  gst_object_unref (bus);
  g_main_loop_unref (app->loop);

  return 0;
}
