/* GStreamer xvid decoder plugin
 * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

#include <string.h>
#include "gstxviddec.h"
#include <gst/video/video.h>

/* elementfactory information */
GstElementDetails gst_xviddec_details = {
  "Xvid decoder",
  "Codec/Video/Decoder",
  "Xvid decoder based on xviddecore",
  "Ronald Bultje <rbultje@ronald.bitfreak.net>",
};

GST_PAD_TEMPLATE_FACTORY(sink_template,
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_CAPS_NEW("xviddec_sink",
               "video/x-xvid",
                 "width",     GST_PROPS_INT_RANGE (0, G_MAXINT),
		 "height",    GST_PROPS_INT_RANGE (0, G_MAXINT),
		 "framerate", GST_PROPS_FLOAT_RANGE (0, G_MAXFLOAT))
)

GST_PAD_TEMPLATE_FACTORY(src_template,
  "src",
  GST_PAD_SRC,
  GST_PAD_ALWAYS,
  gst_caps_new(
    "xviddec_sink",
    "video/x-raw-yuv",
    GST_VIDEO_YUV_PAD_TEMPLATE_PROPS (
      GST_PROPS_LIST(
        GST_PROPS_FOURCC(GST_MAKE_FOURCC('I','4','2','0')),
        GST_PROPS_FOURCC(GST_MAKE_FOURCC('Y','U','Y','2')),
        GST_PROPS_FOURCC(GST_MAKE_FOURCC('Y','V','1','2')),
        GST_PROPS_FOURCC(GST_MAKE_FOURCC('Y','V','Y','U')),
        GST_PROPS_FOURCC(GST_MAKE_FOURCC('U','Y','V','Y'))
      )
    )
  ),
  gst_caps_new(
    "xviddec_sink_rgb24_32",
    "video/x-raw-rgb",
    GST_VIDEO_RGB_PAD_TEMPLATE_PROPS_24_32
  ),
  gst_caps_new(
    "xviddec_sink_rgb15_16",
    "video/x-raw-rgb",
    GST_VIDEO_RGB_PAD_TEMPLATE_PROPS_15_16
  )
)


/* XvidDec signals and args */
enum {
  /* FILL ME */
  LAST_SIGNAL
};

enum {
  ARG_0
  /* FILL ME */
};

static void             gst_xviddec_base_init    (gpointer g_class);
static void             gst_xviddec_class_init   (GstXvidDecClass *klass);
static void             gst_xviddec_init         (GstXvidDec      *xviddec);
static void             gst_xviddec_dispose      (GObject         *object);
static void             gst_xviddec_chain        (GstPad          *pad,
                                                  GstData         *data);
static GstPadLinkReturn gst_xviddec_connect      (GstPad          *pad,
                                                  GstCaps         *vscapslist);
static GstPadLinkReturn	gst_xviddec_negotiate	(GstXvidDec *xviddec);

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


GType
gst_xviddec_get_type(void)
{
  static GType xviddec_type = 0;

  if (!xviddec_type)
  {
    static const GTypeInfo xviddec_info = {
      sizeof(GstXvidDecClass),
      gst_xviddec_base_init,
      NULL,
      (GClassInitFunc) gst_xviddec_class_init,
      NULL,
      NULL,
      sizeof(GstXvidDec),
      0,
      (GInstanceInitFunc) gst_xviddec_init,
    };
    xviddec_type = g_type_register_static(GST_TYPE_ELEMENT,
                                          "GstXvidDec",
                                          &xviddec_info, 0);
  }
  return xviddec_type;
}

static void
gst_xviddec_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (sink_template));
  gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (src_template));

  gst_element_class_set_details (element_class, &gst_xviddec_details);
}

static void
gst_xviddec_class_init (GstXvidDecClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;

  gst_xvid_init();

  parent_class = g_type_class_ref(GST_TYPE_ELEMENT);

  gobject_class->dispose = gst_xviddec_dispose;
}


static void
gst_xviddec_init (GstXvidDec *xviddec)
{
  /* create the sink pad */
  xviddec->sinkpad = gst_pad_new_from_template(
                       GST_PAD_TEMPLATE_GET(sink_template),
                       "sink");
  gst_element_add_pad(GST_ELEMENT(xviddec), xviddec->sinkpad);

  gst_pad_set_chain_function(xviddec->sinkpad, gst_xviddec_chain);
  gst_pad_set_link_function(xviddec->sinkpad, gst_xviddec_connect);

  /* create the src pad */
  xviddec->srcpad = gst_pad_new_from_template(
                      GST_PAD_TEMPLATE_GET(src_template),
                      "src");
  gst_element_add_pad(GST_ELEMENT(xviddec), xviddec->srcpad);

  /* bitrate, etc. */
  xviddec->width = xviddec->height = xviddec->csp = -1;

  /* set xvid handle to NULL */
  xviddec->handle = NULL;
}


static void
gst_xviddec_unset (GstXvidDec *xviddec)
{
  /* unref this instance */
  xvid_decore(xviddec->handle, XVID_DEC_DESTROY, NULL, NULL);
  xviddec->handle = NULL;
}


static gboolean
gst_xviddec_setup (GstXvidDec *xviddec)
{
  XVID_DEC_PARAM xdec;
  int ret;

  /* initialise parameters, see xvid documentation */
  memset(&xdec, 0, sizeof(XVID_DEC_PARAM));
  xdec.width = xviddec->width;
  xdec.height = xviddec->height;

  if ((ret = xvid_decore(NULL, XVID_DEC_CREATE,
                         &xdec, NULL)) != XVID_ERR_OK) {
    gst_element_error(GST_ELEMENT(xviddec),
		      "Setting parameters %dx%d@%d failed: %s (%d)",
	              xviddec->width, xviddec->height, xviddec->csp,
		      gst_xvid_error(ret), ret);
    return FALSE;
  }

  xviddec->handle = xdec.handle;

  return TRUE;
}


static void
gst_xviddec_dispose (GObject *object)
{
  GstXvidDec *xviddec = GST_XVIDDEC(object);

  gst_xviddec_unset(xviddec);
}


static void
gst_xviddec_chain (GstPad    *pad,
                   GstData *_data)
{
  GstBuffer *buf = GST_BUFFER (_data);
  GstXvidDec *xviddec;
  GstBuffer *outbuf;
  XVID_DEC_FRAME xframe;
  int ret;

  g_return_if_fail(pad != NULL);
  g_return_if_fail(GST_IS_PAD(pad));
  g_return_if_fail(buf != NULL);

  xviddec = GST_XVIDDEC(GST_OBJECT_PARENT(pad));

  if (!xviddec->handle) {
    if (!gst_xviddec_negotiate(xviddec)) {
      gst_element_error(GST_ELEMENT(xviddec),
                        "No format set - aborting");
      gst_buffer_unref(buf);
      return;
    }
  }

  outbuf = gst_buffer_new_and_alloc(xviddec->width *
                                    xviddec->height *
                                    xviddec->bpp / 8);
  GST_BUFFER_TIMESTAMP(outbuf) = GST_BUFFER_TIMESTAMP(buf);
  GST_BUFFER_SIZE(outbuf) = xviddec->width *
                            xviddec->height *
                            xviddec->bpp / 8;

  /* encode and so ... */
  xframe.bitstream = (void *) GST_BUFFER_DATA(buf);
  xframe.image = (void *) GST_BUFFER_DATA(outbuf);
  xframe.length = GST_BUFFER_SIZE(buf);
  xframe.stride = 0; /*xviddec->width * xviddec->bpp / 8;*/
  xframe.colorspace = xviddec->csp;

  if ((ret = xvid_decore(xviddec->handle, XVID_DEC_DECODE,
                         &xframe, NULL))) {
    gst_element_error(GST_ELEMENT(xviddec),
                      "Error decoding xvid frame: %s (%d)\n",
		      gst_xvid_error(ret), ret);
    gst_buffer_unref(buf);
    return;
  }

  gst_pad_push(xviddec->srcpad, GST_DATA (outbuf));
  gst_buffer_unref(buf);
}


static GstPadLinkReturn
gst_xviddec_negotiate (GstXvidDec *xviddec)
{
  GstPadLinkReturn ret;
  GstCaps *caps;
  struct {
    guint32 fourcc;
    gint    depth, bpp;
    gint    csp;
  } fmt_list[] = {
    { GST_MAKE_FOURCC('Y','U','Y','V'), 16, 16, XVID_CSP_YUY2   },
    { GST_MAKE_FOURCC('U','Y','V','Y'), 16, 16, XVID_CSP_UYVY   },
    { GST_MAKE_FOURCC('Y','V','Y','U'), 16, 16, XVID_CSP_YVYU   },
    { GST_MAKE_FOURCC('Y','V','1','2'), 12, 12, XVID_CSP_YV12   },
    { GST_MAKE_FOURCC('I','4','2','0'), 12, 12, XVID_CSP_I420   },
    { GST_MAKE_FOURCC('R','G','B',' '), 32, 32, XVID_CSP_RGB32  },
    { GST_MAKE_FOURCC('R','G','B',' '), 24, 24, XVID_CSP_RGB24  },
    { GST_MAKE_FOURCC('R','G','B',' '), 16, 16, XVID_CSP_RGB555 },
    { GST_MAKE_FOURCC('R','G','B',' '), 15, 16, XVID_CSP_RGB565 },
    { 0, 0, 0 }
  };
  gint i;

  for (i = 0; fmt_list[i].fourcc != 0; i++) {
    xviddec->csp = fmt_list[i].csp;

    /* try making a caps to set on the other side */
    if (fmt_list[i].fourcc == GST_MAKE_FOURCC('R','G','B',' ')) {
      guint32 r_mask = 0, b_mask = 0, g_mask = 0;
      gint endianness = 0;
      switch (fmt_list[i].depth) {
        case 15:
          endianness = G_BYTE_ORDER;
          r_mask = 0xf800; g_mask = 0x07c0; b_mask = 0x003e;
          break;
        case 16:
          endianness = G_BYTE_ORDER;
          r_mask = 0xf800; g_mask = 0x07e0; b_mask = 0x001f;
          break;
        case 24:
          endianness = G_BIG_ENDIAN;
          r_mask = R_MASK_24; g_mask = G_MASK_24; b_mask = B_MASK_24;
          break;
        case 32:
          endianness = G_BIG_ENDIAN;
          r_mask = R_MASK_32; g_mask = G_MASK_32; b_mask = B_MASK_32;
          break;
      }
      caps = GST_CAPS_NEW("xviddec_src_pad_rgb",
                          "video/x-raw-rgb",
                            "width",      GST_PROPS_INT(xviddec->width),
                            "height",     GST_PROPS_INT(xviddec->height),
                            "depth",      GST_PROPS_INT(fmt_list[i].depth),
                            "bpp",        GST_PROPS_INT(fmt_list[i].bpp),
                            "endianness", GST_PROPS_INT(endianness),
                            "red_mask",   GST_PROPS_INT(r_mask),
                            "green_mask", GST_PROPS_INT(g_mask),
                            "blue_mask",  GST_PROPS_INT(b_mask),
			    "framerate",  GST_PROPS_FLOAT(xviddec->fps),
                            NULL);
    } else {
      caps = GST_CAPS_NEW("xviddec_src_pad_yuv",
                          "video/x-raw-yuv",
                            "width",      GST_PROPS_INT(xviddec->width),
                            "height",     GST_PROPS_INT(xviddec->height),
                            "format",     GST_PROPS_FOURCC(fmt_list[i].fourcc),
			    "framerate",  GST_PROPS_FLOAT(xviddec->fps),
                            NULL);
    }

    if ((ret = gst_pad_try_set_caps(xviddec->srcpad, caps)) > 0) {
      xviddec->csp = fmt_list[i].csp;
      xviddec->bpp = fmt_list[i].bpp;
      if (gst_xviddec_setup(xviddec))
        return GST_PAD_LINK_OK;
    } else if (ret == GST_PAD_LINK_DELAYED) {
      return ret; /* don't try further (yet) */
    }
  }

  /* if we got here - it's not good */
  return GST_PAD_LINK_REFUSED;
}


static GstPadLinkReturn
gst_xviddec_connect (GstPad  *pad,
                     GstCaps *vscaps)
{
  GstXvidDec *xviddec;

  xviddec = GST_XVIDDEC(gst_pad_get_parent (pad));

  /* if there's something old around, remove it */
  if (xviddec->handle) {
    gst_xviddec_unset(xviddec);
  }

  /* we are not going to act on variable caps */
  if (!GST_CAPS_IS_FIXED(vscaps))
    return GST_PAD_LINK_DELAYED;

  /* if we get here, we know the input is xvid. we
   * only need to bother with the output colorspace */
  gst_caps_get_int(vscaps, "width", &xviddec->width);
  gst_caps_get_int(vscaps, "height", &xviddec->height);
  gst_caps_get_float(vscaps, "framerate", &xviddec->fps);

  return gst_xviddec_negotiate(xviddec);
}


gboolean
gst_xviddec_plugin_init (GstPlugin *plugin)
{
  if (!gst_element_register (plugin, "xviddec", GST_RANK_PRIMARY, GST_TYPE_XVIDDEC))
    return FALSE;

  return TRUE;
}