/*
 * Copyright (C) 2003-2008 Samuel Mimram
 *           (C) 2008-2010 The Savonet Team
 *
 * This file is part of Ocaml-faad.
 *
 * Ocaml-faad 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Ocaml-faad 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 Ocaml-faad; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * Libfaad bindings for OCaml.
 *
 * @author Samuel Mimram
 */


#include <caml/alloc.h>
#include <caml/callback.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include <caml/memory.h>
#include <caml/mlvalues.h>
#include <caml/signals.h>
#include <caml/threads.h>
#include <caml/unixsupport.h>

#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>

#include <neaacdec.h>
#include "mp4ff.h"

static void check_err(int n)
{
  if (n < 0)
    caml_raise_constant(*caml_named_value("ocaml_faad_exn_failed"));
}

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

static void finalize_faad_dec(value l)
{
  NeAACDecHandle dh = Dec_val(l);
  NeAACDecClose(dh);
}

static struct custom_operations faad_dec_ops =
{
  "ocaml_faad_dec",
  finalize_faad_dec,
  custom_compare_default,
  custom_hash_default,
  custom_serialize_default,
  custom_deserialize_default
};

CAMLprim value ocaml_faad_open(value unit)
{
  CAMLparam0();
  CAMLlocal1(ret);
  NeAACDecHandle dh = NeAACDecOpen();
  NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(dh);

  conf->outputFormat = FAAD_FMT_FLOAT;
  NeAACDecSetConfiguration(dh, conf);

  ret  = caml_alloc_custom(&faad_dec_ops, sizeof(NeAACDecHandle), 0, 1);
  Dec_val(ret) = dh;

  CAMLreturn(ret);
}

CAMLprim value ocaml_faad_min_bytes_per_channel(value unit)
{
  return Val_int(FAAD_MIN_STREAMSIZE);
}

CAMLprim value ocaml_faad_init(value dh, value _buf, value _ofs, value _len)
{
  CAMLparam2(dh,_buf);
  CAMLlocal1(ans);

  unsigned long samplerate;
  uint8_t channels;
  int32_t offset;
  int32_t pre_offset = 0;
  int ofs = Int_val(_ofs);
  int len = Int_val(_len);
  unsigned char *buf = (unsigned char*)String_val(_buf);
  int i;

  /* ADTS mpeg file can be a stream and start in the middle of a
   * frame so we need to have extra loop check here */
  for (i = ofs; i < len - 1; i++)
  {
    if (buf[i] == 0xff && (buf[i+1] & 0xf6) == 0xf0) 
    {
      pre_offset =  i;
      break;
    }
  }

  offset = NeAACDecInit(Dec_val(dh), buf+ofs+pre_offset, len-pre_offset, &samplerate, &channels);
  check_err(offset);

  ans = caml_alloc_tuple(3);
  Store_field(ans, 0, Val_int(offset+pre_offset));
  Store_field(ans, 1, Val_int(samplerate));
  Store_field(ans, 2, Val_int(channels));
  CAMLreturn(ans);
}

CAMLprim value ocaml_faad_post_seek_reset(value _dh)
{
  CAMLparam1(_dh);
  NeAACDecHandle dh = Dec_val(_dh);
  NeAACDecPostSeekReset(dh,0);
  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_faad_decode(value _dh, value _inbuf, value _inbufofs, value _inbuflen)
{
  CAMLparam2(_dh,_inbuf);
  CAMLlocal2(ans, outbuf);
  NeAACDecFrameInfo frameInfo;
  int inbufofs = Int_val(_inbufofs);
  int inbuflen = Int_val(_inbuflen);
  unsigned char *inbuf = malloc(inbuflen);
  float *data;
  int c, i;

  memcpy(inbuf, String_val(_inbuf)+inbufofs, inbuflen);

  NeAACDecHandle dh = Dec_val(_dh);

  caml_release_runtime_system();
  data = NeAACDecDecode(dh, &frameInfo, inbuf, inbuflen);
  caml_acquire_runtime_system();

  free(inbuf);

  if (frameInfo.error > 0)
    caml_raise_with_arg(*caml_named_value("ocaml_faad_exn_error"), Val_int(frameInfo.error));
  if (!data)
    caml_raise_constant(*caml_named_value("ocaml_faad_exn_failed"));

  outbuf = caml_alloc_tuple(frameInfo.channels);
  for(c = 0; c < frameInfo.channels; c++)
    Store_field(outbuf, c, caml_alloc(frameInfo.samples / frameInfo.channels * Double_wosize, Double_array_tag));
  for(i = 0; i < frameInfo.samples; i++)
    Store_double_field(Field(outbuf, i % frameInfo.channels), i / frameInfo.channels, data[i]);

  ans = caml_alloc_tuple(2);
  Store_field(ans, 0, Val_int(frameInfo.bytesconsumed));
  Store_field(ans, 1, outbuf);

  CAMLreturn(ans);
}

CAMLprim value ocaml_faad_get_error_message(value err)
{
  return caml_copy_string((char*)NeAACDecGetErrorMessage(Int_val(err)));
}

/***** MP4 *****/

typedef struct
{
  mp4ff_t *ff;
  mp4ff_callback_t ff_cb;
  int fd;
  value read_cb;
  value write_cb;
  value seek_cb;
  value trunc_cb;
} mp4_t;

#define Mp4_val(v) (*((mp4_t**)Data_custom_val(v)))

static void finalize_mp4(value e)
{
  mp4_t *mp = Mp4_val(e);

  if (mp->ff)
    mp4ff_close(mp->ff);
  if (mp->read_cb)
    caml_remove_generational_global_root(&mp->read_cb);
  if (mp->write_cb)
    caml_remove_generational_global_root(&mp->write_cb);
  if (mp->seek_cb)
    caml_remove_generational_global_root(&mp->seek_cb);
  if (mp->trunc_cb)
    caml_remove_generational_global_root(&mp->trunc_cb);

  free(mp);
}

static struct custom_operations mp4_ops =
{
  "ocaml_mp4_t",
  finalize_mp4,
  custom_compare_default,
  custom_hash_default,
  custom_serialize_default,
  custom_deserialize_default
};

static uint32_t read_cb(void *user_data, void *buffer, uint32_t length)
{
  mp4_t *mp = (mp4_t*)user_data;
  value ans;
  int len;
  value tmp;

  if (mp->fd != -1)
    return read(mp->fd, buffer, length);
  else
  {
    caml_acquire_runtime_system();

    tmp = caml_alloc_string(length);
    caml_register_generational_global_root(&tmp);
    
    ans = caml_callback3_exn(mp->read_cb, tmp, Val_int(0), Val_int(length));

    if (Is_exception_result(ans)) {
      ans = Extract_exception(ans);
      caml_remove_generational_global_root(&tmp);
      caml_raise(ans);
    }

    len = Int_val(ans);

    if (len > 0) memcpy(buffer, String_val(tmp), len);

    caml_remove_generational_global_root(&tmp);

    caml_release_runtime_system();

    return len;
  }
}

static uint32_t write_cb(void *user_data, void *buffer, uint32_t length)
{
  mp4_t *mp = (mp4_t*)user_data;

  if (mp->fd != -1)
    return write(mp->fd, buffer, length);
  else
    return 0;
}

static uint32_t seek_cb(void *user_data, uint64_t position)
{
  mp4_t *mp = (mp4_t*)user_data;
  value ans;
  int pos;

  if (mp->fd != -1)
    return lseek(mp->fd, position, SEEK_SET)!=-1?0:-1;
  else
  {
    caml_acquire_runtime_system();
    ans = caml_callback(mp->seek_cb, Val_int(position));
    pos = Int_val(ans);
    caml_release_runtime_system();

    return pos;
  }
}

static uint32_t trunc_cb(void *user_data)
{
  //mp4_t *mp = (mp4_t*)user_data;

  return -1;
}

CAMLprim value ocaml_faad_mp4_open_read(value metaonly, value read, value write, value seek, value trunc)
{
  CAMLparam4(read, write, seek, trunc);
  CAMLlocal1(ans);

  mp4_t *mp = malloc(sizeof(mp4_t));
  mp->fd = -1;
  mp->ff_cb.read = read_cb;
  mp->read_cb = read;
  caml_register_generational_global_root(&mp->read_cb);
  if (Is_block(write))
  {
    mp->ff_cb.write = write_cb;
    mp->write_cb =  Field(write, 0);
    caml_register_generational_global_root(&mp->write_cb);
  }
  else
  {
    mp->ff_cb.write = NULL;
    mp->write_cb = (value)NULL;
  }
  if (Is_block(seek))
  {
    mp->ff_cb.seek = seek_cb;
    mp->seek_cb = Field(seek, 0);
    caml_register_generational_global_root(&mp->seek_cb);
  }
  else
  {
    mp->ff_cb.seek = NULL;
    mp->seek_cb = (value)NULL;
  }
  if (Is_block(trunc))
  {
    mp->ff_cb.truncate = trunc_cb;
    mp->trunc_cb = Field(trunc, 0);
    caml_register_generational_global_root(&mp->trunc_cb);
  }
  else
  {
    mp->ff_cb.truncate = NULL;
    mp->trunc_cb = (value)NULL;
  }
  mp->ff_cb.user_data = mp;

  caml_release_runtime_system();
  if(Bool_val(metaonly))
    mp->ff = mp4ff_open_read_metaonly(&mp->ff_cb);
  else
    mp->ff = mp4ff_open_read(&mp->ff_cb);
  caml_acquire_runtime_system();
  assert(mp->ff);

  ans = caml_alloc_custom(&mp4_ops, sizeof(mp4_t*), 1, 0);
  Mp4_val(ans) = mp;

  CAMLreturn(ans);
}

#ifdef WIN32
#define GET_FD(fh) win_CRT_fd_of_filedescr(fh)
#else
#define GET_FD(fh) Int_val(fh)
#endif

CAMLprim value ocaml_faad_mp4_open_read_fd(value metaonly, value fd)
{
  CAMLparam2(metaonly, fd);
  CAMLlocal1(ans);

  mp4_t *mp = malloc(sizeof(mp4_t));
  mp->fd = GET_FD(fd);
  mp->ff_cb.read = read_cb;
  mp->read_cb = (value)NULL;
  mp->ff_cb.write = write_cb;
  mp->write_cb = (value)NULL;
  mp->ff_cb.seek = seek_cb;
  mp->seek_cb = (value)NULL;
  mp->ff_cb.truncate = trunc_cb;
  mp->trunc_cb = (value)NULL;
  mp->ff_cb.user_data = mp;

  caml_release_runtime_system();
  if(Bool_val(metaonly))
    mp->ff = mp4ff_open_read_metaonly(&mp->ff_cb);
  else
    mp->ff = mp4ff_open_read(&mp->ff_cb);
  caml_acquire_runtime_system();
  assert(mp->ff);

  ans = caml_alloc_custom(&mp4_ops, sizeof(mp4_t*), 1, 0);
  Mp4_val(ans) = mp;

  CAMLreturn(ans);
}

CAMLprim value ocaml_faad_mp4_total_tracks(value m)
{
  CAMLparam1(m);
  mp4_t *mp = Mp4_val(m);
  int n;

  caml_release_runtime_system();
  n = mp4ff_total_tracks(mp->ff);
  caml_acquire_runtime_system();

  CAMLreturn(Val_int(n));
}

CAMLprim value ocaml_faad_mp4_seek(value m, value track, value offset)
{
  CAMLparam1(m);
  CAMLlocal1(ret);
  int32_t toskip = 0;
  mp4_t *mp = Mp4_val(m);
  int t = Int_val(track);

  caml_release_runtime_system();
  int sample = mp4ff_find_sample(mp->ff,t,(int64_t)(Int_val(offset)),&toskip);
  caml_acquire_runtime_system();

  ret = caml_alloc_tuple(2);
  Field(ret,0) = Val_int(sample);
  Field(ret,1) = Val_int(toskip);

  CAMLreturn(ret);
}

CAMLprim value ocaml_faad_mp4_find_aac_track(value m)
{
  CAMLparam1(m);
  mp4_t *mp = Mp4_val(m);

  int i, rc;
  int num_tracks;

  caml_release_runtime_system();
  num_tracks = mp4ff_total_tracks(mp->ff);
  for (i = 0; i < num_tracks; i++) {
    unsigned char *buff = NULL;
    unsigned int buff_size = 0;
    mp4AudioSpecificConfig mp4ASC;

    mp4ff_get_decoder_config(mp->ff, i, &buff, &buff_size);

    if (buff)
    {
      rc = NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC);
      free(buff);
      if (rc < 0)
        continue;
      caml_acquire_runtime_system();
      CAMLreturn(Val_int(i));
    }
  }

  caml_acquire_runtime_system();
  caml_raise_constant(*caml_named_value("ocaml_faad_exn_failed"));
}

CAMLprim value ocaml_faad_mp4_init(value m, value dh, value track)
{
  CAMLparam3(m, dh, track);
  CAMLlocal1(ans);
  mp4_t *mp = Mp4_val(m);
  int t = Int_val(track);
  int ret;
  long unsigned int samplerate;
  unsigned char channels;
  NeAACDecHandle dec = Dec_val(dh);

  unsigned char *mp4_buffer = NULL;
  unsigned int mp4_buffer_size = 0;

  caml_release_runtime_system();
  mp4ff_get_decoder_config(mp->ff, t, &mp4_buffer, &mp4_buffer_size);
  ret = NeAACDecInit2(dec, mp4_buffer, mp4_buffer_size, &samplerate, &channels);
  caml_acquire_runtime_system();

  free(mp4_buffer);
  check_err(ret);

  ans = caml_alloc_tuple(2);
  Store_field(ans, 0, Val_int(samplerate));
  Store_field(ans, 1, Val_int(channels));

  CAMLreturn(ans);
}

CAMLprim value ocaml_faad_mp4_num_samples(value m, value track)
{
  CAMLparam2(m, track);
  mp4_t *mp = Mp4_val(m);
  int t = Int_val(track);
  int ans;

  caml_release_runtime_system();
  ans = mp4ff_num_samples(mp->ff, t);
  caml_acquire_runtime_system();

  CAMLreturn(Val_int(ans));
}

CAMLprim value ocaml_faad_mp4_read_sample(value m, value track, value sample)
{
  CAMLparam3(m, track, sample);
  CAMLlocal1(ans);
  mp4_t *mp = Mp4_val(m);
  int t = Int_val(track);
  int s = Int_val(sample);
  unsigned char *buf = NULL;
  unsigned int buflen = 0;
  int ret;

  caml_release_runtime_system();
  ret = mp4ff_read_sample(mp->ff, t, s, &buf, &buflen);
  caml_acquire_runtime_system();
  check_err(ret);

  ans = caml_alloc_string(buflen);
  memcpy(String_val(ans), buf, buflen);
  free(buf);

  CAMLreturn(ans);
}

// Same as Faad.decode (Faad.Mp4.read_sample) but more efficient. Share code?
CAMLprim value ocaml_faad_mp4_decode(value m, value track, value sample, value dh)
{
  CAMLparam4(m, track, sample, dh);
  CAMLlocal1(outbuf);
  mp4_t *mp = Mp4_val(m);
  int t = Int_val(track);
  int s = Int_val(sample);
  NeAACDecHandle dec = Dec_val(dh);
  NeAACDecFrameInfo frameInfo;
  unsigned char *inbuf = NULL;
  unsigned int inbuflen = 0;
  float *data;
  int c, i, ret;

  caml_release_runtime_system();
  ret = mp4ff_read_sample(mp->ff, t, s, &inbuf, &inbuflen);
  caml_acquire_runtime_system();

  check_err(ret);

  caml_release_runtime_system();
  data = NeAACDecDecode(dec, &frameInfo, inbuf, inbuflen);
  caml_acquire_runtime_system();

  free(inbuf);

  if (!data)
    caml_raise_constant(*caml_named_value("ocaml_faad_exn_failed"));
  if (frameInfo.error != 0)
    caml_raise_with_arg(*caml_named_value("ocaml_faad_exn_error"), Val_int(frameInfo.error));

  outbuf = caml_alloc_tuple(frameInfo.channels);
  for(c = 0; c < frameInfo.channels; c++)
    Store_field(outbuf, c, caml_alloc(frameInfo.samples / frameInfo.channels * Double_wosize, Double_array_tag));
  for(i = 0; i < frameInfo.samples; i++)
    Store_double_field(Field(outbuf, i % frameInfo.channels), i / frameInfo.channels, data[i]);

  CAMLreturn(outbuf);
}

CAMLprim value ocaml_faad_mp4_metadata(value m)
{
  CAMLparam1(m);
  CAMLlocal2(ans,v);
  mp4_t *mp = Mp4_val(m);
  int i, n;
  char *tag, *item;

  caml_release_runtime_system();
  n = mp4ff_meta_get_num_items(mp->ff);
  caml_acquire_runtime_system();

  ans = caml_alloc_tuple(n);
  for (i = 0; i < n; i++)
  {
    tag = NULL;
    item = NULL;

    caml_release_runtime_system();
    mp4ff_meta_get_by_index(mp->ff, i, &item, &tag);
    caml_acquire_runtime_system();

    assert(item && tag);
    v = caml_alloc_tuple(2);
    Store_field(v, 0, caml_copy_string(item));
    Store_field(v, 1, caml_copy_string(tag));
    Store_field(ans, i, v);
    free(item);
    free(tag);
  }

  CAMLreturn(ans);
}
