/*
 *  GStreamer Video sink.
 *
 *  Copyright (C) <2003> Julien Moutte <julien@moutte.net>
 *
 *  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 Library 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.
 */

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

#include "gstvideosink.h"

/* VideoSink signals and args */

enum {
  HAVE_VIDEO_OUT,
  HAVE_SIZE,
  FRAME_DISPLAYED,
  LAST_SIGNAL
};


enum {
  ARG_0,
  ARG_WIDTH,
  ARG_HEIGHT,
  ARG_FRAMES_DISPLAYED,
  ARG_FRAME_TIME,
};

static GstElementClass *parent_class = NULL;
static guint gst_videosink_signals[LAST_SIGNAL] = { 0 };

/* Private methods */

static void
gst_videosink_set_property (GObject *object, guint prop_id,
                            const GValue *value, GParamSpec *pspec)
{
  GstVideoSink *videosink;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (object));
  
  videosink = GST_VIDEOSINK (object);
  
  switch (prop_id)
    {
      case ARG_WIDTH:
        gst_video_sink_set_geometry (videosink, g_value_get_int (value),
                                     videosink->height);
        break;
      case ARG_HEIGHT:
        gst_video_sink_set_geometry (videosink, videosink->width,
                                     g_value_get_int (value));
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
gst_videosink_get_property (GObject *object, guint prop_id,
                            GValue *value, GParamSpec *pspec)
{
  GstVideoSink *videosink;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (object));
  
  videosink = GST_VIDEOSINK (object);
  
  switch (prop_id)
    {
      case ARG_WIDTH:
        g_value_set_int (value, videosink->width);
        break;
      case ARG_HEIGHT:
        g_value_set_int (value, videosink->height);
        break;
      case ARG_FRAMES_DISPLAYED:
        g_value_set_int (value, videosink->frames_displayed);
        break;
      case ARG_FRAME_TIME:
        g_value_set_int64 (value, videosink->frame_time);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
gst_videosink_set_clock (GstElement *element, GstClock *clock)
{
  GstVideoSink *videosink;

  videosink = GST_VIDEOSINK (element);
  
  videosink->clock = clock;
}

/* Initing stuff */

static void
gst_videosink_init (GstVideoSink *videosink)
{
  videosink->video_out = NULL;
  videosink->width = -1;
  videosink->height = -1;
  videosink->frames_displayed = 0;
  videosink->frame_time = 0;

  videosink->clock = NULL;
  videosink->formats = NULL;
}

static void
gst_videosink_class_init (GstVideoSinkClass *klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass*)klass;
  gstelement_class = (GstElementClass*)klass;

  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_WIDTH,
    g_param_spec_int ("width", "Width", "Width of the video output",
                      G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
  
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HEIGHT,
    g_param_spec_int ("height", "Height", "Height of the video output",
                      G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
  
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FRAMES_DISPLAYED,
    g_param_spec_int ("frames_displayed", "Frames displayed",
                      "The number of frames displayed so far",
                      G_MININT,G_MAXINT, 0, G_PARAM_READABLE));
  
  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FRAME_TIME,
    g_param_spec_int64 ("frame_time", "Frame time", "The interval between frames",
                      G_MININT64, G_MAXINT64, 0, G_PARAM_READABLE));

  gobject_class->set_property = gst_videosink_set_property;
  gobject_class->get_property = gst_videosink_get_property;

  gst_videosink_signals[FRAME_DISPLAYED] =
    g_signal_new ("frame_displayed",
                  G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GstVideoSinkClass, frame_displayed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
                  
  gst_videosink_signals[HAVE_SIZE] =
    g_signal_new ("have_size",
                  G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GstVideoSinkClass, have_size),
                  NULL, NULL,
                  gst_marshal_VOID__INT_INT, G_TYPE_NONE, 2,
		  G_TYPE_UINT, G_TYPE_UINT);

  gst_videosink_signals[HAVE_VIDEO_OUT] =
    g_signal_new ("have_video_out",
                  G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GstVideoSinkClass, have_video_out),
                  NULL, NULL,
                  gst_marshal_VOID__POINTER, G_TYPE_NONE, 1,
		  G_TYPE_POINTER);

  gstelement_class->set_clock = gst_videosink_set_clock;
}

/* Public virtual methods */

/**
 * gst_video_sink_set_video_out:
 * @videosink: a #GstVideoSink to set the video out on.
 * @video_out: the #gpointer linking to video out.
 *
 * This will call the video sink's set_video_out method. You should use this
 * method to tell to a video sink to display video output to a specific
 * video out ressource.
 */
void
gst_video_sink_set_video_out (GstVideoSink *videosink, gpointer video_out)
{
  GstVideoSinkClass *class;
  
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  
  class = GST_VIDEOSINK_GET_CLASS (videosink);
  
  if (class->set_video_out)
    class->set_video_out (videosink, video_out);
}

/**
 * gst_video_sink_set_geometry:
 * @videosink: a #GstVideoSink which geometry will be set.
 * @width: a width as a #gint.
 * @height: a height as a #gint.
 *
 * Set video sink's geometry to @width x @height. If that succeed you should
 * get the have_size signal being fired.
 */
void
gst_video_sink_set_geometry (GstVideoSink *videosink, gint width, gint height)
{
  GstVideoSinkClass *class;
  
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  
  class = GST_VIDEOSINK_GET_CLASS (videosink);
  
  if (class->set_geometry)
    class->set_geometry (videosink, width, height);
}

/* Public methods */

/**
 * gst_video_sink_got_video_out:
 * @videosink: a #GstVideoSink which got a video out ressource.
 * @video_out: a #gpointer linking to the video out ressource.
 *
 * This will fire an have_video_out signal and update the internal object's
 * #gpointer.
 *
 * This function should be used by video sink developpers.
 */
void
gst_video_sink_got_video_out (GstVideoSink *videosink, gpointer video_out)
{
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  
  videosink->video_out = video_out;
  
  g_signal_emit (G_OBJECT (videosink), gst_videosink_signals[HAVE_VIDEO_OUT],
                 0, video_out);
}

/**
 * gst_video_sink_got_video_size:
 * @videosink: a #GstVideoSink which received video geometry.
 * @width: a width as a #gint.
 * @height: a height as a #gint.
 *
 * This will fire an have_size signal and update the internal object's
 * geometry.
 *
 * This function should be used by video sink developpers.
 */
void
gst_video_sink_got_video_size (GstVideoSink *videosink, gint width, gint height)
{
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  
  g_signal_emit (G_OBJECT (videosink), gst_videosink_signals[HAVE_SIZE],
                 0, width, height);
}

/**
 * gst_video_sink_frame_displayed:
 * @videosink: a #GstVideoSink which displayed a frame.
 *
 * This will fire an frame_displayed signal and update the internal object's
 * counter.
 *
 * This function should be used by video sink developpers.
 */
void
gst_video_sink_frame_displayed (GstVideoSink *videosink)
{
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  
  videosink->frames_displayed++;
  
  g_signal_emit (G_OBJECT (videosink),
                 gst_videosink_signals[FRAME_DISPLAYED], 0);
}

/**
 * gst_video_sink_get_geometry:
 * @videosink: a #GstVideoSink which displayed a frame.
 * @width: a #gint pointer where the width will be set.
 * @height: a #gint pointer where the height will be set.
 *
 * This will fill set @width and @height with the video sink's current geometry.
 */
void
gst_video_sink_get_geometry (GstVideoSink *videosink, gint *width, gint *height)
{
  g_return_if_fail (videosink != NULL);
  g_return_if_fail (GST_IS_VIDEOSINK (videosink));
  *width = videosink->width;
  *height = videosink->height;
}

GType
gst_videosink_get_type (void)
{
  static GType videosink_type = 0;

  if (!videosink_type)
    {
      static const GTypeInfo videosink_info = {
        sizeof (GstVideoSinkClass),
        NULL,
        NULL,
        (GClassInitFunc) gst_videosink_class_init,
        NULL,
        NULL,
        sizeof (GstVideoSink),
        0,
        (GInstanceInitFunc) gst_videosink_init,
      };
    
      videosink_type = g_type_register_static (GST_TYPE_ELEMENT,
                                               "GstVideoSink",
                                               &videosink_info, 0);
    }
    
  return videosink_type;
}