/* GStreamer EBML I/O
 * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
 *
 * ebml-read.c: read EBML data from file/stream
 *
 * 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 <string.h>

#include "ebml-read.h"
#include "ebml-ids.h"

GST_DEBUG_CATEGORY_STATIC (ebmlread_debug);
#define GST_CAT_DEFAULT ebmlread_debug

enum
{
  /* FILL ME */
  LAST_SIGNAL
};

static void gst_ebml_read_class_init (GstEbmlReadClass * klass);
static void gst_ebml_read_init (GstEbmlRead * ebml);
static GstStateChangeReturn gst_ebml_read_change_state (GstElement * element,
    GstStateChange transition);

static GstElementClass *parent_class = NULL;

GType
gst_ebml_read_get_type (void)
{
  static GType gst_ebml_read_type = 0;

  if (!gst_ebml_read_type) {
    static const GTypeInfo gst_ebml_read_info = {
      sizeof (GstEbmlReadClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_ebml_read_class_init,
      NULL,
      NULL,
      sizeof (GstEbmlRead),
      0,
      (GInstanceInitFunc) gst_ebml_read_init,
    };

    gst_ebml_read_type =
        g_type_register_static (GST_TYPE_ELEMENT, "GstEbmlRead",
        &gst_ebml_read_info, 0);
  }

  return gst_ebml_read_type;
}

static void
gst_ebml_read_class_init (GstEbmlReadClass * klass)
{
  GstElementClass *gstelement_class = (GstElementClass *) klass;

  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

  GST_DEBUG_CATEGORY_INIT (ebmlread_debug, "ebmlread",
      0, "EBML stream helper class");

  gstelement_class->change_state = gst_ebml_read_change_state;
}

static void
gst_ebml_read_init (GstEbmlRead * ebml)
{
  ebml->sinkpad = NULL;
  ebml->bs = NULL;
  ebml->level = NULL;
  ebml->id_cache = 0;
}

static GstStateChangeReturn
gst_ebml_read_change_state (GstElement * element, GstStateChange transition)
{
  GstEbmlRead *ebml = GST_EBML_READ (element);

  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      if (!ebml->sinkpad)
        return GST_STATE_CHANGE_FAILURE;
      ebml->bs = gst_bytestream_new (ebml->sinkpad);
      break;
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_bytestream_destroy (ebml->bs);
      while (ebml->level) {
        GstEbmlLevel *level = ebml->level->data;

        ebml->level = g_list_remove (ebml->level, level);
        g_free (level);
      }
      break;
    default:
      break;
  }

  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  return GST_STATE_CHANGE_SUCCESS;
}

/*
 * Event handler. Basic:
 * - EOS: end-of-file, stop processing, forward EOS.
 * - Interrupt: stop processing.
 * - Discont: shouldn't be handled here but in the seek handler. Error.
 * - Flush: ignore, since we check for flush flags manually. Don't forward.
 * - Others: warn, ignore.
 * Return value indicates whether to continue processing.
 */

static gboolean
gst_ebml_read_use_event (GstEbmlRead * ebml, GstEvent * event)
{
  if (!event) {
    GST_ELEMENT_ERROR (ebml, RESOURCE, READ, (NULL), (NULL));
    return FALSE;
  }

  GST_LOG ("Received event of type %d", GST_EVENT_TYPE (event));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_EOS:
      gst_pad_event_default (ebml->sinkpad, event);
      return FALSE;

    case GST_EVENT_INTERRUPT:
      gst_event_unref (event);
      return FALSE;

    case GST_EVENT_DISCONTINUOUS:
      GST_WARNING_OBJECT (ebml, "Unexpected discont - might lose sync");
      gst_event_unref (event);
      return TRUE;

    case GST_EVENT_FLUSH:
      gst_event_unref (event);
      return TRUE;

    default:
      GST_WARNING ("don't know how to handle event %d", GST_EVENT_TYPE (event));
      gst_pad_event_default (ebml->sinkpad, event);
      return FALSE;
  }

  /* happy */
  g_assert_not_reached ();
  return FALSE;
}

static gboolean
gst_ebml_read_handle_event (GstEbmlRead * ebml)
{
  GstEvent *event = NULL;
  guint32 remaining;

  gst_bytestream_get_status (ebml->bs, &remaining, &event);

  return gst_ebml_read_use_event (ebml, event);
}

/*
 * Return: the amount of levels in the hierarchy that the
 * current element lies higher than the previous one.
 * The opposite isn't done - that's auto-done using master
 * element reading.
 */

static guint
gst_ebml_read_element_level_up (GstEbmlRead * ebml)
{
  guint num = 0;
  guint64 pos = gst_bytestream_tell (ebml->bs);

  while (ebml->level != NULL) {
    GList *last = g_list_last (ebml->level);
    GstEbmlLevel *level = last->data;

    if (pos >= level->start + level->length) {
      ebml->level = g_list_remove (ebml->level, level);
      g_free (level);
      num++;
    } else
      break;
  }

  return num;
}

/*
 * Read: the element content data ID.
 * Return: the number of bytes read or -1 on error.
 */

static gint
gst_ebml_read_element_id (GstEbmlRead * ebml, guint32 * id, guint * level_up)
{
  guint8 *data;
  gint len_mask = 0x80, read = 1, n = 1;
  guint32 total;

  if (ebml->id_cache) {
    *id = ebml->id_cache;
    if (level_up)
      *level_up = 0;

    return 0;
  }

  while (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
    if (!gst_ebml_read_handle_event (ebml))
      return -1;
  }
  total = data[0];
  while (read <= 4 && !(total & len_mask)) {
    read++;
    len_mask >>= 1;
  }
  if (read > 4) {
    guint64 pos = gst_bytestream_tell (ebml->bs);

    GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
        ("Invalid EBML ID size tag (0x%x) at position %llu (0x%llx)",
            data[0], pos, pos));
    return -1;
  }

  while (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) {
    if (!gst_ebml_read_handle_event (ebml))
      return -1;
  }
  while (n < read)
    total = (total << 8) | data[n++];

  *id = ebml->id_cache = total;

  /* level */
  if (level_up)
    *level_up = gst_ebml_read_element_level_up (ebml);

  gst_bytestream_flush_fast (ebml->bs, read);

  return 0;
}

/*
 * Read: element content length.
 * Return: the number of bytes read or -1 on error.
 */

static gint
gst_ebml_read_element_length (GstEbmlRead * ebml, guint64 * length)
{
  guint8 *data;
  gint len_mask = 0x80, read = 1, n = 1, num_ffs = 0;
  guint64 total;

  while (gst_bytestream_peek_bytes (ebml->bs, &data, 1) != 1) {
    if (!gst_ebml_read_handle_event (ebml))
      return -1;
  }
  total = data[0];
  while (read <= 8 && !(total & len_mask)) {
    read++;
    len_mask >>= 1;
  }
  if (read > 8) {
    guint64 pos = gst_bytestream_tell (ebml->bs);

    GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
        ("Invalid EBML length size tag (0x%x) at position %llu (0x%llx)",
            data[0], pos, pos));
    return -1;
  }

  if ((total &= (len_mask - 1)) == len_mask - 1)
    num_ffs++;
  while (gst_bytestream_peek_bytes (ebml->bs, &data, read) != read) {
    if (!gst_ebml_read_handle_event (ebml))
      return -1;
  }
  while (n < read) {
    if (data[n] == 0xff)
      num_ffs++;
    total = (total << 8) | data[n];
    n++;
  }

  if (read == num_ffs)
    *length = G_MAXUINT64;
  else
    *length = total;

  return read;
}

/*
 * Read: the actual data.
 * Return: the data, as a GstBuffer.
 */

static GstBuffer *
gst_ebml_read_element_data (GstEbmlRead * ebml, guint64 length)
{
  GstBuffer *buf = NULL;

  if (gst_bytestream_peek (ebml->bs, &buf, length) != length) {
    if (!gst_ebml_read_handle_event (ebml))
      return NULL;
  }

  gst_bytestream_flush_fast (ebml->bs, length);

  return buf;
}

/*
 * Return: the ID of the next element.
 * Level_up contains the amount of levels that this
 * next element lies higher than the previous one.
 */

guint32
gst_ebml_peek_id (GstEbmlRead * ebml, guint * level_up)
{
  guint32 id;

  g_assert (level_up);

  if (gst_ebml_read_element_id (ebml, &id, level_up) < 0)
    return 0;

  return id;
}

/*
 * Seek to a given offset.
 */

GstEvent *
gst_ebml_read_seek (GstEbmlRead * ebml, guint64 offset)
{
  guint32 remaining;
  GstEvent *event = NULL;
  guchar *data;

  /* first, flush remaining buffers */
  gst_bytestream_get_status (ebml->bs, &remaining, &event);
  if (event) {
    GST_WARNING ("Unexpected event before seek");
    if (!gst_ebml_read_use_event (ebml, event))
      return NULL;
    event = NULL;
  }
  if (remaining)
    gst_bytestream_flush_fast (ebml->bs, remaining);

  /* now seek */
  if (!gst_bytestream_seek (ebml->bs, offset, GST_SEEK_METHOD_SET)) {
    GST_ELEMENT_ERROR (ebml, RESOURCE, SEEK, (NULL),
        ("Seek to position %llu (0x%llx) failed", offset, offset));
    return NULL;
  }

  while (!event) {
    /* and now, peek a new byte. This will fail because there's a
     * pending event. Then, take the event and return it. */
    if (gst_bytestream_peek_bytes (ebml->bs, &data, 1)) {
      GST_WARNING ("Unexpected data after seek - this means seek failed");
      break;
    }

    /* get the discont event and return */
    gst_bytestream_get_status (ebml->bs, &remaining, &event);
    if (!event) {
      GST_WARNING ("No discontinuity event after seek - seek failed");
      break;
    } else if (GST_EVENT_TYPE (event) != GST_EVENT_DISCONTINUOUS) {
      if (!gst_ebml_read_use_event (ebml, event))
        return NULL;
      event = NULL;
    }
  }

  /* at the end of a seek, we have no cached ID anymore */
  ebml->id_cache = 0;

  return event;
}

/*
 * Skip the next element.
 */

gboolean
gst_ebml_read_skip (GstEbmlRead * ebml)
{
  gint bytes;
  guint32 id, remaining;
  guint64 length;
  GstEvent *event;

  if ((bytes = gst_ebml_read_element_id (ebml, &id, NULL)) < 0)
    return FALSE;
  gst_bytestream_flush_fast (ebml->bs, bytes);

  if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
    return FALSE;
  gst_bytestream_flush_fast (ebml->bs, bytes);

  /* do we have enough bytes left to skip? */
  gst_bytestream_get_status (ebml->bs, &remaining, &event);
  if (event) {
    g_warning ("Unexpected event before skip");
    if (!gst_ebml_read_use_event (ebml, event))
      return FALSE;
  }

  if (remaining >= length) {
    ebml->id_cache = 0;
    gst_bytestream_flush_fast (ebml->bs, length);

    return TRUE;
  }

  if (!(event = gst_ebml_read_seek (ebml,
              gst_bytestream_tell (ebml->bs) + length))) {
    return FALSE;
  }

  gst_event_unref (event);
  ebml->id_cache = 0;

  return TRUE;
}

/*
 * Make sure there's one whole next chunk available
 * for uninterrupted reading.
 */

gboolean
gst_ebml_read_reserve (GstEbmlRead * ebml)
{
  guint64 length;
  gint bytes;
  guint8 *data;
  guint32 id;

  /* make sure the ID is cached */
  if (gst_ebml_read_element_id (ebml, &id, NULL) < 0)
    return FALSE;

  /* now we can peek the length */
  if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
    return FALSE;
  length += bytes;

  /* now check if we have that amount available */
  while (gst_bytestream_peek_bytes (ebml->bs, &data, length) != length) {
    if (!gst_ebml_read_handle_event (ebml))
      return FALSE;
  }

  /* yes, available */
  return TRUE;
}

/*
 * Read the next element as a GstBuffer (binary).
 */

gboolean
gst_ebml_read_buffer (GstEbmlRead * ebml, guint32 * id, GstBuffer ** buf)
{
  gint bytes;
  guint64 length;

  if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0)
    return FALSE;

  gst_bytestream_flush_fast (ebml->bs, bytes);

  if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
    return FALSE;

  gst_bytestream_flush_fast (ebml->bs, bytes);
  ebml->id_cache = 0;
  if (length == 0) {
    *buf = gst_buffer_new ();
    return TRUE;
  }

  return ((*buf = gst_ebml_read_element_data (ebml, length)) != NULL);
}

/*
 * Read the next element as an unsigned int.
 */

gboolean
gst_ebml_read_uint (GstEbmlRead * ebml, guint32 * id, guint64 * num)
{
  GstBuffer *buf;
  guint8 *data;
  guint size;

  if (!gst_ebml_read_buffer (ebml, id, &buf))
    return FALSE;

  data = GST_BUFFER_DATA (buf);
  size = GST_BUFFER_SIZE (buf);
  if (size < 1 || size > 8) {
    GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
        ("Invalid integer element size %d at position %llu (0x%llu)",
            size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)));
    gst_buffer_unref (buf);
    return FALSE;
  }
  *num = 0;
  while (size > 0) {
    *num = (*num << 8) | data[GST_BUFFER_SIZE (buf) - size];
    size--;
  }

  gst_buffer_unref (buf);

  return TRUE;
}

/*
 * Read the next element as a signed int.
 */

gboolean
gst_ebml_read_sint (GstEbmlRead * ebml, guint32 * id, gint64 * num)
{
  GstBuffer *buf;
  guint8 *data;
  guint size, negative = 0, n = 0;

  if (!gst_ebml_read_buffer (ebml, id, &buf))
    return FALSE;

  data = GST_BUFFER_DATA (buf);
  size = GST_BUFFER_SIZE (buf);
  if (size < 1 || size > 8) {
    GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
        ("Invalid integer element size %d at position %llu (0x%llx)",
            size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)));
    gst_buffer_unref (buf);
    return FALSE;
  }
  if (data[0] & 0x80) {
    negative = 1;
    data[0] &= ~0x80;
  }
  *num = 0;
  while (n < size) {
    *num = (*num << 8) | data[n++];
  }

  /* make signed */
  if (negative) {
    *num = *num - (1LL << ((8 * size) - 1));
  }

  gst_buffer_unref (buf);

  return TRUE;
}

/*
 * Read the next element as a float.
 */

gboolean
gst_ebml_read_float (GstEbmlRead * ebml, guint32 * id, gdouble * num)
{
  GstBuffer *buf;
  guint8 *data;
  guint size;

  if (!gst_ebml_read_buffer (ebml, id, &buf))
    return FALSE;

  data = GST_BUFFER_DATA (buf);
  size = GST_BUFFER_SIZE (buf);

  if (size != 4 && size != 8 && size != 10) {
    GST_ELEMENT_ERROR (ebml, STREAM, DEMUX, (NULL),
        ("Invalid float element size %d at position %llu (0x%llx)",
            size, GST_BUFFER_OFFSET (buf), GST_BUFFER_OFFSET (buf)));
    gst_buffer_unref (buf);
    return FALSE;
  }

  if (size == 10) {
    GST_ELEMENT_ERROR (ebml, CORE, NOT_IMPLEMENTED, (NULL),
        ("FIXME! 10-byte floats unimplemented"));
    gst_buffer_unref (buf);
    return FALSE;
  }

  if (size == 4) {
    gfloat f;

#if (G_BYTE_ORDER == G_BIG_ENDIAN)
    f = *(gfloat *) data;
#else
    while (size > 0) {
      ((guint8 *) & f)[size - 1] = data[4 - size];
      size--;
    }
#endif

    *num = f;
  } else {
    gdouble d;

#if (G_BYTE_ORDER == G_BIG_ENDIAN)
    d = *(gdouble *) data;
#else
    while (size > 0) {
      ((guint8 *) & d)[size - 1] = data[8 - size];
      size--;
    }
#endif

    *num = d;
  }

  gst_buffer_unref (buf);

  return TRUE;
}

/*
 * Read the next element as an ASCII string.
 */

gboolean
gst_ebml_read_ascii (GstEbmlRead * ebml, guint32 * id, gchar ** str)
{
  GstBuffer *buf;

  if (!gst_ebml_read_buffer (ebml, id, &buf))
    return FALSE;

  *str = g_malloc (GST_BUFFER_SIZE (buf) + 1);
  memcpy (*str, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
  (*str)[GST_BUFFER_SIZE (buf)] = '\0';

  gst_buffer_unref (buf);

  return TRUE;
}

/*
 * Read the next element as a UTF-8 string.
 */

gboolean
gst_ebml_read_utf8 (GstEbmlRead * ebml, guint32 * id, gchar ** str)
{
  return gst_ebml_read_ascii (ebml, id, str);
}

/*
 * Read the next element as a date.
 * Returns the seconds since the unix epoch.
 */

gboolean
gst_ebml_read_date (GstEbmlRead * ebml, guint32 * id, gint64 * date)
{
  gint64 ebml_date;
  gboolean res = gst_ebml_read_sint (ebml, id, &ebml_date);

  *date = (ebml_date / GST_SECOND) + GST_EBML_DATE_OFFSET;
  return res;
}

/*
 * Read the next element, but only the header. The contents
 * are supposed to be sub-elements which can be read separately.
 */

gboolean
gst_ebml_read_master (GstEbmlRead * ebml, guint32 * id)
{
  gint bytes;
  guint64 length;
  GstEbmlLevel *level;

  if ((bytes = gst_ebml_read_element_id (ebml, id, NULL)) < 0)
    return FALSE;
  gst_bytestream_flush_fast (ebml->bs, bytes);

  if ((bytes = gst_ebml_read_element_length (ebml, &length)) < 0)
    return FALSE;
  gst_bytestream_flush_fast (ebml->bs, bytes);

  /* remember level */
  level = g_new (GstEbmlLevel, 1);
  level->start = gst_bytestream_tell (ebml->bs);
  level->length = length;
  ebml->level = g_list_append (ebml->level, level);
  ebml->id_cache = 0;

  return TRUE;
}

/*
 * Read the next element as binary data.
 */

gboolean
gst_ebml_read_binary (GstEbmlRead * ebml,
    guint32 * id, guint8 ** binary, guint64 * length)
{
  GstBuffer *buf;

  if (!gst_ebml_read_buffer (ebml, id, &buf))
    return FALSE;

  *length = GST_BUFFER_SIZE (buf);
  *binary = g_memdup (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));

  gst_buffer_unref (buf);

  return TRUE;
}

/*
 * Read an EBML header.
 */

gboolean
gst_ebml_read_header (GstEbmlRead * ebml, gchar ** doctype, guint * version)
{
  /* this function is the first to be called */
  guint32 id;
  guint level_up;

  /* default init */
  if (doctype)
    *doctype = NULL;
  if (version)
    *version = 1;

  if (!(id = gst_ebml_peek_id (ebml, &level_up)))
    return FALSE;
  if (level_up != 0 || id != GST_EBML_ID_HEADER) {
    GST_ELEMENT_ERROR (ebml, STREAM, WRONG_TYPE, (NULL), (NULL));
    return FALSE;
  }
  if (!gst_ebml_read_master (ebml, &id))
    return FALSE;
  g_assert (id == GST_EBML_ID_HEADER);

  while (TRUE) {
    if (!(id = gst_ebml_peek_id (ebml, &level_up)))
      return FALSE;

    /* end-of-header */
    if (level_up)
      break;

    switch (id) {
        /* is our read version uptodate? */
      case GST_EBML_ID_EBMLREADVERSION:{
        guint64 num;

        if (!gst_ebml_read_uint (ebml, &id, &num))
          return FALSE;
        g_assert (id == GST_EBML_ID_EBMLREADVERSION);
        if (num != GST_EBML_VERSION)
          return FALSE;
        break;
      }

        /* we only handle 8 byte lengths at max */
      case GST_EBML_ID_EBMLMAXSIZELENGTH:{
        guint64 num;

        if (!gst_ebml_read_uint (ebml, &id, &num))
          return FALSE;
        g_assert (id == GST_EBML_ID_EBMLMAXSIZELENGTH);
        if (num != sizeof (guint64))
          return FALSE;
        break;
      }

        /* we handle 4 byte IDs at max */
      case GST_EBML_ID_EBMLMAXIDLENGTH:{
        guint64 num;

        if (!gst_ebml_read_uint (ebml, &id, &num))
          return FALSE;
        g_assert (id == GST_EBML_ID_EBMLMAXIDLENGTH);
        if (num != sizeof (guint32))
          return FALSE;
        break;
      }

      case GST_EBML_ID_DOCTYPE:{
        gchar *text;

        if (!gst_ebml_read_ascii (ebml, &id, &text))
          return FALSE;
        g_assert (id == GST_EBML_ID_DOCTYPE);
        if (doctype) {
          if (doctype)
            g_free (*doctype);
          *doctype = text;
        } else
          g_free (text);
        break;
      }

      case GST_EBML_ID_DOCTYPEREADVERSION:{
        guint64 num;

        if (!gst_ebml_read_uint (ebml, &id, &num))
          return FALSE;
        g_assert (id == GST_EBML_ID_DOCTYPEREADVERSION);
        if (version)
          *version = num;
        break;
      }

      default:
        GST_WARNING ("Unknown data type 0x%x in EBML header (ignored)", id);
        /* pass-through */

        /* we ignore these two, as they don't tell us anything we care about */
      case GST_EBML_ID_VOID:
      case GST_EBML_ID_EBMLVERSION:
      case GST_EBML_ID_DOCTYPEVERSION:
        if (!gst_ebml_read_skip (ebml))
          return FALSE;
        break;
    }
  }

  return TRUE;
}
