/* GStreamer
 * Copyright (C) 2004 Benjamin Otte <in7y118@public.uni-hamburg.de>
 *
 * 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.
 */

#include <gst/gst.h>
#include "gstxine.h"
#include <xine/xine_internal.h>
#include <xine/plugin_catalog.h>

#define GST_TYPE_XINE_AUDIO_DEC \
  (gst_xine_audio_dec_get_type())
#define GST_XINE_AUDIO_DEC(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_XINE_AUDIO_DEC,GstXineAudioDec))
#define GST_XINE_AUDIO_DEC_GET_CLASS(obj) \
  (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_XINE_AUDIO_DEC, GstXineAudioDecClass))
#define GST_XINE_AUDIO_DEC_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_XINE_AUDIO_DEC,GstXineAudioDecClass))
#define GST_IS_XINE_AUDIO_DEC(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_XINE_AUDIO_DEC))
#define GST_IS_XINE_AUDIO_DEC_CLASS(obj) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_XINE_AUDIO_DEC))

GType gst_xine_audio_dec_get_type (void);

typedef struct _GstXineAudioDec      GstXineAudioDec;
typedef struct _GstXineAudioDecClass GstXineAudioDecClass;

struct _GstXineAudioDec
{
  GstXine		parent;

  GstPad *		sinkpad;
  GstPad *		srcpad;

  audio_decoder_t *	decoder;
  guint32		format;
  xine_waveformatex	wave;
  gboolean		setup;
};

struct _GstXineAudioDecClass 
{
  GstXineClass		parent_class;

  plugin_node_t *	plugin_node;
};

/*** xine audio driver wrapper ************************************************/
 
typedef struct {
  xine_ao_driver_t	driver;
  GstXineAudioDec *   	xine;
  gboolean		open;
} GstXineAudioDriver;

static guint32
_driver_get_capabilities (xine_ao_driver_t *driver)
{
  /* FIXME: add more when gst handles more than 2 channels */
  return AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO | AO_CAP_8BITS;
}

static gint
_driver_get_property (xine_ao_driver_t *driver, int property)
{
  return 0;
}

static gint
_driver_set_property (xine_ao_driver_t * driver, int property, int value)
{
  return ~value;
}

static gint
_driver_open (xine_ao_driver_t *driver, xine_stream_t *stream, guint32 bits, guint32 rate, int mode)
{
  GstCaps *caps;
  GstXineAudioDriver *xine = ((GstXineAudioDriver *) driver);
  
  caps = gst_caps_new_simple ("audio/x-raw-int",
      "endianness", G_TYPE_INT,	    G_BYTE_ORDER,
      "width",	    G_TYPE_INT,	    (gint) bits,
      "depth",	    G_TYPE_INT,	    (gint) bits,
      "signed",	    G_TYPE_BOOLEAN, (bits == 8) ? FALSE : TRUE,
      "channels",   G_TYPE_INT,	    (mode | AO_CAP_MODE_STEREO) ? 2 : 1,
      "rate",	    G_TYPE_INT,	    rate,
      NULL);
      
  if (!gst_pad_set_explicit_caps (xine->xine->srcpad, caps)) {
    gst_caps_free (caps);
    driver->open = FALSE;
    return -1;
  }
  
  xine->open = TRUE;
  gst_caps_free (caps);
  return rate;
}

static void
_driver_close (xine_ao_driver_t *driver, xine_stream_t *stream)
{
  /* FIXME: unset explicit caps here? And how? */
  driver->open = FALSE;
}

static void
_driver_exit (xine_ao_driver_t *driver)
{
  g_free (driver);
}

static int
_driver_control (xine_ao_driver_t *driver, int cmd, ...)
{
  return 0;
}

static void
_driver_flush (xine_ao_driver_t *driver)
{
}

static int
_driver_status (xine_ao_driver_t *driver, xine_stream_t *stream, uint32_t *bits, uint32_t *rate, int *mode)
{
  const GstCaps *caps;
  GstStructure *structure;
  gint temp;
  GstXineAudioDriver *xine = (GstXineAudioDriver *) driver;
  
  if (xine->open == FALSE || !(caps = gst_pad_get_negotiated_caps (xine->xine->srcpad)))
    return 0;

  structure = gst_caps_get_structure (caps, 0);
  *bits = 0; /* FIXME */
  if (!gst_structure_get_int (structure, "rate", &temp)) {
    g_assert_not_reached (); /* may never happen with negotiated caps */
    return 0;
  }
  *rate = temp;
  if (!gst_structure_get_int (structure, "channels", &temp)) {
    g_assert_not_reached (); /* may never happen with negotiated caps */
    return 0;
  }
  *mode = (temp == 2) ? AO_CAP_MODE_STEREO : AO_CAP_MODE_MONO;
  if (!gst_structure_get_int (structure, "width", &temp)) {
    g_assert_not_reached (); /* may never happen with negotiated caps */
    return 0;
  }
  if (temp == 8)
    *mode |= AO_CAP_8BITS;

  return 1;
}

#define _DRIVER_BUFFER_SIZE 4096
static audio_buffer_t *
_driver_get_buffer (xine_ao_driver_t *driver)
{
  GstXineAudioDriver *xine = (GstXineAudioDriver *) driver;
  audio_buffer_t *audio = g_new0 (audio_buffer_t, 1); 

  audio->mem = g_malloc (_DRIVER_BUFFER_SIZE);
  audio->mem_size = _DRIVER_BUFFER_SIZE;
  audio->stream = gst_xine_get_stream (GST_XINE (xine->xine));
  /* FIXME: fill more fields */
  
  return audio;
}

static void 
_driver_put_buffer (xine_ao_driver_t *driver, audio_buffer_t *audio, xine_stream_t *stream)
{
  GstXineAudioDriver *xine = (GstXineAudioDriver *) driver;
  GstBuffer *buffer;
  
  buffer = gst_buffer_new ();
  GST_BUFFER_DATA (buffer) = (guint8 *) audio->mem;
  GST_BUFFER_SIZE (buffer) = audio->mem_size;
  GST_BUFFER_MAXSIZE (buffer) = audio->mem_size;
  /* FIXME: fill more fields */
  g_free (audio);
  gst_pad_push (xine->xine->srcpad, GST_DATA (buffer));
}
  
static xine_ao_driver_t *
_gst_xine_audio_dec_create_audio_driver (GstXine *xine)
{
  GstXineAudioDriver *driver = g_new (GstXineAudioDriver, 1);

  driver->xine = GST_XINE_AUDIO_DEC (xine);
  driver->open = FALSE;
  
  driver->driver.get_buffer	    = _driver_get_buffer;
  driver->driver.put_buffer	    = _driver_put_buffer;
  driver->driver.get_capabilities   = _driver_get_capabilities;
  driver->driver.get_property       = _driver_get_property;
  driver->driver.set_property       = _driver_set_property;
  driver->driver.open               = _driver_open;
  driver->driver.close              = _driver_close;
  driver->driver.exit               = _driver_exit;
  driver->driver.control	    = _driver_control;
  driver->driver.flush		    = _driver_flush;
  driver->driver.status		    = _driver_status;
  
  return (xine_ao_driver_t *) driver;
}

/** GstXineAudioDec ***********************************************************/

GST_BOILERPLATE (GstXineAudioDec, gst_xine_audio_dec, GstXine, GST_TYPE_XINE)

static void	gst_xine_audio_dec_chain	(GstPad *pad, GstData *in);
static GstElementStateReturn
		gst_xine_audio_dec_change_state	(GstElement *element);

/* this function handles the link with other plug-ins */
static GstPadLinkReturn
gst_xine_audio_dec_sink_link (GstPad *pad, const GstCaps *caps)
{
  guint temp;
  GstStructure *structure;
  GstXineAudioDec *xine = GST_XINE_AUDIO_DEC (gst_object_get_parent (GST_OBJECT (pad)));

  xine->format = gst_xine_get_format_for_caps (caps);
  if (xine->format == 0) return GST_PAD_LINK_REFUSED;

  /* get setup data */
  xine->setup = FALSE;
  structure = gst_caps_get_structure (caps, 0);
  if (gst_structure_get_int (structure, "channels", &temp))
    xine->wave.nChannels = temp;
  if (gst_structure_get_int (structure, "rate", &temp))
    xine->wave.nSamplesPerSec = temp;
  xine->wave.wBitsPerSample = 16; /* FIXME: how do we figure this thing out? */
  /* FIXME: fill wave header better */

  return GST_PAD_LINK_OK;
}

static void
gst_xine_audio_dec_base_init (gpointer g_class)
{
}

static void
gst_xine_audio_dec_class_init (GstXineAudioDecClass *klass)
{
  GstXineClass *xine = GST_XINE_CLASS (klass);
  GstElementClass *element = GST_ELEMENT_CLASS (klass);

  element->change_state = gst_xine_audio_dec_change_state;

  xine->create_audio_driver = _gst_xine_audio_dec_create_audio_driver;
}

static void
gst_xine_audio_dec_init (GstXineAudioDec *xine)
{
  xine->setup = FALSE;
}

static void
gst_xine_audio_dec_event (GstXineAudioDec *xine, GstEvent *event)
{
  gst_pad_event_default (xine->sinkpad, event);
}

static void
gst_xine_audio_dec_chain (GstPad *pad, GstData *in)
{
  buf_element_t buffer = { 0, };
  GstXineAudioDec *xine = GST_XINE_AUDIO_DEC (gst_object_get_parent (GST_OBJECT (pad)));

  if (GST_IS_EVENT (in)) {
    gst_xine_audio_dec_event (xine, GST_EVENT (in));
    return;
  }

  if (xine->format == 0) {
    /* no caps yet */
    GST_ELEMENT_ERROR (xine, CORE, NEGOTIATION, (NULL), ("buffer sent before doing caps nego"));
    gst_data_unref (in);
    return;
  }

  if (!xine->setup) {
    buf_element_t element = { 0, };
    guint8 stsd[150] = { 0, };
    guint temp;
    GstStructure *structure;
    /* sent setup header */
    element.type = xine->format;
    element.decoder_flags = BUF_FLAG_HEADER;
    element.decoder_info[0] = 0;
    element.decoder_info[1] = xine->wave.nSamplesPerSec;
    element.decoder_info[2] = xine->wave.wBitsPerSample;
    element.decoder_info[3] = xine->wave.nChannels;
    element.content = (guchar *) &xine->wave;
    element.size = sizeof (xine_waveformatex);
    xine->decoder->decode_data (xine->decoder, &element);
    /* send stsd emulation to the decoder */
    /* FIXME: qdm2 only right now */
    g_assert (gst_pad_get_negotiated_caps (xine->sinkpad));
    structure = gst_caps_get_structure (gst_pad_get_negotiated_caps (xine->sinkpad), 0);
    *((guint32 *) &stsd[56]) = GUINT32_TO_BE (12);
    memcpy (&stsd[60], "frmaQDM2", 8);
    *((guint32 *) &stsd[68]) = GUINT32_TO_BE (36);
    memcpy (&stsd[72], "QDCA", 4);
    *((guint32 *) &stsd[76]) = GUINT32_TO_BE (1);
    if (!gst_structure_get_int (structure, "channels", &temp))
      temp = 0;
    *((guint32 *) &stsd[80]) = GUINT32_TO_BE (temp);
    if (!gst_structure_get_int (structure, "rate", &temp))
      temp = 0;
    *((guint32 *) &stsd[84]) = GUINT32_TO_BE (temp);
    if (!gst_structure_get_int (structure, "bitrate", &temp))
      temp = 0;
    *((guint32 *) &stsd[88]) = GUINT32_TO_BE (temp);
    if (!gst_structure_get_int (structure, "blocksize", &temp))
      temp = 0;
    *((guint32 *) &stsd[92]) = GUINT32_TO_BE (temp);
    *((guint32 *) &stsd[96]) = GUINT32_TO_BE (256);
    if (!gst_structure_get_int (structure, "framesize", &temp))
      temp = 0;
    *((guint32 *) &stsd[100]) = GUINT32_TO_BE (temp);
    *((guint32 *) &stsd[104]) = GUINT32_TO_BE (28);
    memcpy (&stsd[108], "QDCP", 4);
    *((guint32 *) &stsd[112]) = GUINT32_TO_BE (1065353216);
    *((guint32 *) &stsd[116]) = GUINT32_TO_BE (0);
    *((guint32 *) &stsd[120]) = GUINT32_TO_BE (1065353216);
    *((guint32 *) &stsd[124]) = GUINT32_TO_BE (1065353216);
    *((guint32 *) &stsd[128]) = GUINT32_TO_BE (27);
    *((guint32 *) &stsd[132]) = GUINT32_TO_BE (8);
    *((guint32 *) &stsd[136]) = GUINT32_TO_BE (0);
    *((guint32 *) &stsd[140]) = GUINT32_TO_BE (24);
    gst_util_dump_mem (stsd, 144); 
    element.decoder_flags = BUF_FLAG_SPECIAL;
    element.decoder_info[1] = BUF_SPECIAL_STSD_ATOM;
    element.decoder_info[2] = 144;
    element.decoder_info[3] = 0;
    element.decoder_info_ptr[2] = stsd;
    element.size = 0;
    element.content = 0;
    xine->decoder->decode_data (xine->decoder, &element);
    
    xine->setup = TRUE;
  }
  
  gst_buffer_to_xine_buffer (&buffer, GST_BUFFER (in));
  buffer.type = xine->format;
  xine->decoder->decode_data (xine->decoder, &buffer);
  gst_data_unref (in);
}

static audio_decoder_t *
_load_decoder (GstXineAudioDec* dec)
{
  xine_stream_t *stream = gst_xine_get_stream (GST_XINE (dec));
  plugin_catalog_t *catalog = stream->xine->plugin_catalog;
  plugin_node_t *node = GST_XINE_AUDIO_DEC_GET_CLASS (dec)->plugin_node;
  audio_decoder_t *ret;
  
  /* FIXME: this is really hacky, but how to force xine to load a plugin? */
  /* how it works: xine can load a plugin for a particular stream type.
   * We just take one type, which should not have plugins attached to it,
   * attach our plugin and load it */
  g_assert (catalog->audio_decoder_map[DECODER_MAX - 1][0] == NULL);
  catalog->audio_decoder_map[DECODER_MAX - 1][0] = node;
  ret = _x_get_audio_decoder (stream, DECODER_MAX - 1);
  catalog->audio_decoder_map[DECODER_MAX - 1][0] = NULL;

  return ret;
}

static GstElementStateReturn
gst_xine_audio_dec_change_state (GstElement *element)
{
  GstXineAudioDec *xine = GST_XINE_AUDIO_DEC (element);
  
  switch (GST_STATE_TRANSITION (element)) {
    case GST_STATE_NULL_TO_READY:
      xine->decoder = _load_decoder (xine);
      if (!xine->decoder)
	return GST_STATE_FAILURE;
      break;
    case GST_STATE_READY_TO_PAUSED:
      break;
    case GST_STATE_PAUSED_TO_PLAYING:
      break;
    case GST_STATE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_PAUSED_TO_READY:
      break;
    case GST_STATE_READY_TO_NULL:
      xine->setup = FALSE;
      _x_free_audio_decoder (gst_xine_get_stream (GST_XINE (xine)), xine->decoder);
      break;
    default:
      GST_ERROR_OBJECT (element, "invalid state change");
      break;
  }
  
  return GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state, (element), GST_STATE_SUCCESS);
}

/** GstXineAudioDec subclasses ************************************************/

static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE (
  "src",
  GST_PAD_SRC,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS (
    "audio/x-raw-int, "
      "endianness = (int) BYTE_ORDER, "
      "signed = (boolean) TRUE, "
      "width = (int) 16, "
      "depth = (int) 16, "
      "rate = (int) [ 1, MAX ], "
      "channels = (int) [ 1, 2 ]; "
    "audio/x-raw-int, "
      "signed = (boolean) FALSE, "
      "width = (int) 8, "
      "depth = (int) 8, "
      "rate = (int) [ 1, MAX ], "
      "channels = (int) [ 1, 2 ]"
  )
);

static void
gst_xine_audio_dec_subclass_init (gpointer g_class, gpointer class_data)
{
  GstXineAudioDecClass *xine_class = GST_XINE_AUDIO_DEC_CLASS (g_class);
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  GstElementDetails details = GST_ELEMENT_DETAILS (
    NULL,
    "Filter/Decoder/Audio",
    NULL,
    "Benjamin Otte <otte@gnome.org>"
  );
  GstPadTemplate *template;
  guint i = 0;
  GstCaps *caps = gst_caps_new_empty ();
  decoder_info_t *dec;
  
  xine_class->plugin_node = class_data;
  dec = xine_class->plugin_node->info->special_info;
  details.longname = g_strdup_printf ("%s xine audio decoder", xine_class->plugin_node->info->id);
  details.description = g_strdup_printf ("decodes audio using the xine '%s' plugin", xine_class->plugin_node->info->id);
  gst_element_class_set_details (element_class, &details);
  g_free (details.longname);
  g_free (details.description);
  
  gst_element_class_add_pad_template (element_class, 
      gst_static_pad_template_get (&src_template));
  
  while (dec->supported_types[i] != 0) {
    const gchar *type_str = gst_xine_get_caps_for_format (dec->supported_types[i]);
    if (type_str) {
      gst_caps_append (caps, gst_caps_from_string (type_str));
    }
    i++;
  }
  template = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps);
  gst_element_class_add_pad_template (element_class, template);
}

static void
gst_xine_audio_dec_sub_init (GTypeInstance *instance, gpointer g_class)
{
  GstElementClass *klass = GST_ELEMENT_GET_CLASS (instance);
  GstXineAudioDec *xine = GST_XINE_AUDIO_DEC (instance);

  xine->sinkpad = gst_pad_new_from_template (
	gst_element_class_get_pad_template (klass, "sink"), "sink");
  gst_pad_set_link_function (xine->sinkpad, gst_xine_audio_dec_sink_link);
  gst_pad_set_chain_function (xine->sinkpad, gst_xine_audio_dec_chain);
  gst_element_add_pad (GST_ELEMENT (xine), xine->sinkpad);

  xine->srcpad = gst_pad_new_from_template (
	gst_element_class_get_pad_template (klass, "src"), "src");
  gst_pad_use_explicit_caps (xine->srcpad);
  gst_element_add_pad (GST_ELEMENT (xine), xine->srcpad);
}

gboolean
gst_xine_audio_dec_init_plugin (GstPlugin *plugin)
{
  GTypeInfo plugin_info =
  {
    sizeof (GstXineAudioDecClass),
    NULL,
    NULL,
    gst_xine_audio_dec_subclass_init,
    NULL,
    NULL,
    sizeof (GstXineAudioDec),
    0,
    gst_xine_audio_dec_sub_init,
  };
  xine_node_t *list;
  GstXineClass *klass;
    
  klass = g_type_class_ref (GST_TYPE_XINE);
  
  list = klass->xine->plugin_catalog->audio->first;
  while (list) {
    plugin_node_t *node = list->content;
    decoder_info_t *dec;
    guint format = 0;

    list = list->next;
    if (!node) continue;
    
    dec = node->info->special_info;
    while (dec->supported_types[format] != 0) {
      const gchar *caps = gst_xine_get_caps_for_format (dec->supported_types[format]);
      if (caps) {
	gchar *plugin_name = g_strdup_printf ("xineaudiodec_%s", node->info->id);
	gchar *type_name = g_strdup_printf ("GstXineAudioDec%s", node->info->id);
	GType type;
	plugin_info.class_data = node;
	type = g_type_register_static (GST_TYPE_XINE_AUDIO_DEC, type_name, &plugin_info, 0);
	g_free (type_name);
	if (!gst_element_register (plugin, plugin_name, 
	      MAX (GST_RANK_MARGINAL, GST_RANK_MARGINAL * dec->priority / 10 + 1), type)) {
	  g_free (plugin_name);
	  return FALSE;
	}
	g_free (plugin_name);
      }
      format++;
    }
  }

  g_type_class_unref (klass); 
  return TRUE;
}