#include <caml/alloc.h>
#include <caml/callback.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/misc.h>
#include <caml/mlvalues.h>
#include <caml/signals.h>
#define caml_acquire_runtime_system caml_leave_blocking_section
#define caml_release_runtime_system caml_enter_blocking_section

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include <ogg/ogg.h>
#include <ocaml-ogg.h>
#include <opus/opus.h>
#include <opus/opus_multistream.h>

#ifdef BIGENDIAN
// code from bits/byteswap.h (C) 1997, 1998 Free Software Foundation, Inc.
#define int32le_to_native(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) | \
      (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
#define int16le_to_native(x) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))
#else
#define int32le_to_native(x) x
#define int16le_to_native(x) x
#endif

/* polymorphic variant utility macro */
#define get_var(x) caml_hash_variant(#x)

/* Macros to convert variants to controls. */
#define set_ctl(tag, variant, handle, fn, name, v) \
  if (tag == get_var(variant)) { \
    check(fn(handle,name(Int_val(v)))); \
    CAMLreturn(Val_unit); \
  }

#define set_value_ctl(tag, variant, handle, fn, name, v, convert) \
  if (tag == get_var(variant)) { \
    check(fn(handle,name(convert(v)))); \
    CAMLreturn(Val_unit); \
  }

#define get_ctl(tag, variant, handle, fn, name, v, type) \
  if (tag == get_var(variant)) { \
    type name = (type)Int_val(Field(v,0)); \
    check(fn(handle,name(&name))); \
    Store_field(v,0,Val_int(name)); \
    CAMLreturn(Val_unit); \
  }

#define get_value_ctl(tag, variant, handle, fn, name, v, type, convert) \
  if (tag == get_var(variant)) { \
    type name = (type)Int_val(Field(v,0)); \
    check(fn(handle,name(&name))); \
    Store_field(v,0,convert(name)); \
    CAMLreturn(Val_unit); \
  }

static void check(int ret)
{
  if (ret < 0)
    switch (ret)
      {
      case OPUS_BAD_ARG:
        caml_invalid_argument("opus");

      case OPUS_BUFFER_TOO_SMALL:
        caml_raise_constant(*caml_named_value("opus_exn_buffer_too_small"));

      case OPUS_INTERNAL_ERROR:
        caml_raise_constant(*caml_named_value("opus_exn_internal_error"));

      case OPUS_INVALID_PACKET:
        caml_raise_constant(*caml_named_value("opus_exn_invalid_packet"));

      case OPUS_UNIMPLEMENTED:
        caml_raise_constant(*caml_named_value("opus_exn_unimplemented"));

      case OPUS_INVALID_STATE:
        caml_raise_constant(*caml_named_value("opus_exn_invalid_state"));

      case OPUS_ALLOC_FAIL:
        caml_raise_constant(*caml_named_value("opus_exn_alloc_fail"));

      default:
        caml_failwith("Unknown opus error");
      }
}

CAMLprim value ocaml_opus_version_string(value unit)
{
  CAMLparam0();
  CAMLreturn(caml_copy_string(opus_get_version_string()));
}

/***** Decoder ******/

#define Dec_val(v) (*(OpusDecoder**)Data_custom_val(v))

static void finalize_dec(value v)
{
  OpusDecoder *dec = Dec_val(v);
  opus_decoder_destroy(dec);
}

static struct custom_operations dec_ops =
  {
    "ocaml_opus_dec",
    finalize_dec,
    custom_compare_default,
    custom_hash_default,
    custom_serialize_default,
    custom_deserialize_default
  };

CAMLprim value ocaml_opus_decoder_create(value _sr, value _chans)
{
  CAMLparam0();
  CAMLlocal1(ans);
  opus_int32 sr = Int_val(_sr);
  int chans = Int_val(_chans);
  int ret = 0;
  OpusDecoder *dec;

  dec = opus_decoder_create(sr, chans, &ret);

  check(ret);
  ans = caml_alloc_custom(&dec_ops, sizeof(OpusDecoder*), 0, 1);
  Dec_val(ans) = dec;
  CAMLreturn(ans);
}

CAMLprim value ocaml_opus_packet_check_header(value packet)
{
  CAMLparam1(packet);
  ogg_packet *op = Packet_val(packet);
  int ans = 0;

  if (op->bytes >= 8 && !memcmp(op->packet, "OpusHead", 8))
    ans = 1;

  CAMLreturn(Val_bool(ans));
}

CAMLprim value ocaml_opus_decoder_channels(value packet)
{
  CAMLparam1(packet);
  ogg_packet *op = Packet_val(packet);
  uint8_t *data = op->packet;
  uint8_t version = *(data+8);

  if (op->bytes <= 10 || memcmp(op->packet, "OpusHead", 8))
    caml_invalid_argument("Wrong header data.");

  if (version != 1)
    caml_invalid_argument("Wrong header version.");

  uint8_t ret = *(data+9);

  CAMLreturn(Val_int(ret));
}

CAMLprim value ocaml_opus_comments(value packet)
{
  CAMLparam1(packet);
  CAMLlocal2(ans, comments);
  ogg_packet *op = Packet_val(packet);
  if (!(op->bytes >= 8 && !memcmp(op->packet, "OpusTags", 8)))
    check(OPUS_INVALID_PACKET);
  ans = caml_alloc_tuple(2);

  int off = 8;
  /* Vendor */

  if (off+4 > op->bytes)
    check(OPUS_INVALID_PACKET);

  opus_int32 vendor_length = int32le_to_native((opus_int32)*(op->packet+off));
  off += 4;

  if (off+vendor_length > op->bytes)
    check(OPUS_INVALID_PACKET);

  Store_field(ans, 0, caml_alloc_string(vendor_length));
  memcpy(String_val(Field(ans,0)), op->packet+off, vendor_length);

  off += vendor_length;

  /* Comments */

  if (off+4 > op->bytes)
    check(OPUS_INVALID_PACKET);

  opus_int32 comments_length = int32le_to_native((opus_int32)*(op->packet+off));
  off += 4;

  comments = caml_alloc_tuple(comments_length);
  Store_field(ans, 1, comments);
  opus_int32 i, len;
  for(i = 0; i < comments_length; i++)
    {
      if (off+4 > op->bytes)
        check(OPUS_INVALID_PACKET);

      len = int32le_to_native((opus_int32)*(op->packet+off));
      off += 4;

      if (off+len > op->bytes)
        check(OPUS_INVALID_PACKET);

      Store_field(comments, i, caml_alloc_string(len));
      memcpy(String_val(Field(comments, i)), op->packet+off, len);
      off += len;
    }

  CAMLreturn(ans);
}

static opus_int32 bandwidth_of_value(value v) {
  if (v == get_var(Auto))
    return OPUS_AUTO;
  if (v == get_var(Narrow_band))
    return OPUS_BANDWIDTH_NARROWBAND;
  if (v == get_var(Medium_band))
    return OPUS_BANDWIDTH_MEDIUMBAND;
  if (v == get_var(Wide_band))
    return OPUS_BANDWIDTH_WIDEBAND;
  if (v == get_var(Super_wide_band))
    return OPUS_BANDWIDTH_SUPERWIDEBAND;
  if (v == get_var(Full_band))
    return OPUS_BANDWIDTH_FULLBAND;

  caml_failwith("Unknown opus error");
}

static value value_of_bandwidth(opus_int32 a) {
  switch (a) {
    case OPUS_AUTO:
      return get_var(Auto);
    case OPUS_BANDWIDTH_NARROWBAND:
      return get_var(Narrow_band);
    case OPUS_BANDWIDTH_MEDIUMBAND:
      return get_var(Medium_band);
    case OPUS_BANDWIDTH_WIDEBAND:
      return get_var(Wide_band);
    case OPUS_BANDWIDTH_SUPERWIDEBAND:
      return get_var(Super_wide_band);
    case OPUS_BANDWIDTH_FULLBAND:
      return get_var(Full_band);
    default:
      caml_failwith("Unknown opus error");
  }
}

CAMLprim value ocaml_opus_decoder_ctl(value ctl, value _dec)
{
  CAMLparam2(_dec, ctl);
  CAMLlocal2(tag,v);
  OpusDecoder *dec = Dec_val(_dec);
  if (Is_long(ctl)) {
    // Only ctl without argument here is reset state..
    opus_decoder_ctl(dec, OPUS_RESET_STATE);
    CAMLreturn(Val_unit);
  } else {
    v   = Field(ctl,1);
    tag = Field(ctl,0);

    /* Generic controls. */
    get_ctl(tag, Get_final_range, dec, opus_decoder_ctl, OPUS_GET_FINAL_RANGE, v, opus_uint32);
    get_ctl(tag, Get_pitch, dec, opus_decoder_ctl, OPUS_GET_PITCH, v, opus_int32);
    get_value_ctl(tag, Get_bandwidth, dec, opus_decoder_ctl, OPUS_GET_BANDWIDTH, v, opus_int32, value_of_bandwidth);
    set_ctl(tag, Set_lsb_depth, dec, opus_decoder_ctl, OPUS_SET_LSB_DEPTH, v);
    get_ctl(tag, Get_lsb_depth, dec, opus_decoder_ctl, OPUS_GET_LSB_DEPTH, v, opus_int32);
#ifdef OPUS_SET_PHASE_INVERSION_DISABLED
    set_ctl(tag, Set_phase_inversion_disabled, dec, opus_decoder_ctl, OPUS_SET_PHASE_INVERSION_DISABLED, v);
#endif

    /* Decoder controls. */
    get_ctl(tag, Get_gain, dec, opus_decoder_ctl, OPUS_GET_GAIN, v, opus_int32);
    set_ctl(tag, Set_gain, dec, opus_decoder_ctl, OPUS_SET_GAIN, v);
  }

  caml_failwith("Unknown opus error"); 
}

CAMLprim value ocaml_opus_decoder_decode_float(value _dec, value _os, value buf, value _ofs, value _len, value _fec)
{
  CAMLparam3(_dec, _os, buf);
  CAMLlocal1(chan);
  ogg_stream_state *os = Stream_state_val(_os);
  ogg_packet op;
  OpusDecoder *dec = Dec_val(_dec);
  int decode_fec = Int_val(_fec);

  int ofs = Int_val(_ofs);
  int len = Int_val(_len);
  int total_samples = 0;
  int ret;

  int   chans = Wosize_val(buf); 
  float *pcm  = malloc(chans * len * sizeof(float));
  if (pcm == NULL)
    caml_raise_out_of_memory();

  int i, c;

  while (total_samples < len) {
    ret = ogg_stream_packetout(os,&op);
    /* returned values are:
     * 1: ok
     * 0: not enough data. in this case
     *    we return the number of samples
     *    decoded if > 0 and raise 
     *    Ogg_not_enough_data otherwise
     * -1: out of sync */
    if (ret == -1)
      caml_raise_constant(*caml_named_value("ogg_exn_out_of_sync"));

    if (ret == 0) {
      free(pcm);
      if (total_samples > 0) {
        CAMLreturn(Val_int(total_samples));
      } else {
        caml_raise_constant(*caml_named_value("ogg_exn_not_enough_data"));
      }
    }

    if (chans != opus_packet_get_nb_channels(op.packet))
      caml_invalid_argument("Wrong number of channels.");

    caml_release_runtime_system();
    ret = opus_decode_float(dec, op.packet, op.bytes, pcm, len, decode_fec);
    caml_acquire_runtime_system();

    if (ret < 0) {
      free(pcm);
      check(ret);
    }
    for (c = 0; c < chans; c++) {
      chan = Field(buf, c);
      for (i = 0; i < ret; i++)
        Store_double_field(chan, ofs+total_samples+i, pcm[i*chans+c]);
    }
    total_samples += ret;
    len           -= ret;
  }

  free(pcm);
  CAMLreturn(Val_int(total_samples));
}

CAMLprim value ocaml_opus_decoder_decode_float_byte(value *argv, int argn)
{
  return ocaml_opus_decoder_decode_float(argv[0], argv[1], argv[2], 
                                         argv[3], argv[4], argv[5]);
}

/***** Encoder *****/

typedef struct encoder_t {
  OpusEncoder *encoder;
  int         samplerate_ratio;
  ogg_int64_t granulepos;
  ogg_int64_t packetno;
} encoder_t;

#define Enc_val(v) (*(encoder_t**)Data_custom_val(v))

static void finalize_enc(value v)
{
  encoder_t *enc = Enc_val(v);
  opus_encoder_destroy(enc->encoder);
  free(enc);
}

static struct custom_operations enc_ops =
  {
    "ocaml_opus_enc",
    finalize_enc,
    custom_compare_default,
    custom_hash_default,
    custom_serialize_default,
    custom_deserialize_default
  };

static opus_int32 application_of_value(value v) {
  if (v == get_var(Voip))
    return OPUS_APPLICATION_VOIP;
  if (v == get_var(Audio))
    return OPUS_APPLICATION_AUDIO;
  if (v == get_var(Restricted_lowdelay))
    return OPUS_APPLICATION_RESTRICTED_LOWDELAY;

  caml_failwith("Unknown opus error");
}

static value value_of_application(opus_int32 a) {
  switch (a) {
    case OPUS_APPLICATION_VOIP:
      return get_var(Voip);
    case OPUS_APPLICATION_AUDIO:
      return get_var(Audio);
    case OPUS_APPLICATION_RESTRICTED_LOWDELAY:
      return get_var(Restricted_lowdelay);
    default:
      caml_failwith("Unknown opus error");
  }
}

static char header_packet[19] = {
  /* Identifier. */
 'O', 'p', 'u', 's',
 'H', 'e', 'a', 'd',
 /* version, channels count, pre-skip (16 bits, unsigned, 
  *                                    little endian) */
 1,    2,   0,   0,
 /* Samperate (32 bits, unsigned, little endian) */
 0,    0,   0,   0,
 /* output gain (16 bits, signed, little endian), channels mapping familly,
  * stream count (always 0 in this implementation) */
 0,    0,  0 };

static void pack_header(ogg_packet *op, opus_int32 sr, int channels, opus_int16 pre_skip, opus_int16 gain) {
  op->bytes  = sizeof(header_packet);
  op->packet = malloc(op->bytes);
  if (op->packet == NULL)
    caml_raise_out_of_memory();

  memcpy(op->packet, header_packet, op->bytes);

  /* Now fill data. */
  op->packet[9] = channels;
  memcpy(op->packet+10, &int16le_to_native(pre_skip), sizeof(opus_int16));
  memcpy(op->packet+12, &int32le_to_native(sr), sizeof(opus_int32));
  memcpy(op->packet+16, &int16le_to_native(gain), sizeof(opus_int16));

  op->b_o_s = 1;
  op->e_o_s = op->granulepos = op->packetno = 0;
}

static void pack_comments(ogg_packet *op, char *vendor, value comments) {
  int  i;
  long pos = 0;
  opus_int32 vendor_length = strlen(vendor);
  char *comment;
  opus_int32 comments_len = Wosize_val(comments);
  opus_int32 comment_length;

  op->bytes = 8 + 4 + vendor_length + 4;

  for (i = 0; i < Wosize_val(comments); i++)
    op->bytes += 4 + caml_string_length(Field(comments, i));

  op->packet = malloc(op->bytes);
  if (op->packet == NULL)
    caml_raise_out_of_memory();

  /* Identifier. */
  memcpy(op->packet, "OpusTags", 8);
  pos += 8;

  /* Vendor. */
  memcpy(op->packet+8, &int32le_to_native(vendor_length), sizeof(opus_int32));
  memcpy(op->packet+12,vendor,vendor_length);
  pos += 4 + vendor_length;

  /* Comments length. */
  memcpy(op->packet+pos, &comments_len, sizeof(opus_int32));
  pos += 4;

  /* Comments. */
  for (i = 0; i < comments_len; i++) {
    comment = String_val(Field(comments, i));
    comment_length = caml_string_length(Field(comments, i));
    memcpy(op->packet+pos, &int32le_to_native(comment_length), sizeof(opus_int32));
    memcpy(op->packet+pos+4, comment, comment_length);
    pos += 4 + comment_length;
  }

  op->e_o_s = op->b_o_s = op->granulepos = 0;
  op->packetno = 1;
}

CAMLprim value ocaml_opus_encoder_create(value _skip, value _comments, value _gain, value _sr, value _chans, value _application)
{
  CAMLparam0();
  CAMLlocal2(_enc, ans);
  opus_int32 sr = Int_val(_sr);
  int chans = Int_val(_chans);
  int ret = 0;
  int app = application_of_value(_application);
  encoder_t *enc = malloc(sizeof(encoder_t));
  if (enc == NULL)
    caml_raise_out_of_memory();
  /* First encoded packet is the third one. */
  enc->packetno   = 1;
  enc->granulepos = 0;
  /* Value samplerates are: 48000, 24000, 16000, 12000, 8000 
   * so this value is always an integer. */
  enc->samplerate_ratio = 48000 / sr;

  ogg_packet header;
  pack_header(&header, sr, chans, Int_val(_skip), Int_val(_gain));

  ogg_packet comments;
  pack_comments(&comments, "ocaml-opus by the Savonet Team.", _comments);

  enc->encoder = opus_encoder_create(sr, chans, app, &ret);

  check(ret);
  _enc = caml_alloc_custom(&enc_ops, sizeof(encoder_t*), 0, 1);
  Enc_val(_enc) = enc;

  ans = caml_alloc_tuple(3);

  Store_field(ans, 0, _enc);
  Store_field(ans, 1, value_of_packet(&header));
  Store_field(ans, 2, value_of_packet(&comments));

  CAMLreturn(ans);
}

CAMLprim value ocaml_opus_encoder_create_byte(value *argv, int argn)
{
  return ocaml_opus_encoder_create(argv[0], argv[1], argv[2],
                                   argv[3], argv[4], argv[5]);
}

static opus_int32 bitrate_of_value(value v) {
  if (Is_long(v)) {
    if (v == get_var(Auto))
      return OPUS_AUTO;
    if (v == get_var(Bitrate_max))
      return OPUS_BITRATE_MAX;
  } else {
    if (Field(v,0) == get_var(Bitrate))
      return Int_val(Field(v,1));
  }

  caml_failwith("Unknown opus error");
}

CAMLprim value value_of_bitrate(opus_int32 a) {
  CAMLparam0();
  CAMLlocal1(ret);
  switch (a) {
    case OPUS_AUTO:
      CAMLreturn(get_var(Auto));
    case OPUS_BITRATE_MAX:
      CAMLreturn(get_var(Voice));
    default:
      ret = caml_alloc_tuple(2);
      Store_field(ret,0,get_var(Bitrate));
      Store_field(ret,1,Val_int(a));
      CAMLreturn(ret);
  }
}

static opus_int32 signal_of_value(value v) {
  if (v == get_var(Auto))
    return OPUS_AUTO;
  if (v == get_var(Voice))
    return OPUS_SIGNAL_VOICE;
  if (v == get_var(Music))
    return OPUS_SIGNAL_MUSIC;

  caml_failwith("Unknown opus error");
}

static value value_of_signal(opus_int32 a) {
  switch (a) {
    case OPUS_AUTO:
      return get_var(Auto);
    case OPUS_SIGNAL_VOICE:
      return get_var(Voice);
    case OPUS_SIGNAL_MUSIC:
      return get_var(Music);
    default:
      caml_failwith("Unknown opus error");
  }
}

CAMLprim value ocaml_opus_encoder_ctl(value ctl, value _enc)
{
  CAMLparam2(_enc, ctl);
  CAMLlocal2(tag,v);
  encoder_t *handler = Enc_val(_enc);
  OpusEncoder *enc = handler->encoder;
  if (Is_long(ctl)) {
    // Only ctl without argument here is reset state..
    opus_encoder_ctl(enc, OPUS_RESET_STATE);
    CAMLreturn(Val_unit);
  } else {
    v   = Field(ctl,1);
    tag = Field(ctl,0);

    /* Generic controls. */
    get_ctl(tag, Get_final_range, enc, opus_encoder_ctl, OPUS_GET_FINAL_RANGE, v, opus_uint32);
    get_ctl(tag, Get_pitch, enc, opus_encoder_ctl, OPUS_GET_PITCH, v, opus_int32);
    get_value_ctl(tag, Get_bandwidth, enc, opus_encoder_ctl, OPUS_GET_BANDWIDTH, v, opus_int32, value_of_bandwidth);
    set_ctl(tag, Set_lsb_depth, enc, opus_encoder_ctl, OPUS_SET_LSB_DEPTH, v);
    get_ctl(tag, Get_lsb_depth, enc, opus_encoder_ctl, OPUS_GET_LSB_DEPTH, v, opus_int32);
#ifdef OPUS_SET_PHASE_INVERSION_DISABLED
    set_ctl(tag, Set_phase_inversion_disabled, enc, opus_encoder_ctl, OPUS_SET_PHASE_INVERSION_DISABLED, v);
#endif
    
    /* Encoder controls. */
    set_ctl(tag, Set_complexity, enc, opus_encoder_ctl, OPUS_SET_COMPLEXITY, v);
    get_ctl(tag, Get_complexity, enc, opus_encoder_ctl, OPUS_GET_COMPLEXITY, v, opus_int32);
    set_ctl(tag, Set_vbr, enc, opus_encoder_ctl, OPUS_SET_VBR, v);
    get_ctl(tag, Get_vbr, enc, opus_encoder_ctl, OPUS_GET_VBR, v, opus_int32);
    set_ctl(tag, Set_vbr_constraint, enc, opus_encoder_ctl, OPUS_SET_VBR_CONSTRAINT, v);
    get_ctl(tag, Get_vbr_constraint, enc, opus_encoder_ctl, OPUS_GET_VBR_CONSTRAINT, v, opus_int32);
    set_ctl(tag, Set_force_channels, enc, opus_encoder_ctl, OPUS_SET_FORCE_CHANNELS, v);
    get_ctl(tag, Get_force_channels, enc, opus_encoder_ctl, OPUS_GET_FORCE_CHANNELS, v, opus_int32);
    get_ctl(tag, Get_samplerate, enc, opus_encoder_ctl, OPUS_GET_SAMPLE_RATE, v, opus_int32);
    get_ctl(tag, Get_lookhead, enc, opus_encoder_ctl, OPUS_GET_LOOKAHEAD, v, opus_int32);
    set_ctl(tag, Set_inband_fec, enc, opus_encoder_ctl, OPUS_SET_INBAND_FEC, v);
    get_ctl(tag, Get_inband_fec, enc, opus_encoder_ctl, OPUS_GET_INBAND_FEC, v, opus_int32);
    set_ctl(tag, Set_packet_loss_perc, enc, opus_encoder_ctl, OPUS_SET_PACKET_LOSS_PERC, v);
    get_ctl(tag, Get_packet_loss_perc, enc, opus_encoder_ctl, OPUS_GET_PACKET_LOSS_PERC, v, opus_int32);
    set_ctl(tag, Set_dtx, enc, opus_encoder_ctl, OPUS_SET_DTX, v);
    get_ctl(tag, Get_dtx, enc, opus_encoder_ctl, OPUS_GET_DTX, v, opus_int32);

    /* These guys have polynmorphic variant as argument.. */
    set_value_ctl(tag, Set_bitrate, enc, opus_encoder_ctl, OPUS_SET_BITRATE, v, bitrate_of_value);
    get_value_ctl(tag, Get_bitrate, enc, opus_encoder_ctl, OPUS_GET_BITRATE, v, opus_int32, value_of_bitrate);
    set_value_ctl(tag, Set_max_bandwidth, enc, opus_encoder_ctl, OPUS_SET_MAX_BANDWIDTH, v, bandwidth_of_value);
    get_value_ctl(tag, Get_max_bandwidth, enc, opus_encoder_ctl, OPUS_GET_MAX_BANDWIDTH, v, opus_int32, value_of_bandwidth);
    set_value_ctl(tag, Set_bandwidth, enc, opus_encoder_ctl, OPUS_SET_BANDWIDTH, v, bandwidth_of_value);
    set_value_ctl(tag, Set_signal, enc, opus_encoder_ctl, OPUS_SET_SIGNAL, v, signal_of_value);
    get_value_ctl(tag, Get_signal, enc, opus_encoder_ctl, OPUS_GET_SIGNAL, v, opus_int32, value_of_signal);
    set_value_ctl(tag, Set_application, enc, opus_encoder_ctl, OPUS_SET_APPLICATION, v, application_of_value);
    get_value_ctl(tag, Get_application, enc, opus_encoder_ctl, OPUS_GET_APPLICATION, v, opus_int32, value_of_application);
  }

  caml_failwith("Unknown opus error");
}

CAMLprim value ocaml_opus_encode_float(value _frame_size, value _enc, value buf, value _off, value _len, value _os)
{
  CAMLparam3(_enc, buf, _os);
  encoder_t *handler = Enc_val(_enc);
  OpusEncoder *enc = handler->encoder;
  ogg_stream_state *os = Stream_state_val(_os);
  ogg_packet op;
  int off = Int_val(_off);
  int len = Int_val(_len);
  int frame_size = Int_val(_frame_size);

  if (len < frame_size)
    caml_raise_constant(*caml_named_value("opus_exn_buffer_too_small"));

  int chans = Wosize_val(buf);
  /* This is the recommended value */
  int max_data_bytes = 4000;
  unsigned char *data = malloc(max_data_bytes);
  if (data == NULL)
    caml_raise_out_of_memory();
  float *pcm = malloc(chans*frame_size*sizeof(float));
  if (data == NULL)
    caml_raise_out_of_memory();
  int i, j, c;
  int ret;
  int loops = len / frame_size;
  for (i = 0; i < loops; i++) {
    for (j = 0; j < frame_size; j++)
      for(c = 0; c < chans; c++)
        pcm[chans*j+c] = Double_field(Field(buf, c), off+j+i*frame_size);

    caml_release_runtime_system();
    ret = opus_encode_float(enc, pcm, frame_size, data, max_data_bytes);
    caml_acquire_runtime_system();

    if (ret < 0) {
      free(pcm);
      free(data);
    }
    check(ret);

    /* From the documentation: If the return value is 1 byte,
     * then the packet does not need to be transmitted (DTX). */
    if (ret < 2)
      continue;

    handler->granulepos += frame_size*handler->samplerate_ratio;
    handler->packetno++;

    op.bytes  = ret; 
    op.packet = data;
    op.b_o_s = op.e_o_s = 0;
    op.packetno = handler->packetno;
    op.granulepos = handler->granulepos;

    if (ogg_stream_packetin(os, &op) != 0)
      caml_raise_constant(*caml_named_value("ogg_exn_internal_error"));;
  }
  free(pcm);
  free(data);

  CAMLreturn(Val_int(loops*frame_size));
}

CAMLprim value ocaml_opus_encode_float_byte(value *argv, int argn)
{
  return ocaml_opus_encode_float(argv[0], argv[1], argv[2],
                                 argv[3], argv[4], argv[5]);
}

CAMLprim value ocaml_opus_encode_eos(value _os, value _enc) {
  CAMLparam2(_os, _enc);
  ogg_stream_state *os = Stream_state_val(_os);
  ogg_packet op;
  encoder_t *handler = Enc_val(_enc);
  handler->packetno++;
  
  op.bytes  = 0;
  op.packet = NULL;
  op.b_o_s  = 0;
  op.e_o_s  = 1;
  op.packetno = handler->packetno;
  op.granulepos = handler->granulepos;

  if (ogg_stream_packetin(os, &op) != 0)
    caml_raise_constant(*caml_named_value("ogg_exn_internal_error"));;

  CAMLreturn(Val_unit);
}
