/*
 * This file is part of GNAC - Gnome Audio Converter
 *
 * Copyright (C) 2007 - 2012 Gnac
 *    
 *    - DUPASQUIER  Benoit    <bdupasqu@src.gnome.org>
 *    - JOAQUIM     David     <djoaquim@src.gnome.org>
 *    - ROUX        Alexandre <alexroux@src.gnome.org>
 *
 * GNAC 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 3 of the License, or
 * (at your option) any later version.
 *
 * GNAC 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 GNAC; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib/gi18n.h>

#include "libgnac-error.h"
#include "libgnac-gst.h"
#include "libgnac-gst-utils.h"
#include "libgnac-media-item.h"


#define libgnac_gst_element_add_ghost_pad(element, pad, name) \
    gst_element_add_pad(element, gst_ghost_pad_new(name, pad))

void
libgnac_gst_run(LibgnacMediaItem  *item, 
                GError           **error)
{
  g_return_if_fail(!error || !*error);

  gst_element_set_state(item->pipeline, GST_STATE_PLAYING);
}


void
libgnac_gst_pause(LibgnacMediaItem  *item,
                  GError           **error)
{
  g_return_if_fail(!error || !*error);
  
  gst_element_set_state(item->pipeline, GST_STATE_PAUSED);
}


void
libgnac_gst_stop(LibgnacMediaItem  *item, 
                 GError           **error)
{
  g_return_if_fail(!error || !*error);

  if (!item) return;

  if (item->pipeline) {
    libgnac_gst_clean_pipeline(item);
  }
}


static void
libgnac_gst_parse_error_missing_elem(GstParseContext  *ctx,
                                     gchar            *descr,
                                     GError          **error)
{
  g_return_if_fail(error);

  if (g_error_matches(*error, GST_PARSE_ERROR,
      GST_PARSE_ERROR_NO_SUCH_ELEMENT))
  {
    /* clean up the "old" error */
    g_clear_error(error);
    /* get the name of the missing elements */
    gchar **missing = gst_parse_context_get_missing_elements(ctx);
    g_debug("Failed to parse description: %s", descr);
    /* set the "new" error */
    g_set_error(error, LIBGNAC_ERROR,
        LIBGNAC_ERROR_MISSING_PLUGIN, "%s", missing[0]);
    /* clean up */
    g_strfreev(missing);
    gst_parse_context_free(ctx);
  }
}


static GstElement *
libgnac_gst_get_audio_bin(LibgnacMediaItem  *item,
                          LibgnacProfile    *profile,
                          GError           **error)
{
  GstElement *bin = gst_bin_new("audio_bin");

  GstElement *converter = libgnac_gstu_make_pipeline_element(bin,
      "audioconvert", NULL, error);
  g_return_val_if_fail(converter, NULL);

  GstElement *resample = libgnac_gstu_make_pipeline_element(bin,
      "audioresample", NULL, error);
  g_return_val_if_fail(resample, NULL);

  GstElement *rate = libgnac_gstu_make_pipeline_element(bin,
      "audiorate", NULL, error);
  g_return_val_if_fail(rate, NULL);

  GstParseContext *context = gst_parse_context_new();

  GstElement *encoder = gst_parse_bin_from_description_full(
      profile->audio_desc, TRUE, context, GST_PARSE_FLAG_NONE, error);
  if (!encoder) {
    libgnac_gst_parse_error_missing_elem(context, profile->audio_desc, error);
    gst_parse_context_free(context);
    return NULL;
  }

  gst_parse_context_free(context);

  g_return_val_if_fail(gst_bin_add(GST_BIN(bin), encoder), NULL);
  g_return_val_if_fail(
      gst_element_link_many(converter, resample, rate, encoder, NULL),
      NULL);

  /* ghost pads */
  GstPad *pad = gst_element_get_static_pad(converter, "sink");
  libgnac_gst_element_add_ghost_pad(bin, pad, "sink");
  gst_object_unref(pad);

  pad = gst_element_get_static_pad(encoder, "src");
  libgnac_gst_element_add_ghost_pad(bin, pad, "src");
  gst_object_unref(pad);

  return bin;
}


static GstElement *
libgnac_gst_get_video_bin(LibgnacMediaItem  *item,
                          LibgnacProfile    *profile,
                          GError           **error)
{
  GstElement *bin = gst_bin_new("video_bin");

  GstElement *converter = libgnac_gstu_make_pipeline_element(bin,
      "ffmpegcolorspace", NULL, error);
  g_return_val_if_fail(converter, NULL);

  GstElement *rate = libgnac_gstu_make_pipeline_element(bin,
      "videorate", NULL, error);
  g_return_val_if_fail(rate, NULL);

  GstParseContext *context = gst_parse_context_new();

  GstElement *encoder = gst_parse_bin_from_description_full(
      profile->video_desc, TRUE, context, GST_PARSE_FLAG_NONE, error);
  if (!encoder) {
    libgnac_gst_parse_error_missing_elem(context, profile->video_desc, error);
    gst_parse_context_free(context);
    return NULL;
  }

  gst_parse_context_free(context);

  g_return_val_if_fail(gst_bin_add(GST_BIN(bin), encoder), NULL);
  g_return_val_if_fail(
      gst_element_link_many(converter, rate, encoder, NULL),
      NULL);

  /* ghost pads */
  GstPad *pad = gst_element_get_static_pad(converter, "sink");
  libgnac_gst_element_add_ghost_pad(bin, pad, "video_sink");
  gst_object_unref(pad);

  pad = gst_element_get_static_pad(encoder, "src");
  libgnac_gst_element_add_ghost_pad(bin, pad, "src");
  gst_object_unref(pad);

  return bin;
}


void
libgnac_gst_build_pipeline(LibgnacMediaItem  *item,
                           GError           **error)
{
  g_return_if_fail(!error || !*error);

  GError *err = NULL;

  /* pipeline */
  item->pipeline = libgnac_gstu_pipeline_new(&err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  /* bus */
  item->bus = gst_element_get_bus(GST_ELEMENT(item->pipeline));
  gst_bus_add_signal_watch(item->bus);

  /* source */
  GstElement *source = libgnac_gstu_make_pipeline_element(item->pipeline,
      "giosrc", NULL, &err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  g_object_set(G_OBJECT(source), "file", item->source, NULL);

  /* decodebin */
  GstElement *decodebin = libgnac_gstu_make_pipeline_element(item->pipeline,
      "decodebin", NULL, &err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  libgnac_gstu_element_link(source, decodebin, &err);
  if (err) {
    g_debug("link source->decodebin failed");
    g_propagate_error(error, err);
    return;
  }

  LibgnacProfile *profile = item->profile;

  /* audio_bin */
  GstElement *audio_bin = libgnac_gst_get_audio_bin(item, profile, &err);
  if (err) {
    g_debug("audio_bin creation failed");
    g_propagate_error(error, err);
    return;
  }

  item->audio_encoder = audio_bin;

  libgnac_gstu_bin_add(item->pipeline, audio_bin, &err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  /* sink */
  GstElement *sink = libgnac_gstu_make_pipeline_element(item->pipeline,
      "giosink", NULL, &err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  g_object_set(G_OBJECT(sink), "file", item->destination, NULL);

  gboolean has_audio = libgnac_item_has_audio(item);
  gboolean has_video = libgnac_item_has_video(item);

  if (has_video && item->video_channel != -1) {
    /* muxer */
    GstElement *muxer = libgnac_gstu_make_pipeline_element(item->pipeline,
        profile->muxer_desc, NULL, &err);
    if (err) {
      g_propagate_error(error, err);
      return;
    }

    item->muxer = muxer;

    libgnac_gstu_element_link(muxer, sink, &err);
    if (err) {
      g_debug("link encoder->sink failed");
      g_propagate_error(error, err);
      return;
    }

    /* audio */
    GstElement *audio_queue = libgnac_gstu_make_pipeline_element(item->pipeline,
        "queue", "audio_queue", &err);
    if (err) {
      g_propagate_error(error, err);
      return;
    }

    item->audio_encoder = audio_queue;

    if (!gst_element_link_many(audio_queue, audio_bin, muxer, NULL)) {
      g_set_error(error, LIBGNAC_ERROR, LIBGNAC_ERROR_PIPELINE_CREATION,
          _("Failed to link many audio elements"));
      return;
    }

    /* video */
    GstElement *video_queue = libgnac_gstu_make_pipeline_element(item->pipeline,
        "queue", "video_queue", &err);
    if (err) {
      g_propagate_error(error, err);
      return;
    }

    item->video_encoder = video_queue;

    GstElement *video_bin = libgnac_gst_get_video_bin(item, profile, &err);
    if (err) {
      g_debug("video_bin creation failed");
      g_propagate_error(error, err);
      return;
    }

    libgnac_gstu_bin_add(item->pipeline, video_bin, &err);
    if (err) {
      g_propagate_error(error, err);
      return;
    }

    if (!gst_element_link_many(video_queue, video_bin, muxer, NULL)) {
      g_set_error(error, LIBGNAC_ERROR, LIBGNAC_ERROR_PIPELINE_CREATION,
          _("Failed to link many video elements"));
      return;
    }
  }
  else if (has_audio)
  {
    libgnac_gstu_element_link(audio_bin, sink, &err);
    if (err) {
      g_debug("link encoder->sink failed");
      g_propagate_error(error, err);
      return;
    }
  }
  else
  {
    g_debug("The file contains no usable audio or video data");
    g_set_error(error, LIBGNAC_ERROR, LIBGNAC_ERROR_PIPELINE_CREATION,
        _("Unable to create pipeline"));
    return;
  }

  g_signal_connect(decodebin, "pad-added",
      G_CALLBACK(libgnac_gst_newpad_cb), item);
}


void
libgnac_gst_clean_pipeline(LibgnacMediaItem *item)
{
  if (!item) return;

  if (item->pipeline) {
    if (!gst_element_set_state(item->pipeline, GST_STATE_NULL)) {
      g_debug("Unable to set state to null");
      return;
    }
    gst_object_unref(item->pipeline);
    item->pipeline = NULL;
  }

  if (item->bus) {
    item->bus = NULL;
  }

  if (item->audio_encoder) {
    item->audio_encoder = NULL;
  }
}


void
libgnac_gst_newpad_cb(GstElement *decodebin,
                      GstPad     *pad,
                      gpointer    data)
{
  LibgnacMediaItem *item = (LibgnacMediaItem *) data;
	GstCaps *caps = gst_pad_query_caps(pad, NULL);

	if (gst_caps_is_empty(caps) || gst_caps_is_any(caps)) {
    g_debug("Got a bad caps: %s", gst_caps_to_string(caps));
    gst_caps_unref(caps);
    return;
	}

	GstStructure *structure = gst_caps_get_structure(caps, 0);
	const gchar *mimetype = gst_structure_get_name(structure);

	if (g_str_has_prefix(mimetype, "audio/")) {
	  if (!libgnac_gstu_get_compatible_pad(item->audio_encoder, pad,
        caps, "audio"))
    {
      return;
    }
  } else if (g_str_has_prefix(mimetype, "video/")
      && item->video_channel != -1)
  {
	  if (!libgnac_gstu_get_compatible_pad(item->video_encoder, pad,
        caps, "video"))
    {
      return;
    }
	} else {
    g_debug("Unhandled mime-type: %s", mimetype);
	}

	gst_caps_unref(caps);
}
