/* $Id: jack_stubs.c 2604 2006-07-07 09:35:43Z smimram $ */

#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>

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>

static void check_for_err(int ret, char* caml_name)
{
  /* TODO: custom exception(s) */
  if (ret)
    caml_failwith(caml_name);
}

static value error_function = (value)NULL;

static void custom_error_function(const char *msg)
{
  /* TODO: leave blocking sections! */
  caml_callback(error_function, caml_copy_string(msg));
}

CAMLprim value ocaml_jack_set_error_function(value f)
{
  CAMLparam1(f);

  if (!error_function)
    caml_register_global_root(&error_function);
  error_function = f;
  jack_set_error_function(custom_error_function);

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_get_sample_size(value unit)
{
  CAMLparam1(unit);
  CAMLreturn(Val_int(sizeof(jack_default_audio_sample_t)));
}

/* TODO: function to convert from jacks' jack_default_audio_sample_t to 16 bits */

/***************
 * Ringbuffers *
 ***************/

#define Ringbuffer_val(v) (*(jack_ringbuffer_t**)Data_custom_val(v))

static void finalize_ringbuffer(value rbv)
{
  jack_ringbuffer_t *rb = Ringbuffer_val(rbv);
  jack_ringbuffer_free(rb);
}

static struct custom_operations ringbuffer_ops =
{
  "ocaml_jack_ringbuffer",
  finalize_ringbuffer,
  custom_compare_default,
  custom_hash_default,
  custom_serialize_default,
  custom_deserialize_default
};

CAMLprim value ocaml_jack_ringbuffer_create(value size)
{
  CAMLparam1(size);
  CAMLlocal1(rbv);
  jack_ringbuffer_t *rb;

  rb = jack_ringbuffer_create(Int_val(size));
  rbv = caml_alloc_custom(&ringbuffer_ops, sizeof(jack_ringbuffer_t*), 0, 1);
  Ringbuffer_val(rbv) = rb;
  printf("create rb: %d\n", rbv);

  CAMLreturn(rbv);
}

CAMLprim value ocaml_jack_ringbuffer_read(value rbv, value buf, value ofs, value len)
{
  CAMLparam4(rbv, buf, ofs, len);
  size_t n;

  n = jack_ringbuffer_read(Ringbuffer_val(rbv), String_val(buf) + Int_val(ofs), Int_val(len));

  CAMLreturn(Val_int(n));
}

CAMLprim value ocaml_jack_ringbuffer_read_advance(value rbv, value ofs)
{
  CAMLparam2(rbv, ofs);

  jack_ringbuffer_read_advance(Ringbuffer_val(rbv), Int_val(ofs));

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_ringbuffer_read_space(value rbv)
{
  CAMLparam1(rbv);
  size_t n;

  n = jack_ringbuffer_read_space(Ringbuffer_val(rbv));

  CAMLreturn(Val_int(n));
}

CAMLprim value ocaml_jack_ringbuffer_reset(value rbv)
{
  CAMLparam1(rbv);

  jack_ringbuffer_reset(Ringbuffer_val(rbv));

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_ringbuffer_write(value rbv, value buf, value ofs, value len)
{
  CAMLparam4(rbv, buf, ofs,  len);
  size_t n;

  n = jack_ringbuffer_write(Ringbuffer_val(rbv), String_val(buf) + Int_val(ofs), Int_val(len));

  CAMLreturn(Val_int(n));
}

CAMLprim value ocaml_jack_ringbuffer_write_advance(value rbv, value len)
{
  CAMLparam2(rbv, len);

  jack_ringbuffer_write_advance(Ringbuffer_val(rbv), Int_val(len));

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_ringbuffer_write_space(value rbv)
{
  CAMLparam1(rbv);
  size_t n;

  n = jack_ringbuffer_write_space(Ringbuffer_val(rbv));

  CAMLreturn(Val_int(n));
}

/*********
 * Ports *
 *********/

#define Port_val(v) ((jack_port_t*)v)
#define Val_port(p) ((value)p)

CAMLprim value ocaml_jack_port_name(value pv)
{
  CAMLparam1(pv);
  CAMLreturn(caml_copy_string(jack_port_name(Port_val(pv))));
}

CAMLprim value ocaml_jack_port_short_name(value pv)
{
  CAMLparam1(pv);
  CAMLreturn(caml_copy_string(jack_port_short_name(Port_val(pv))));
}

CAMLprim value ocaml_jack_port_flags(value pv)
{
  CAMLparam1(pv);
  CAMLlocal1(ret);
  int i = 0, n = 0;
  int flags = jack_port_flags(Port_val(pv));

  if (flags & JackPortIsInput)
    n++;
  if (flags & JackPortIsOutput)
    n++;
  if (flags & JackPortIsPhysical)
    n++;
  if (flags & JackPortCanMonitor)
    n++;
  if (flags & JackPortIsTerminal)
    n++;

  ret = caml_alloc_tuple(n);
  if (flags & JackPortIsInput)
    Store_field(ret, i++, Val_int(0));
  if (flags & JackPortIsOutput)
    Store_field(ret, i++, Val_int(1));
  if (flags & JackPortIsPhysical)
    Store_field(ret, i++, Val_int(2));
  if (flags & JackPortCanMonitor)
    Store_field(ret, i++, Val_int(3));
  if (flags & JackPortIsTerminal)
    Store_field(ret, i++, Val_int(4));

  CAMLreturn(ret);
}

static unsigned long long_of_flags_list(value flags)
{
  int i;
  unsigned long ret = 0;

  if (Is_long(flags))
    return 0;

  for (i = 0; i < Wosize_val(flags); i++)
    switch (Int_val(Field(flags, i)))
    {
      case 0:
        ret |= JackPortIsInput;
        break;

      case 1:
        ret |= JackPortIsOutput;
        break;

      case 2:
        ret |= JackPortIsPhysical;
        break;

      case 3:
        ret |= JackPortCanMonitor;
        break;

      case 4:
        ret |= JackPortIsTerminal;
        break;

      default:
        break;
    }

  return (ret);
}

CAMLprim value ocaml_jack_port_type(value pv)
{
  CAMLparam1(pv);
  CAMLreturn(caml_copy_string(jack_port_type(Port_val(pv))));
}

CAMLprim value ocaml_jack_port_connected(value pv)
{
  CAMLparam1(pv);
  CAMLreturn(Val_int(jack_port_connected(Port_val(pv))));
}

CAMLprim value ocaml_jack_port_connected_to(value pv, value pn)
{
  CAMLparam2(pv, pn);
  CAMLreturn(Val_int(jack_port_connected_to(Port_val(pv), String_val(pn))));
}

/****************
 * Jack clients *
 ****************/

#define DIR_READ 0
#define DIR_WRITE 1

typedef struct
{
  jack_client_t *client;
  value on_shutdown_callback;
  value client_process_callback_caml_mutex;
  pthread_mutex_t *client_process_callback_mutex; /* V when a caml callback should be called */
  pthread_t *client_process_callback_poller;
  value *client_process_callback_ringbuffersv; /* ringbuffers used by callback functions, it is a couple inputing / output ringbuffer, needed for the caml_register_global_root */
  /* TODO: array of tuples? */
  jack_ringbuffer_t **client_process_callback_ringbuffers; /* C version of the ringbuffers */
  int client_process_callback_ringbuffers_nb; /* size of the client_process_callback_ringbuffer array */
  jack_port_t **client_process_callback_ringbuffers_port;
  int *client_process_callback_ringbuffers_dir; /* read / write direction for each buffer in client_process_callback_ringbuffer array */
} caml_client_t;

#define Caml_client_val(v) (*(caml_client_t**)Data_custom_val(v))
#define Client_val(v) (Caml_client_val(v)->client)

static void remove_process_callback(caml_client_t *cc)
{
  int i;

  if (!cc->client_process_callback_ringbuffersv)
  {
    printf("Not removing process callback\n");
    return;
  }

  for (i = 0; i < cc->client_process_callback_ringbuffers_nb; i++)
    caml_remove_global_root(&cc->client_process_callback_ringbuffersv[i]);
  cc->client_process_callback_ringbuffers_nb = 0;
  free(cc->client_process_callback_ringbuffers);
  cc->client_process_callback_ringbuffers = NULL;
  free(cc->client_process_callback_ringbuffersv);
  cc->client_process_callback_ringbuffersv = (value)NULL;
  free(cc->client_process_callback_ringbuffers_port);
  cc->client_process_callback_ringbuffers_port = NULL;
}

static void finalize_client(value cv)
{
  printf("Finalizing client!\n");
  caml_client_t *cc = Caml_client_val(cv);
  if (cc->on_shutdown_callback)
    caml_remove_global_root(&cc->on_shutdown_callback);
  caml_remove_global_root(&cc->client_process_callback_caml_mutex);
  free(cc->client_process_callback_mutex);
  /* TODO: kill polling thread */
  free(cc->client_process_callback_poller);
  remove_process_callback(cc);
  free(cc);
}

static struct custom_operations client_ops =
{
  "ocaml_jack_client",
  finalize_client,
  custom_compare_default,
  custom_hash_default,
  custom_serialize_default,
  custom_deserialize_default
};

static void* poll_for_callback(void *arg)
{
  caml_client_t *cc = (caml_client_t*)arg;
  while(1)
  {
    /* TODO: condition? */
    pthread_mutex_lock(cc->client_process_callback_mutex);
    caml_callback(*caml_named_value("caml_mutex_unlock"), cc->client_process_callback_caml_mutex);
  }
}

CAMLprim value ocaml_jack_client_new(value name)
{
  CAMLparam1(name);
  CAMLlocal1(cv);
  jack_client_t *jc;
  caml_client_t *cc;

  jc = jack_client_new(String_val(name));
  if (!jc)
    caml_failwith("Jack.Client.new");
  cc = malloc(sizeof(caml_client_t));
  cc->client = jc;
  cc->on_shutdown_callback = (value)NULL;
  cc->client_process_callback_ringbuffersv = (value)NULL;
  cc->client_process_callback_ringbuffers = NULL;
  cc->client_process_callback_ringbuffers_nb = 0;
  cc->client_process_callback_ringbuffers_port = NULL;
  cc->client_process_callback_ringbuffers_dir = (value)NULL;
  cv = caml_alloc_custom(&client_ops, sizeof(caml_client_t*), 0, 1);
  Caml_client_val(cv) = cc;

  caml_register_global_root(&cc->client_process_callback_caml_mutex);
  cc->client_process_callback_caml_mutex = caml_callback(*caml_named_value("caml_mutex_create"), Val_unit);
  cc->client_process_callback_mutex = malloc(sizeof(pthread_mutex_t));
  pthread_mutex_init(cc->client_process_callback_mutex, NULL);
  pthread_mutex_lock(cc->client_process_callback_mutex);
  cc->client_process_callback_poller = malloc(sizeof(pthread_t));
  pthread_create(cc->client_process_callback_poller, NULL, poll_for_callback, cc);

  CAMLreturn(cv);
}

CAMLprim value ocaml_jack_client_close(value cv)
{
  CAMLparam1(cv);

  check_for_err(jack_client_close(Client_val(cv)), "Jack.Client.close");

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_is_realtime(value cv)
{
  CAMLparam1(cv);
  CAMLreturn(Val_int(jack_is_realtime(Client_val(cv))));
}

static int ringbuffer_callback(jack_nframes_t nframes, void *arg)
{
  int i;
  jack_default_audio_sample_t *port_buf;
  caml_client_t *cc = (caml_client_t*)arg;

  for (i = 0; i < cc->client_process_callback_ringbuffers_nb; i++)
  {
    port_buf = jack_port_get_buffer(cc->client_process_callback_ringbuffers_port[i], nframes);
    if (cc->client_process_callback_ringbuffers_dir[i] == DIR_READ)
      jack_ringbuffer_write(cc->client_process_callback_ringbuffers[i], (char*)port_buf, sizeof(jack_default_audio_sample_t) * nframes);
    else
      jack_ringbuffer_read(cc->client_process_callback_ringbuffers[i], (char*)port_buf, sizeof(jack_default_audio_sample_t) * nframes);
  }
  pthread_mutex_unlock(cc->client_process_callback_mutex);

  return 0;
}

CAMLprim value ocaml_jack_set_process_ringbuffer_callback(value cv, value bufs)
{
  CAMLparam2(cv, bufs);
  int i,n=0;
  value * bufptr = &bufs;
  caml_client_t *cc = Caml_client_val(cv);

  remove_process_callback(cc);
  if (Is_long(bufs))
  {
    printf("Empty callback\n");
    return;
  }
  printf("n: %d\n", Wosize_val(bufs));
  n++;
  while(bufptr != NULL && 2 == Wosize_val(*bufptr)) {
    printf("XXX n: %d\n", Wosize_val(*bufptr));
    printf("XXX n: %d - 1 bufs[i]: %d    rbv: %d\n", n, Field(*bufptr, 0), Field(Field(*bufptr, 0), 1));
    printf("XXX n: %d - 2 bufs[i]: %d  %d\n", n, Field(*bufptr, 1),bufptr);
    n++;
    if (&Field(bufs,1) == bufptr) {
      bufptr = NULL;
    } else {
      bufptr = &Field(bufs,1);
    }
  }
  cc->client_process_callback_ringbuffers_nb = n; /* Wosize_val(bufs); */
  cc->client_process_callback_ringbuffersv = malloc(sizeof(value) * cc->client_process_callback_ringbuffers_nb);
  cc->client_process_callback_ringbuffers = malloc(sizeof(jack_ringbuffer_t*) * cc->client_process_callback_ringbuffers_nb);
  cc->client_process_callback_ringbuffers_port = malloc(sizeof(jack_port_t*) * cc->client_process_callback_ringbuffers_nb);
  cc->client_process_callback_ringbuffers_dir = malloc(sizeof(int) * cc->client_process_callback_ringbuffers_nb);

  bufptr = &bufs; /* init the linked list */
  for (i = 0; i < cc->client_process_callback_ringbuffers_nb; i++)
  {    
    printf("i: %d     bufs[i]: %d    rbv: %d\n", i, Field(*bufptr, 0), Field(Field(*bufptr, 0), 1));
    caml_register_global_root(&cc->client_process_callback_ringbuffersv[i]);
    printf("Registered Global Root\n");
    cc->client_process_callback_ringbuffersv[i] = Field(Field(*bufptr, 0), 1);
    cc->client_process_callback_ringbuffers[i] = Ringbuffer_val(cc->client_process_callback_ringbuffersv[i]);
    printf("Set Ring Buffer\n");
    cc->client_process_callback_ringbuffers_port[i] = Port_val(Field(Field(*bufptr, 0), 0));
    cc->client_process_callback_ringbuffers_dir[i] = Int_val(Field(Field(*bufptr, 0), 2));
    bufptr = &Field(bufs,1); /* iterate linked list */
  }
  bufptr = NULL; 
  printf("Set Process callback\n");
  jack_set_process_callback(Client_val(cv), ringbuffer_callback, cc);

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_get_process_callback_mutex(value cv)
{
  CAMLparam1(cv);
  caml_client_t *cc = Caml_client_val(cv);
  CAMLreturn(cc->client_process_callback_caml_mutex);
}

CAMLprim value ocaml_jack_activate(value cv)
{
  CAMLparam1(cv);

  check_for_err(jack_activate(Client_val(cv)), "Jack.Client.activate");

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_deactivate(value cv)
{
  CAMLparam1(cv);

  check_for_err(jack_deactivate(Client_val(cv)), "Jack.Client.deactivate");

  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_get_max_buffer_size(value cv)
{
  CAMLparam1(cv);
  CAMLreturn(Val_int(jack_get_buffer_size(Client_val(cv))));
}

static void on_shutdown_callback(void *arg)
{
  caml_client_t *cc = (caml_client_t*)arg;
  caml_callback(cc->on_shutdown_callback, Val_unit);
}

CAMLprim value ocaml_jack_on_shutdown(value cv, value cb)
{
  CAMLparam2(cv, cb);
  caml_client_t *cc = Caml_client_val(cv);
  cc->on_shutdown_callback = cb;
  jack_on_shutdown(cc->client, on_shutdown_callback, cc);
  CAMLreturn(Val_unit);
}

CAMLprim value ocaml_jack_port_register(value cv, value name, value type, value flags, value bufsize)
{
  CAMLparam5(cv, name, type, flags, bufsize);
  CAMLreturn(Val_port(jack_port_register(Client_val(cv), String_val(name), String_val(type), long_of_flags_list(flags), Int_val(bufsize))));
}

CAMLprim value ocaml_jack_get_sample_rate(value cv)
{
  CAMLparam1(cv);
  CAMLreturn(Val_int(jack_get_sample_rate(Client_val(cv))));
}
