/* libSoX Opus-in-Ogg sound format handler
 * Copyright (C) 2013 John Stumpo <stump@jstump.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation version 2.
 *
 * This program 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 General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Largely based on vorbis.c:
 * libSoX Ogg Vorbis sound format handler
 * Copyright 2001, Stan Seibert <indigo@aztec.asu.edu>
 *
 * Portions from oggenc, (c) Michael Smith <msmith@labyrinth.net.au>,
 * ogg123, (c) Kenneth Arnold <kcarnold@yahoo.com>, and
 * libvorbisfile (c) Xiphophorus Company
 *
 * May 9, 2001 - Stan Seibert (indigo@aztec.asu.edu)
 * Ogg Vorbis handler initially written.
 *
 * July 5, 1991 - Skeleton file
 * Copyright 1991 Lance Norskog And Sundry Contributors
 * This source code is freely redistributable and may be used for
 * any purpose.  This copyright notice must be maintained.
 * Lance Norskog And Sundry Contributors are not responsible for
 * the consequences of using this software.
 */

#include "sox_i.h"

#include <opus/opusfile.h>

#define DEF_BUF_LEN 4096

#define BUF_ERROR -1
#define BUF_EOF  0
#define BUF_DATA 1

typedef struct {
  /* Decoding data */
  OggOpusFile *of;
  char *buf;
  size_t buf_len;
  size_t start;
  size_t end;     /* Unsent data samples in buf[start] through buf[end-1] */
  int current_section;
  int eof;
} priv_t;

/******** Callback functions used in op_open_callbacks ************/

static int callback_read(void* ft_data, unsigned char* ptr, int nbytes)
{
  sox_format_t* ft = (sox_format_t*)ft_data;
  return lsx_readbuf(ft, ptr, (size_t)nbytes);
}

static int callback_seek(void* ft_data, opus_int64 off, int whence)
{
  sox_format_t* ft = (sox_format_t*)ft_data;
  int ret = ft->seekable ? lsx_seeki(ft, (off_t)off, whence) : -1;

  if (ret == EBADF)
    ret = -1;
  return ret;
}

static int callback_close(void* ft_data UNUSED)
{
  /* Do nothing so sox can close the file for us */
  return 0;
}

static opus_int64 callback_tell(void* ft_data)
{
  sox_format_t* ft = (sox_format_t*)ft_data;
  return lsx_tell(ft);
}

/********************* End callbacks *****************************/


/*
 * Do anything required before you start reading samples.
 * Read file header.
 *      Find out sampling rate,
 *      size and encoding of samples,
 *      mono/stereo/quad.
 */
static int startread(sox_format_t * ft)
{
  priv_t * vb = (priv_t *) ft->priv;
  const OpusTags *ot;
  int i;

  OpusFileCallbacks callbacks = {
    callback_read,
    callback_seek,
    callback_tell,
    callback_close
  };

  /* Init the decoder */
  vb->of = op_open_callbacks(ft, &callbacks, NULL, (size_t) 0, NULL);
  if (vb->of == NULL) {
    lsx_fail_errno(ft, SOX_EHDR, "input is not an Ogg Opus audio stream");
    return (SOX_EOF);
  }

  /* Get info about the Opus stream */
  ot = op_tags(vb->of, -1);

  /* Record audio info */
  ft->signal.rate = 48000;  /* libopusfile always uses 48 kHz */
  ft->encoding.encoding = SOX_ENCODING_OPUS;
  ft->signal.channels = op_channel_count(vb->of, -1);

  /* op_pcm_total doesn't work on non-seekable files so
   * skip that step in that case.  Also, it reports
   * "frame"-ish results so we must * channels.
   */
  if (ft->seekable)
    ft->signal.length = op_pcm_total(vb->of, -1) * ft->signal.channels;

  /* Record comments */
  for (i = 0; i < ot->comments; i++)
    sox_append_comment(&ft->oob.comments, ot->user_comments[i]);

  /* Setup buffer */
  vb->buf_len = DEF_BUF_LEN;
  vb->buf_len -= vb->buf_len % (ft->signal.channels*2); /* 2 bytes per sample */
  lsx_vcalloc(vb->buf, vb->buf_len);
  vb->start = vb->end = 0;

  /* Fill in other info */
  vb->eof = 0;
  vb->current_section = -1;

  return (SOX_SUCCESS);
}


/* Refill the buffer with samples.  Returns BUF_EOF if the end of the
 * Opus data was reached while the buffer was being filled,
 * BUF_ERROR is something bad happens, and BUF_DATA otherwise */
static int refill_buffer(sox_format_t * ft)
{
  priv_t * vb = (priv_t *) ft->priv;
  int num_read;

  if (vb->start == vb->end)     /* Samples all played */
    vb->start = vb->end = 0;

  while (vb->end < vb->buf_len) {
    num_read = op_read(vb->of, (opus_int16*) (vb->buf + vb->end),
        (int) ((vb->buf_len - vb->end) / sizeof(opus_int16)),
        &vb->current_section);
    if (num_read == 0)
      return (BUF_EOF);
    else if (num_read == OP_HOLE)
      lsx_warn("hole in stream; probably harmless");
    else if (num_read < 0)
      return (BUF_ERROR);
    else
      vb->end += num_read * sizeof(opus_int16) * ft->signal.channels;
  }
  return (BUF_DATA);
}


/*
 * Read up to len samples from file.
 * Convert to signed longs.
 * Place in buf[].
 * Return number of samples read.
 */

static size_t read_samples(sox_format_t * ft, sox_sample_t * buf, size_t len)
{
  priv_t * vb = (priv_t *) ft->priv;
  size_t i;
  int ret;
  sox_sample_t l;


  for (i = 0; i < len; i++) {
    if (vb->start == vb->end) {
      if (vb->eof)
        break;
      ret = refill_buffer(ft);
      if (ret == BUF_EOF || ret == BUF_ERROR) {
        vb->eof = 1;
        if (vb->end == 0)
          break;
      }
    }

    l = (vb->buf[vb->start + 1] << 24)
        | (0xffffff & (vb->buf[vb->start] << 16));
    *(buf + i) = l;
    vb->start += 2;
  }
  return i;
}

/*
 * Do anything required when you stop reading samples.
 * Don't close input file!
 */
static int stopread(sox_format_t * ft)
{
  priv_t * vb = (priv_t *) ft->priv;

  free(vb->buf);
  op_free(vb->of);

  return (SOX_SUCCESS);
}

static int seek(sox_format_t * ft, sox_uint64_t offset)
{
  priv_t * vb = (priv_t *) ft->priv;

  return op_pcm_seek(vb->of, (opus_int64)(offset / ft->signal.channels))? SOX_EOF:SOX_SUCCESS;
}

LSX_FORMAT_HANDLER(opus)
{
  static const char *const names[] = {"opus", NULL};
  static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE,
    "Xiph.org's Opus lossy compression", names, 0,
    startread, read_samples, stopread,
    NULL, NULL, NULL,
    seek, NULL, NULL, sizeof(priv_t)
  };
  return &handler;
}
