/**
   OctaveJack v0.1 -- Copyright 2008 Mark Tobenkin <mmt@mit.edu>
   
   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, either version 3 of the License, or
   (at your option) any later version.
   
   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, see <http://www.gnu.org/licenses/>.
**/
#include <string>
#include <iostream>
#include <math.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>
#include <octave/oct.h>

typedef struct dad_info_s {
  jack_client_t *client;
  jack_port_t **ports;
  jack_default_audio_sample_t **in;
  jack_ringbuffer_t *samples;
  size_t sample_size;
  size_t frame_width;
  size_t chan_no;
  size_t fifo_size;
  size_t sampling_rate;
  size_t overruns;
} dad_info_t;

typedef dad_info_t *dad_info;

static size_t dad_bytes_to_frames(dad_info dad,size_t bytes)
{
  return bytes/((dad->chan_no)*(dad->frame_width)*(dad->sample_size));
}


static dad_info dad_info_new(jack_client_t *client,size_t chan_no,
		      size_t frame_width, size_t frame_no)
{
  dad_info dad = (dad_info)malloc(sizeof(dad_info_t));
  dad->client = client;

  
  dad->ports = (jack_port_t**)malloc(sizeof(jack_port_t*)*chan_no);
  dad->in = (jack_default_audio_sample_t**)malloc(sizeof(jack_default_audio_sample_t*)*chan_no);
  int c;
  for(c = 0; c < chan_no; c++){
    dad->ports[c] = 0;
    dad->in[c] = 0;
  }
  
  dad->sample_size = sizeof(jack_default_audio_sample_t);
  dad->chan_no = chan_no;
  dad->frame_width = frame_width;

  dad->samples = jack_ringbuffer_create(chan_no*frame_width*frame_no*
					(dad->sample_size));
  
  dad->fifo_size = dad_bytes_to_frames(dad,jack_ringbuffer_write_space(dad->samples));

  dad->sampling_rate = jack_get_sample_rate(client);

  dad->overruns = 0;

  return dad;
}


static void dad_info_free(dad_info dad)
{
  jack_client_close(dad->client);
  jack_ringbuffer_free(dad->samples);
  free(dad->ports);
  free(dad->in);
  free(dad);
}


static void report_error(const char *str)
{
  std::cout << str;
}


static int jack_receive(jack_nframes_t nframes, void *arg)
{
  dad_info dad = (dad_info) arg;
  int c,i;

  if(nframes == 0 || dad->ports[0] == NULL) return 0;
  
  size_t qty = (dad->chan_no)*(dad->sample_size)*nframes;

  if(jack_ringbuffer_write_space(dad->samples) < qty){
    dad->overruns++;
    return 0;
  } 

  // temporarily cache the pointers to these input buffers
  for(c = 0; c < dad->chan_no; c++)
    dad->in[c] =  (jack_default_audio_sample_t*)
      jack_port_get_buffer(dad->ports[c], nframes);

  // interleave the frames
  for(i = 0; i < nframes; i++)
    for(c = 0; c < dad->chan_no; c++){
      jack_ringbuffer_write(dad->samples,(char*)((dad->in[c])+i),
			    dad->sample_size);
    }
  
  return 0;
}

static void jack_server_shutdown(void *arg)
{
  dad_info dad = (dad_info)arg;
}

/** Number of channels currently limited to 999  **/
static dad_info jack_init(int channo, int framew, int frameno)
{
  jack_client_t *client;
  dad_info dad;

  if(channo > 999 || channo < 1){
    report_error("Bad Channel Number");
    return NULL;
  }

  if(framew < 1){
    report_error("Bad Frame Width");
    return NULL;
  }

  if(frameno < 1){
    report_error("Bad FIFO size");
    return NULL;
  }
  
 // CREATE CLIENT
  if((client = jack_client_new("Octave")) == 0){
    report_error("jack server not running?");
    return NULL;
  }

  dad = dad_info_new(client,channo,framew,frameno);

  // FINISH JACK SETUP
  jack_set_process_callback(client, jack_receive, dad);
  
  jack_on_shutdown(client, jack_server_shutdown, &dad);

  char portnm[] = "input   ";
  int i;
  for(i = 0; i < dad->chan_no; i++){
    snprintf(portnm+5,3,"%d",i);
    dad->ports[i] = jack_port_register(client, portnm,
				       JACK_DEFAULT_AUDIO_TYPE,
				       JackPortIsInput, 0);
  }

  if(jack_activate(client)){
    report_error("cannot activate client");
    return NULL;
  }

  return dad;
}


//octave_value_list
DEFUN_DLD(jack,args,nlhs,"Jack Audio client interface")
{
  static dad_info dad = NULL;

  size_t nrhs = args.length();

  octave_value_list lhs;

  const double err = -1.0;


  if(nrhs == 1){
    
    if(dad == NULL){
      report_error("Jack client not running!");
      return octave_value(err);
    }

    if(nlhs > 2){
      report_error("Jack returns at most two value");
      return octave_value(err);
    }

    if(args(0).is_string()){
      if(std::string("close") == args(0).string_value()){
	dad_info_free(dad);
	dad = NULL;
	return octave_value(0);
      } else{
	report_error("Jack only accepts the string 'close'");
	return octave_value(err);
      }
    }

    int rqst = args(0).int_value();

    if(rqst < 0) rqst = 0;
    
    size_t frames = dad_bytes_to_frames(dad,jack_ringbuffer_read_space(dad->samples));
    size_t drop = rqst;

    if(drop > frames) drop = frames;
    if(drop > 0) jack_ringbuffer_read_advance(dad->samples,
					      (dad->chan_no)*
					      (dad->sample_size)*
					      (dad->frame_width)*drop);
    
    lhs.append(octave_value((double)(frames - drop)));
    if(nlhs == 2) lhs.append(octave_value(dad->overruns));
    return lhs;
  } else if(nrhs == 3){
    if(nlhs > 1){
      report_error("Jack initialization returns a single value");
      return octave_value(err);
    }

    if(dad != NULL) dad_info_free(dad);

    dad = jack_init(args(0).int_value(),
		    args(1).int_value(),
		    args(2).int_value());

    if(dad == NULL)
      report_error("Jack Initialization failed!");
    else
      return octave_value(dad->fifo_size);
    
    return octave_value(err);
  } else if(nrhs != 0){
    report_error("Jack takes {0,1,3} RHS arguments");
    return octave_value(err);
  }

  if(dad == NULL){
    report_error("Jack client not connected");
    return octave_value(err);
  }

  size_t rqstsz = (dad->frame_width)*(dad->chan_no)*(dad->sample_size);
  
  if(jack_ringbuffer_read_space(dad->samples) < rqstsz){
    report_error("Jack doesn't have enough samples");
    return octave_value(err);
  }

  Matrix A = Matrix(dim_vector(dad->frame_width,dad->chan_no));
  
  jack_default_audio_sample_t *samples = (jack_default_audio_sample_t *)
    malloc(rqstsz);
  
  jack_ringbuffer_read(dad->samples,(char*)samples,rqstsz);

  int i,c;
  for(i = 0; i < dad->frame_width; i++)
    for(c = 0; c < dad->chan_no; c++)
      A(i,c) = samples[i*(dad->chan_no)+c];

  free(samples);
  return octave_value(A);
}


