/*
 * GStreamer
 * Copyright (C) 2009 Julien Isorce <julien.isorce@gmail.com>
 * Copyright (C) 2009 Andrey Nechypurenko <andreynech@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "pipeline.h"


Pipeline::Pipeline (GstGLDisplay *display,
    GstGLContext * context, const QString & videoLocation, QObject * parent)
    :
QObject (parent),
m_videoLocation (videoLocation),
m_loop (NULL),
m_bus (NULL),
m_pipeline (NULL)
{
  this->display = display;
  this->context = context;
  this->configure ();
}

Pipeline::~Pipeline ()
{
}

void
Pipeline::configure ()
{

#ifdef Q_WS_WIN
  m_loop = g_main_loop_new (NULL, FALSE);
#endif

  if (m_videoLocation.isEmpty ()) {
    qDebug ("No video file specified. Using video test source.");
    m_pipeline =
        GST_PIPELINE (gst_parse_launch
        ("videotestsrc ! "
            "video/x-raw, width=640, height=480, "
            "framerate=(fraction)30/1 ! "
            "glupload ! gleffects effect=5 ! fakesink sync=1", NULL));
  } else {
    QByteArray ba = m_videoLocation.toLocal8Bit ();
    qDebug ("Loading video: %s", ba.data ());
    gchar *pipeline = g_strdup_printf ("filesrc name=f ! "
        "decodebin ! gleffects effect=5 ! " "fakesink sync=1");
    m_pipeline = GST_PIPELINE (gst_parse_launch (pipeline, NULL));
    GstElement *f = gst_bin_get_by_name (GST_BIN (m_pipeline), "f");
    g_object_set (G_OBJECT (f), "location", ba.data (), NULL);
    gst_object_unref (GST_OBJECT (f));
    g_free (pipeline);
  }

  m_bus = gst_pipeline_get_bus (GST_PIPELINE (m_pipeline));
  gst_bus_add_watch (m_bus, (GstBusFunc) bus_call, this);
  gst_bus_enable_sync_message_emission (m_bus);
  g_signal_connect (m_bus, "sync-message", G_CALLBACK (sync_bus_call), this);
  gst_object_unref (m_bus);

  gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_PAUSED);
  GstState state = GST_STATE_PAUSED;
  if (gst_element_get_state (GST_ELEMENT (this->m_pipeline),
          &state, NULL, GST_CLOCK_TIME_NONE)
      != GST_STATE_CHANGE_SUCCESS) {
    qDebug ("failed to pause pipeline");
    return;
  }
}

void
Pipeline::start ()
{
  // set a callback to retrieve the gst gl textures
  GstElement *fakesink = gst_bin_get_by_name (GST_BIN (this->m_pipeline),
      "fakesink0");
  g_object_set (G_OBJECT (fakesink), "signal-handoffs", TRUE, NULL);
  g_signal_connect (fakesink, "handoff", G_CALLBACK (on_gst_buffer), this);
  gst_object_unref (fakesink);

  GstStateChangeReturn ret =
      gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    qDebug ("Failed to start up pipeline!");

    /* check if there is an error message with details on the bus */
    GstMessage *msg = gst_bus_poll (this->m_bus, GST_MESSAGE_ERROR, 0);
    if (msg) {
      GError *err = NULL;
      gst_message_parse_error (msg, &err, NULL);
      qDebug ("ERROR: %s", err->message);
      g_error_free (err);
      gst_message_unref (msg);
    }
    return;
  }
#ifdef Q_WS_WIN
  g_main_loop_run (m_loop);
#endif
}

/* fakesink handoff callback */
void
Pipeline::on_gst_buffer (GstElement * element,
    GstBuffer * buf, GstPad * pad, Pipeline * p)
{
  Q_UNUSED (pad)
      Q_UNUSED (element)

      /* ref then push buffer to use it in qt */
      gst_buffer_ref (buf);
  p->queue_input_buf.put (buf);

  if (p->queue_input_buf.size () > 3)
    p->notifyNewFrame ();

  /* pop then unref buffer we have finished to use in qt */
  if (p->queue_output_buf.size () > 3) {
    GstBuffer *buf_old = (p->queue_output_buf.get ());
    if (buf_old)
      gst_buffer_unref (buf_old);
  }
}

void
Pipeline::stop ()
{
#ifdef Q_WS_WIN
  g_main_loop_quit (m_loop);
#else
  emit stopRequested ();
#endif
}

void
Pipeline::unconfigure ()
{
  gst_element_set_state (GST_ELEMENT (this->m_pipeline), GST_STATE_NULL);

  GstBuffer *buf;
  while (this->queue_input_buf.size ()) {
    buf = (GstBuffer *) (this->queue_input_buf.get ());
    gst_buffer_unref (buf);
  }
  while (this->queue_output_buf.size ()) {
    buf = (GstBuffer *) (this->queue_output_buf.get ());
    gst_buffer_unref (buf);
  }

  gst_object_unref (m_pipeline);
}

gboolean Pipeline::bus_call (GstBus * bus, GstMessage * msg, Pipeline * p)
{
  Q_UNUSED (bus)

      switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:
      qDebug ("End-of-stream received. Stopping.");
      p->stop ();
      break;

    case GST_MESSAGE_ERROR:
    {
      gchar *
          debug = NULL;
      GError *
          err = NULL;
      gst_message_parse_error (msg, &err, &debug);
      qDebug ("Error: %s", err->message);
      g_error_free (err);
      if (debug) {
        qDebug ("Debug deails: %s", debug);
        g_free (debug);
      }
      p->stop ();
      break;
    }

    default:
      break;
  }

  return TRUE;
}

gboolean Pipeline::sync_bus_call (GstBus * bus, GstMessage * msg, Pipeline * p)
{
  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_NEED_CONTEXT:
    {
      const gchar *
          context_type;

      gst_message_parse_context_type (msg, &context_type);
      g_print ("got need context %s\n", context_type);

      if (g_strcmp0 (context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
        GstContext *display_context = gst_context_new (GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
        gst_context_set_gl_display (display_context, p->display);
        gst_element_set_context (GST_ELEMENT (msg->src), display_context);
      } else if (g_strcmp0 (context_type, "gst.gl.app_context") == 0) {
        GstContext *app_context = gst_context_new ("gst.gl.app_context", TRUE);
        GstStructure *s = gst_context_writable_structure (app_context);
        gst_structure_set (s, "context", GST_GL_TYPE_CONTEXT, p->context, NULL);
        gst_element_set_context (GST_ELEMENT (msg->src), app_context);
      }
      break;
    }
    default:
      break;
  }
  return FALSE;
}
