/* GStreamer
 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
 *                    2000 Wim Taymans <wtay@chello.be>
 *
 * gstafparse.c: 
 *
 * 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
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>
#include "gstafparse.h"

/* AFParse signals and args */
enum
{
  /* FILL ME */
  SIGNAL_HANDOFF,
  LAST_SIGNAL
};

enum
{
  ARG_0
};

/* added a src factory function to force audio/raw MIME type */
static GstStaticPadTemplate afparse_src_factory =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw-int, "
        "rate = (int) [ 1, MAX ], "
        "channels = (int) [ 1, MAX ], "
        "endianness = (int) BYTE_ORDER, "
        "width = (int) { 8, 16 }, "
        "depth = (int) { 8, 16 }, " "signed = (boolean) { true, false }")
    );

static GstStaticPadTemplate afparse_sink_factory =
    GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-aiff; " "audio/x-wav; " "audio/x-au")
    );

static void gst_afparse_base_init (gpointer g_class);
static void gst_afparse_class_init (GstAFParseClass * klass);
static void gst_afparse_init (GstAFParse * afparse);

static gboolean gst_afparse_open_file (GstAFParse * afparse);
static void gst_afparse_close_file (GstAFParse * afparse);

static void gst_afparse_loop (GstElement * element);
static void gst_afparse_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_afparse_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static ssize_t gst_afparse_vf_read (AFvirtualfile * vfile, void *data,
    size_t nbytes);
static long gst_afparse_vf_length (AFvirtualfile * vfile);
static ssize_t gst_afparse_vf_write (AFvirtualfile * vfile, const void *data,
    size_t nbytes);
static void gst_afparse_vf_destroy (AFvirtualfile * vfile);
static long gst_afparse_vf_seek (AFvirtualfile * vfile, long offset,
    int is_relative);
static long gst_afparse_vf_tell (AFvirtualfile * vfile);

GType
gst_afparse_get_type (void)
{
  static GType afparse_type = 0;

  if (!afparse_type) {
    static const GTypeInfo afparse_info = {
      sizeof (GstAFParseClass),
      gst_afparse_base_init,
      NULL,
      (GClassInitFunc) gst_afparse_class_init,
      NULL,
      NULL,
      sizeof (GstAFParse),
      0,
      (GInstanceInitFunc) gst_afparse_init,
    };

    afparse_type =
        g_type_register_static (GST_TYPE_ELEMENT, "GstAFParse", &afparse_info,
        0);
  }
  return afparse_type;
}

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

  gst_element_class_add_static_pad_template (element_class,
      &afparse_src_factory);
  gst_element_class_add_static_pad_template (element_class,
      &afparse_sink_factory);

  gst_element_class_set_details_simple (element_class, "Audiofile demuxer",
      "Codec/Demuxer/Audio",
      "Audiofile parser for audio/raw",
      "Steve Baker <stevebaker_org@yahoo.co.uk>");
}

static void
gst_afparse_class_init (GstAFParseClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

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

  gobject_class->set_property = gst_afparse_set_property;
  gobject_class->get_property = gst_afparse_get_property;

}

static void
gst_afparse_init (GstAFParse * afparse)
{
  afparse->srcpad =
      gst_pad_new_from_template (gst_element_get_pad_template (GST_ELEMENT
          (afparse), "src"), "src");
  gst_pad_use_explicit_caps (afparse->srcpad);
  gst_element_add_pad (GST_ELEMENT (afparse), afparse->srcpad);

  afparse->sinkpad =
      gst_pad_new_from_template (gst_element_get_pad_template (GST_ELEMENT
          (afparse), "sink"), "sink");
  gst_element_add_pad (GST_ELEMENT (afparse), afparse->sinkpad);

  gst_element_set_loop_function (GST_ELEMENT (afparse), gst_afparse_loop);

  afparse->vfile = af_virtual_file_new ();
  afparse->vfile->closure = NULL;
  afparse->vfile->read = gst_afparse_vf_read;
  afparse->vfile->length = gst_afparse_vf_length;
  afparse->vfile->write = gst_afparse_vf_write;
  afparse->vfile->destroy = gst_afparse_vf_destroy;
  afparse->vfile->seek = gst_afparse_vf_seek;
  afparse->vfile->tell = gst_afparse_vf_tell;

  afparse->frames_per_read = 1024;
  afparse->curoffset = 0;
  afparse->seq = 0;

  afparse->file = NULL;
  /* default values, should never be needed */
  afparse->channels = 2;
  afparse->width = 16;
  afparse->rate = 44100;
  afparse->type = AF_FILE_WAVE;
  afparse->endianness_data = 1234;
  afparse->endianness_wanted = 1234;
  afparse->timestamp = 0LL;
}

static void
gst_afparse_loop (GstElement * element)
{
  GstAFParse *afparse;
  GstBuffer *buf;
  gint numframes = 0, frames_to_bytes, frames_per_read, bytes_per_read;
  guint8 *data;
  gboolean bypass_afread = TRUE;
  GstByteStream *bs;
  int s_format, v_format, s_width, v_width;

  afparse = GST_AFPARSE (element);

  afparse->vfile->closure = bs = gst_bytestream_new (afparse->sinkpad);

  /* just stop if we cannot open the file */
  if (!gst_afparse_open_file (afparse)) {
    gst_bytestream_destroy ((GstByteStream *) afparse->vfile->closure);
    gst_pad_push (afparse->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
    gst_element_set_eos (GST_ELEMENT (afparse));
    return;
  }

  /* if audiofile changes the data in any way, we have to access
   * the audio data via afReadFrames. Otherwise we can just access
   * the data directly. */
  afGetSampleFormat (afparse->file, AF_DEFAULT_TRACK, &s_format, &s_width);
  afGetVirtualSampleFormat (afparse->file, AF_DEFAULT_TRACK, &v_format,
      &v_width);
  if (afGetCompression != AF_COMPRESSION_NONE
      || afGetByteOrder (afparse->file,
          AF_DEFAULT_TRACK) != afGetVirtualByteOrder (afparse->file,
          AF_DEFAULT_TRACK) || s_format != v_format || s_width != v_width) {
    bypass_afread = FALSE;
  }

  if (bypass_afread) {
    GST_DEBUG ("will bypass afReadFrames\n");
  }

  frames_to_bytes = afparse->channels * afparse->width / 8;
  frames_per_read = afparse->frames_per_read;
  bytes_per_read = frames_per_read * frames_to_bytes;

  afSeekFrame (afparse->file, AF_DEFAULT_TRACK, 0);

  if (bypass_afread) {
    GstEvent *event = NULL;
    guint32 waiting;
    guint32 got_bytes;

    do {

      got_bytes = gst_bytestream_read (bs, &buf, bytes_per_read);
      if (got_bytes == 0) {
        /* we need to check for an event. */
        gst_bytestream_get_status (bs, &waiting, &event);
        if (event && GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
          gst_pad_push (afparse->srcpad,
              GST_DATA (gst_event_new (GST_EVENT_EOS)));
          gst_element_set_eos (GST_ELEMENT (afparse));
          break;
        }
      } else {
        GST_BUFFER_TIMESTAMP (buf) = afparse->timestamp;
        gst_pad_push (afparse->srcpad, GST_DATA (buf));
        if (got_bytes != bytes_per_read) {
          /* this shouldn't happen very often */
          /* FIXME calculate the timestamps based on the fewer bytes received */

        } else {
          afparse->timestamp += frames_per_read * 1E9 / afparse->rate;
        }
      }
    }
    while (TRUE);

  } else {
    do {
      buf = gst_buffer_new_and_alloc (bytes_per_read);
      GST_BUFFER_TIMESTAMP (buf) = afparse->timestamp;
      data = GST_BUFFER_DATA (buf);
      numframes =
          afReadFrames (afparse->file, AF_DEFAULT_TRACK, data, frames_per_read);

      /* events are handled in gst_afparse_vf_read so if there are no
       * frames it must be EOS */
      if (numframes < 1) {
        gst_buffer_unref (buf);

        gst_pad_push (afparse->srcpad,
            GST_DATA (gst_event_new (GST_EVENT_EOS)));
        gst_element_set_eos (GST_ELEMENT (afparse));
        break;
      }
      GST_BUFFER_SIZE (buf) = numframes * frames_to_bytes;
      gst_pad_push (afparse->srcpad, GST_DATA (buf));
      afparse->timestamp += numframes * 1E9 / afparse->rate;
    }
    while (TRUE);
  }
  gst_afparse_close_file (afparse);

  gst_bytestream_destroy ((GstByteStream *) afparse->vfile->closure);

}


static void
gst_afparse_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstAFParse *afparse;

  afparse = GST_AFPARSE (object);

  switch (prop_id) {
    default:
      break;
  }
}

static void
gst_afparse_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstAFParse *afparse;

  g_return_if_fail (GST_IS_AFPARSE (object));

  afparse = GST_AFPARSE (object);

  switch (prop_id) {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

gboolean
gst_afparse_plugin_init (GstPlugin * plugin)
{
  /* load audio support library */
  if (!gst_library_load ("gstaudio"))
    return FALSE;

  if (!gst_element_register (plugin, "afparse", GST_RANK_NONE,
          GST_TYPE_AFPARSE))
    return FALSE;

  return TRUE;
}

/* this is where we open the audiofile */
static gboolean
gst_afparse_open_file (GstAFParse * afparse)
{
  g_return_val_if_fail (!GST_OBJECT_FLAG_IS_SET (afparse, GST_AFPARSE_OPEN),
      FALSE);


  /* open the file */
  GST_DEBUG ("opening vfile %p\n", afparse->vfile);
  afparse->file = afOpenVirtualFile (afparse->vfile, "r", AF_NULL_FILESETUP);
  if (afparse->file == AF_NULL_FILEHANDLE) {
    /* this should never happen */
    g_warning ("ERROR: gstafparse: Could not open virtual file for reading\n");
    return FALSE;
  }

  GST_DEBUG ("vfile opened\n");
  /* get the audiofile audio parameters */
  {
    int sampleFormat, sampleWidth;

    afparse->channels = afGetChannels (afparse->file, AF_DEFAULT_TRACK);
    afGetSampleFormat (afparse->file, AF_DEFAULT_TRACK,
        &sampleFormat, &sampleWidth);
    switch (sampleFormat) {
      case AF_SAMPFMT_TWOSCOMP:
        afparse->is_signed = TRUE;
        break;
      case AF_SAMPFMT_UNSIGNED:
        afparse->is_signed = FALSE;
        break;
      case AF_SAMPFMT_FLOAT:
      case AF_SAMPFMT_DOUBLE:
        GST_DEBUG ("ERROR: float data not supported yet !\n");
    }
    afparse->rate = (guint) afGetRate (afparse->file, AF_DEFAULT_TRACK);
    afparse->width = sampleWidth;
    GST_DEBUG ("input file: %d channels, %d width, %d rate, signed %s\n",
        afparse->channels, afparse->width, afparse->rate,
        afparse->is_signed ? "yes" : "no");
  }

  /* set caps on src */
  /*FIXME: add all the possible formats, especially float ! */
  gst_pad_set_explicit_caps (afparse->srcpad,
      gst_caps_new_simple ("audio/x-raw-int",
          "endianness", G_TYPE_INT, G_BYTE_ORDER,
          "signed", G_TYPE_BOOLEAN, afparse->is_signed,
          "width", G_TYPE_INT, afparse->width,
          "depth", G_TYPE_INT, afparse->width,
          "rate", G_TYPE_INT, afparse->rate,
          "channels", G_TYPE_INT, afparse->channels, NULL));

  GST_OBJECT_FLAG_SET (afparse, GST_AFPARSE_OPEN);

  return TRUE;
}

static void
gst_afparse_close_file (GstAFParse * afparse)
{
  g_return_if_fail (GST_OBJECT_FLAG_IS_SET (afparse, GST_AFPARSE_OPEN));
  if (afCloseFile (afparse->file) != 0) {
    g_warning ("afparse: oops, error closing !\n");
  } else {
    GST_OBJECT_FLAG_UNSET (afparse, GST_AFPARSE_OPEN);
  }
}

static ssize_t
gst_afparse_vf_read (AFvirtualfile * vfile, void *data, size_t nbytes)
{
  GstByteStream *bs = (GstByteStream *) vfile->closure;
  guint8 *bytes = NULL;
  GstEvent *event = NULL;
  guint32 waiting;
  guint32 got_bytes;

  /*gchar        *debug_str; */

  got_bytes = gst_bytestream_peek_bytes (bs, &bytes, nbytes);

  while (got_bytes != nbytes) {
    /* handle events */
    gst_bytestream_get_status (bs, &waiting, &event);

    /* FIXME this event handling isn't right yet */
    if (!event) {
      /*g_print("no event found with %u bytes\n", got_bytes); */
      return 0;
    }
    switch (GST_EVENT_TYPE (event)) {
      case GST_EVENT_EOS:
        return 0;
      case GST_EVENT_FLUSH:
        GST_DEBUG ("flush");
        break;
      case GST_EVENT_DISCONTINUOUS:
        GST_DEBUG ("seek done");
        got_bytes = gst_bytestream_peek_bytes (bs, &bytes, nbytes);
        break;
      default:
        g_warning ("unknown event %d", GST_EVENT_TYPE (event));
        got_bytes = gst_bytestream_peek_bytes (bs, &bytes, nbytes);
    }
  }

  memcpy (data, bytes, got_bytes);
  gst_bytestream_flush_fast (bs, got_bytes);

  /*  debug_str = g_strndup((gchar*)bytes, got_bytes);
     g_print("read %u bytes: %s\n", got_bytes, debug_str);
   */
  return got_bytes;
}

static long
gst_afparse_vf_seek (AFvirtualfile * vfile, long offset, int is_relative)
{
  GstByteStream *bs = (GstByteStream *) vfile->closure;
  GstSeekType method;
  guint64 current_offset = gst_bytestream_tell (bs);

  if (!is_relative) {
    if ((guint64) offset == current_offset) {
      /* this seems to happen before every read - bad audiofile */
      return offset;
    }

    method = GST_SEEK_METHOD_SET;
  } else {
    if (offset == 0)
      return current_offset;
    method = GST_SEEK_METHOD_CUR;
  }

  if (gst_bytestream_seek (bs, (gint64) offset, method)) {
    GST_DEBUG ("doing seek to %d", (gint) offset);
    return offset;
  }
  return 0;
}

static long
gst_afparse_vf_length (AFvirtualfile * vfile)
{
  GstByteStream *bs = (GstByteStream *) vfile->closure;
  guint64 length;

  length = gst_bytestream_length (bs);
  GST_DEBUG ("doing length: %" G_GUINT64_FORMAT, length);
  return length;
}

static ssize_t
gst_afparse_vf_write (AFvirtualfile * vfile, const void *data, size_t nbytes)
{
  /* GstByteStream *bs = (GstByteStream*)vfile->closure; */
  g_warning ("shouldn't write to a readonly pad");
  return 0;
}

static void
gst_afparse_vf_destroy (AFvirtualfile * vfile)
{
  /* GstByteStream *bs = (GstByteStream*)vfile->closure; */

  GST_DEBUG ("doing destroy");
}

static long
gst_afparse_vf_tell (AFvirtualfile * vfile)
{
  GstByteStream *bs = (GstByteStream *) vfile->closure;
  guint64 offset;

  offset = gst_bytestream_tell (bs);
  GST_DEBUG ("doing tell: %" G_GUINT64_FORMAT, offset);
  return offset;
}
