/* GStreamer
 *
 * Copyright (C) 2006 Lutz Mueller <lutz@topfrose.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 <config.h>

#include <gst/gstelement.h>
#include <gst/gsterror.h>
#include <gst/gstinfo.h>
#include <gst/gstutils.h>
#include <gst/audio/audio.h>

#include <dlfcn.h>
#include <string.h>

#include "gstrealaudiodec.h"

GST_DEBUG_CATEGORY_STATIC (real_audio_dec_debug);
#define GST_CAT_DEFAULT real_audio_dec_debug

static GstElementDetails real_audio_dec_details = GST_ELEMENT_DETAILS (
    "REAL audio decoder",
    "Codec/Decoder", "Decoder for REAL audio streams",
    "Lutz Mueller <lutz@topfrose.de>");

static GstStaticPadTemplate snk_t =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
    GST_STATIC_CAPS (
      "audio/x-pn-realaudio; "
      "audio/x-sipro "));
static GstStaticPadTemplate src_t =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw-int, "
      "width = (int) [ 1, MAX ], "
      "depth = (int) [ 1, MAX ], "
      "rate = (int) [ 1, MAX ], "
      "channels = (int) [ 1, MAX ]"));

#define DEFAULT_PATH_RACOOK "/usr/lib/win32/cook.so.6.0"
#define DEFAULT_PATH_RAATRK "/usr/lib/win32/atrk.so.6.0"
#define DEFAULT_PATH_RA14_4 "/usr/lib/win32/14_4.so.6.0"
#define DEFAULT_PATH_RA28_8 "/usr/lib/win32/28_8.so.6.0"
#define DEFAULT_PATH_RASIPR "/usr/lib/win32/sipr.so.6.0"
#define DEFAULT_PWD "Ardubancel Quazanga"

enum {
  PROP_0,
  PROP_PATH_RACOOK,
  PROP_PATH_RAATRK,
  PROP_PATH_RA14_4,
  PROP_PATH_RA28_8,
  PROP_PATH_RASIPR,
  PROP_PASSWORD
};

typedef enum {
  GST_REAL_AUDIO_DEC_VERSION_COOK = 8,
  GST_REAL_AUDIO_DEC_VERSION_ATRK = 3,
  GST_REAL_AUDIO_DEC_VERSION_14_4 = 4,
  GST_REAL_AUDIO_DEC_VERSION_28_8 = 5,
  GST_REAL_AUDIO_DEC_VERSION_SIPR = 6
} GstRealAudioDecVersion;

typedef struct {
  guint16  (*RACloseCodec) (void*);
  guint16  (*RADecode)     (void *, gchar *, guint16, char *, guint *, gint16);
  guint16  (*RAFlush)      (guint16, guint16, guint16);
  guint16  (*RAFreeDecoder)       (void *);
  void *   (*RAGetFlavorProperty) (void *, guint, guint16, gint *);
  guint16  (*RAInitDecoder)       (void *, void *);
  guint16  (*RAOpenCodec2)        (void *);
  guint16  (*RASetFlavor)         (void *, guint16);
  void     (*SetDLLAccessPath)    (gchar *);
  void     (*RASetPwd)            (gchar*, gchar*);
} RealFunctions;

struct _GstRealAudioDec
{
  GstElement parent;

  GstPad *src, *snk;

  /* Caps */
  GstRealAudioDecVersion version;
  guint width, height, leaf_size, packet_size, channels, rate;

  /* State */
  gchar *frame;
  guint16 frame_size;

  /* Hooks */
  gpointer handle;
  RealFunctions funcs;

  /* Used by the REAL library. */
  gpointer context;

  /* Properties */
  gchar *path_racook, *path_raatrk, *path_ra14_4, *path_ra28_8, *path_rasipr;
  gchar *pwd;
};

struct _GstRealAudioDecClass
{
  GstElementClass parent_class;
};

GST_BOILERPLATE (GstRealAudioDec, gst_real_audio_dec, GstElement, GST_TYPE_ELEMENT);

static GstFlowReturn
gst_real_audio_dec_chain (GstPad * pad, GstBuffer * in)
{
  GstRealAudioDec *dec = GST_REAL_AUDIO_DEC (GST_PAD_PARENT (pad));
  guint w, h, len, sample_count;
  GstBuffer *out = NULL;
  guint16 res;
  GstFlowReturn ret = GST_FLOW_OK;
  GstClockTime timestamp = GST_BUFFER_TIMESTAMP (in);

  GST_DEBUG_OBJECT (dec, "Got buffer with timestamp %" GST_TIME_FORMAT,
      GST_TIME_ARGS (timestamp));
  for (h = 0; h < dec->height; h++)
    for (w = 0; w < dec->packet_size / dec->leaf_size; w++)
      memcpy (dec->frame + dec->leaf_size *
          (dec->height * w + ((dec->height + 1) / 2) * (h & 1) + (h >> 1)),
          GST_BUFFER_DATA (in) + dec->leaf_size *
          (h * dec->packet_size / dec->leaf_size + w), dec->leaf_size);
  gst_buffer_unref (in);
  for (w = 0; w < dec->frame_size; w += dec->packet_size) {
    if ((ret = gst_pad_alloc_buffer (dec->src,
            GST_CLOCK_TIME_TO_FRAMES (timestamp, dec->rate),
            dec->width * dec->height * dec->leaf_size,
            GST_PAD_CAPS (dec->src), &out)) != GST_FLOW_OK)
      break;
    res = dec->funcs.RADecode (dec->context, dec->frame + w, dec->packet_size, 
        (gchar *) GST_BUFFER_DATA (out), &len, -1);
    if (res)
      goto could_not_decode;
    sample_count = len / (dec->leaf_size * dec->height);
    GST_BUFFER_TIMESTAMP (out) = timestamp;
    GST_BUFFER_OFFSET_END (out) = GST_BUFFER_OFFSET (out) + sample_count;
    GST_BUFFER_DURATION (out) = sample_count * GST_SECOND / dec->rate;
    if ((ret = gst_pad_push (dec->src, out)) != GST_FLOW_OK)
      break;
  }
  return ret;

  /* Errors */
could_not_decode:
  gst_buffer_unref (out);
  GST_ELEMENT_ERROR (dec, STREAM, DECODE, ("Could not decode buffer (%i).",
        res), (NULL));
  return GST_FLOW_ERROR;
}

static gboolean
gst_real_audio_dec_setcaps (GstPad * pad, GstCaps * caps)
{
  GstRealAudioDec *dec = GST_REAL_AUDIO_DEC (GST_PAD_PARENT (pad));
  GstStructure *s = gst_caps_get_structure (caps, 0);
  gchar *path;
  gint version, flavor, channels, rate, leaf_size, packet_size, width, height;
  guint16 res, frame_size;
  guint8 data[28];
  gchar *frame;
  gboolean bres;
  const GValue *v;
  GstBuffer *buf = NULL;
  const gchar *name = gst_structure_get_name (s);
  gpointer context = NULL, handle;
  RealFunctions funcs;

  if (!strcmp (name, "audio/x-sipro"))
    version = GST_REAL_AUDIO_DEC_VERSION_SIPR;
  else {
    if (!gst_structure_get_int (s, "raversion", &version))
      goto missing_keys;
  }

  if (!gst_structure_get_int (s, "flavor", &flavor) ||
      !gst_structure_get_int (s, "channels", &channels) ||
      !gst_structure_get_int (s, "width", &width) ||
      !gst_structure_get_int (s, "rate", &rate) ||
      !gst_structure_get_int (s, "height", &height) ||
      !gst_structure_get_int (s, "leaf_size", &leaf_size) ||
      !gst_structure_get_int (s, "packet_size", &packet_size))
    goto missing_keys;
  if ((v = gst_structure_get_value (s, "codec_data")))
    buf = g_value_peek_pointer (v);

  switch (version) {
  case GST_REAL_AUDIO_DEC_VERSION_COOK:
    path = dec->path_racook ? dec->path_racook : DEFAULT_PATH_RACOOK;
    break;
  case GST_REAL_AUDIO_DEC_VERSION_ATRK:
    path = dec->path_raatrk ? dec->path_raatrk : DEFAULT_PATH_RAATRK;
    break;
  case GST_REAL_AUDIO_DEC_VERSION_14_4:
    path = dec->path_ra14_4 ? dec->path_ra14_4 : DEFAULT_PATH_RA14_4;
    break;
  case GST_REAL_AUDIO_DEC_VERSION_28_8:
    path = dec->path_ra28_8 ? dec->path_ra28_8 : DEFAULT_PATH_RA28_8;
    break;
  case GST_REAL_AUDIO_DEC_VERSION_SIPR:
    path = dec->path_rasipr ? dec->path_rasipr : DEFAULT_PATH_RASIPR;
    break;
  default:
    goto unknown_version;
  }

  handle = dlopen (path, RTLD_LAZY);
  if (!handle)
    goto could_not_open;
  funcs.RACloseCodec        = dlsym (handle, "RACloseCodec");
  funcs.RADecode            = dlsym (handle, "RADecode");
  funcs.RAFlush             = dlsym (handle, "RAFlush");
  funcs.RAFreeDecoder       = dlsym (handle, "RAFreeDecoder");
  funcs.RAGetFlavorProperty = dlsym (handle, "RAGetFlavorProperty");
  funcs.RAOpenCodec2        = dlsym (handle, "RAOpenCodec2");
  funcs.RAInitDecoder       = dlsym (handle, "RAInitDecoder");
  funcs.RASetFlavor         = dlsym (handle, "RASetFlavor");
  funcs.SetDLLAccessPath    = dlsym (handle, "SetDLLAccessPath");
  funcs.RASetPwd            = dlsym (handle, "RASetPwd");
  if (!(funcs.RACloseCodec        && funcs.RADecode      &&
        funcs.RAFlush             && funcs.RAFreeDecoder &&
        funcs.RAGetFlavorProperty && funcs.RAOpenCodec2  &&
        funcs.RAInitDecoder       && funcs.RASetFlavor))
    goto could_not_load;

  res = funcs.RAOpenCodec2 (&context);
  if (res)
    goto could_not_initialize;

  GST_WRITE_UINT32_LE (data + 0, rate);
  GST_WRITE_UINT16_LE (data + 4, width);
  GST_WRITE_UINT16_LE (data + 6, channels);
  GST_WRITE_UINT32_LE (data + 8, 100);
  GST_WRITE_UINT32_LE (data + 12, leaf_size);
  GST_WRITE_UINT32_LE (data + 16, packet_size);
  GST_WRITE_UINT32_LE (data + 20, buf ? GST_BUFFER_SIZE (buf) : 0);
  GST_WRITE_UINT32_LE (data + 24, buf ? GST_BUFFER_DATA (buf) : 0);

  res = funcs.RAInitDecoder (context, data);
  if (res)
    goto could_not_initialize;

  if (funcs.RASetPwd)
    funcs.RASetPwd (dec->context, dec->pwd ? dec->pwd : DEFAULT_PWD);

  res = funcs.RASetFlavor (context, flavor);
  if (res)
    goto could_not_initialize;

  frame_size = packet_size * height;
  frame = g_new (gchar, frame_size);
  if (!frame)
    goto could_not_allocate;

  caps = gst_caps_new_simple ("audio/x-raw-int",
      "endianness", G_TYPE_INT, G_BYTE_ORDER,
      "width", G_TYPE_INT, width,
      "depth", G_TYPE_INT, height,
      "rate", G_TYPE_INT, rate,
      "channels", G_TYPE_INT, channels,
      "signed", G_TYPE_BOOLEAN, TRUE,
      NULL);
  bres = gst_pad_set_caps (GST_PAD (dec->src), caps);
  gst_caps_unref (caps);
  if (!bres)
    goto could_not_set_caps;

  dec->width = width;
  dec->height = height;
  dec->version = version;
  dec->leaf_size = leaf_size;
  dec->packet_size = packet_size;
  dec->channels = channels;
  dec->rate = rate;
  if (dec->context)
    dec->funcs.RAFreeDecoder (dec->context);
  dec->context = context;
  if (dec->handle)
    dlclose (dec->handle);
  dec->handle = handle;
  dec->funcs = funcs;
  if (dec->frame)
    g_free (dec->frame);
  dec->frame = frame;
  dec->frame_size = frame_size;

  return TRUE;

missing_keys:
  GST_DEBUG_OBJECT (dec, "Could not find all necessary keys in structure.");
  return FALSE;
unknown_version:
  GST_DEBUG_OBJECT (dec, "Cannot handle version %i.", version);
  return FALSE;
could_not_open:
  GST_DEBUG_OBJECT (dec, "Could not open library '%s'.", path);
  return FALSE;
could_not_load:
  dlclose (handle);
  GST_DEBUG_OBJECT (dec, "Could not load all symbols.");
  return FALSE;
could_not_initialize:
  if (context)
    funcs.RAFreeDecoder (context);
  dlclose (handle);
  GST_DEBUG_OBJECT (dec, "Initialization of REAL driver failed (%i).", res);
  return FALSE;
could_not_allocate:
  if (context)
    funcs.RAFreeDecoder (context);
  dlclose (handle);
  GST_DEBUG_OBJECT (dec, "Could not allocate buffer.");
  return FALSE;
could_not_set_caps:
  g_free (frame);
  if (context)
    funcs.RAFreeDecoder (context);
  dlclose (handle);
  GST_DEBUG_OBJECT (dec, "Could not convince peer to accept dimensions "
      "%i x %i.", dec->width, dec->height);
  return FALSE;
}

static void
gst_real_audio_dec_init (GstRealAudioDec * dec, GstRealAudioDecClass * klass)
{
  dec->snk = gst_pad_new_from_template (
      gst_static_pad_template_get (&snk_t), "sink");
  gst_pad_set_setcaps_function (dec->snk, gst_real_audio_dec_setcaps);
  gst_pad_set_chain_function (dec->snk, gst_real_audio_dec_chain);
  gst_element_add_pad (GST_ELEMENT (dec), dec->snk);
  dec->src = gst_pad_new_from_template (
      gst_static_pad_template_get (&src_t), "src");
  gst_element_add_pad (GST_ELEMENT (dec), dec->src);
}

static void
gst_real_audio_dec_base_init (gpointer g_class)
{
  GstElementClass *ec = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&snk_t));
  gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&src_t));
  gst_element_class_set_details (ec, &real_audio_dec_details);
}

static GstStateChangeReturn
gst_real_audio_dec_change_state (GstElement * element, GstStateChange transition)
{
  GstStateChangeReturn ret;

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      break;
    default:
      break;
  }
  return ret;
}

static void
gst_real_audio_dec_finalize (GObject *object)
{
  GstRealAudioDec *dec = GST_REAL_AUDIO_DEC (object);

  if (dec->context) {
    dec->funcs.RAFreeDecoder (dec->context);
    dec->context = NULL;
  }
  if (dec->handle) {
    dlclose (dec->handle);
    dec->handle = NULL;
  }

  if (dec->frame) {
    g_free (dec->frame);
    dec->frame = NULL;
  }

  if (dec->path_racook) {
    g_free (dec->path_racook);
    dec->path_racook = NULL;
  }
  if (dec->path_raatrk) {
    g_free (dec->path_raatrk);
    dec->path_raatrk = NULL;
  }
  if (dec->path_ra14_4) {
    g_free (dec->path_ra14_4);
    dec->path_ra14_4 = NULL;
  }
  if (dec->path_ra28_8) {
    g_free (dec->path_ra28_8);
    dec->path_ra28_8 = NULL;
  }
  if (dec->path_rasipr) {
    g_free (dec->path_rasipr);
    dec->path_rasipr = NULL;
  }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_real_audio_dec_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstRealAudioDec *dec = GST_REAL_AUDIO_DEC (object);

  switch (prop_id) {
  case PROP_PATH_RACOOK:
    if (dec->path_racook)
      g_free (dec->path_racook);
    dec->path_racook = g_strdup (g_value_get_string (value));
    break;
  case PROP_PATH_RAATRK:
    if (dec->path_raatrk)
      g_free (dec->path_raatrk);
    dec->path_raatrk = g_strdup (g_value_get_string (value));
    break;
  case PROP_PATH_RA14_4:
    if (dec->path_ra14_4)
      g_free (dec->path_ra14_4);
    dec->path_ra14_4 = g_strdup (g_value_get_string (value));
    break;
  case PROP_PATH_RA28_8:
    if (dec->path_ra28_8)
      g_free (dec->path_ra28_8);
    dec->path_ra28_8 = g_strdup (g_value_get_string (value));
    break;
  case PROP_PATH_RASIPR:
    if (dec->path_rasipr)
      g_free (dec->path_rasipr);
    dec->path_rasipr = g_strdup (g_value_get_string (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
gst_real_audio_dec_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstRealAudioDec *dec = GST_REAL_AUDIO_DEC (object);

  switch (prop_id) {
  case PROP_PATH_RACOOK:
    g_value_set_string (value, dec->path_racook ? dec->path_racook :
        DEFAULT_PATH_RACOOK);
    break;
  case PROP_PATH_RAATRK:
    g_value_set_string (value, dec->path_raatrk ? dec->path_raatrk :
        DEFAULT_PATH_RAATRK);
    break;
  case PROP_PATH_RA14_4:
    g_value_set_string (value, dec->path_ra14_4 ? dec->path_ra14_4 :
        DEFAULT_PATH_RA14_4);
    break;
  case PROP_PATH_RA28_8:
    g_value_set_string (value, dec->path_ra28_8 ? dec->path_ra28_8 :
        DEFAULT_PATH_RA28_8);
    break;
  case PROP_PATH_RASIPR:
    g_value_set_string (value, dec->path_rasipr ? dec->path_rasipr :
        DEFAULT_PATH_RASIPR);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
gst_real_audio_dec_class_init (GstRealAudioDecClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  
  object_class->set_property = gst_real_audio_dec_set_property;
  object_class->get_property = gst_real_audio_dec_get_property;
  object_class->finalize = gst_real_audio_dec_finalize;

  element_class->change_state = gst_real_audio_dec_change_state;

  g_object_class_install_property (object_class, PROP_PATH_RACOOK,
      g_param_spec_string ("path_racook", "Path to cook driver",
        "Path to cook driver", DEFAULT_PATH_RACOOK, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PATH_RAATRK,
      g_param_spec_string ("path_raatrk", "Path to atrk driver",
        "Path to atrk driver", DEFAULT_PATH_RAATRK, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PATH_RA14_4,
      g_param_spec_string ("path_ra14_4", "Path to 14_4 driver",
        "Path to 14_4 driver", DEFAULT_PATH_RA14_4, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PATH_RA28_8,
      g_param_spec_string ("path_ra28_8", "Path to 28_8 driver",
        "Path to 28_8 driver", DEFAULT_PATH_RA28_8, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PATH_RASIPR,
      g_param_spec_string ("path_rasipr", "Path to sipr driver",
        "Path to sipr driver", DEFAULT_PATH_RASIPR, G_PARAM_READWRITE));
  g_object_class_install_property (object_class, PROP_PASSWORD,
      g_param_spec_string ("password", "Password",
        "Password", DEFAULT_PWD, G_PARAM_READWRITE));

  GST_DEBUG_CATEGORY_INIT (real_audio_dec_debug, "realaudiodec", 0,
      "REAL audio decoder");
}

/* These functions are needed by the plugins */

void * __builtin_new (guint32 size);
void   __builtin_delete (void * p);
void * __builtin_vec_new (guint32 size);
void   __builtin_vec_delete (void * p);
void   __pure_virtual (void);

void *
__builtin_new (guint32 size)
{
  return g_new (guint32, size);
}

void
__builtin_delete (void * p)
{
  g_free (p);
}

void *
__builtin_vec_new (guint32 size)
{
  return g_new (guint32, size);
}

void
__builtin_vec_delete (void * p)
{
  g_free (p);
}

void
__pure_virtual (void)
{
}
