/**
   MatlabJack 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 <math.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>
#include "mex.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 frame_no;
  size_t sampling_rate;
  size_t overruns;
} dad_info_t;

typedef dad_info_t *dad_info;

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 = malloc(sizeof(jack_port_t*)*chan_no);
  dad->in = 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->frame_no = frame_no;

  dad->samples = jack_ringbuffer_create(chan_no*frame_width*frame_no*
					(dad->sample_size));

  dad->sampling_rate = jack_get_sample_rate(client);

  dad->overruns = 0;

  return dad;
}


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);
}


void report_error(const char *str)
{
  mexErrMsgTxt(str);
}


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,(void*)((dad->in[c])+i),
			    dad->sample_size);
    }
  
  return 0;
}

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

/** Number of channels currently limited to 999  **/
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("Matlab")) == 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)){
    fprintf(stderr, "cannot activate client");
    return NULL;
  }

  return dad;
}

void mexFunction( int nlhs, mxArray *plhs[], 
		  int nrhs, const mxArray*prhs[] )
     
{
  static dad_info dad = NULL;
  mwSize m=0,n=0;
  int rqst;
  double *dst;

  if(nrhs == 1){
    if(nlhs > 1){
      mexErrMsgTxt("Jack close client returns a single value");
      return;
    }

    if(dad == NULL){
      mexErrMsgTxt("Jack client not running!");
      return;
    }
    
    plhs[0] = mxCreateDoubleScalar(22);
    dad_info_free(dad);
    dad = NULL;
    return;
  } else if(nrhs == 3){
    if(nlhs > 1){
      mexErrMsgTxt("Jack initialization returns a single value");
      return;
    }
    int i;
    int args[3];
    for(i = 0; i < 3; i++){
      m = mxGetM(prhs[i]);
      n = mxGetN(prhs[i]);
      if (!mxIsDouble(prhs[i]) || mxIsComplex(prhs[i]) || 
	  m != 1 || n != 1){
	mexErrMsgTxt("Jack initializers must be scalars");
	return;
      }
      args[i] = *mxGetPr(prhs[i]);
    }

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

    dad = jack_init(args[0],args[1],args[2]);

    if(dad == NULL)
      mxErrMsgTxt("Jack Initialization failed!");
    else
      plhs[0] = mxCreateDoubleScalar(17);
    
    return;
  } else if(nrhs != 0){
    mxErrMsgTxt("Jack takes {0,1,3} RHS arguments");
    return;
  }

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

  
  if(plhs[0] != NULL){
    m = mxGetM(plhs[0]);
    n = mxGetN(plhs[0]);
  }

  if(plhs[0] == NULL || m != dad->frame_width || n != dad->chan_no){
    fprintf(stderr,"created! %d,%d",m,n);
    /** Create a matrix of samples **/
    plhs[0] = mxCreateDoubleMatrix(dad->frame_width,dad->chan_no,mxREAL);
  }
  
  /** Grab the memory pointer **/
  dst = mxGetPr(plhs[0]);

  jack_default_audio_sample_t *samples = (jack_default_audio_sample_t *)
    malloc(rqstsz);
  
  /** And write! Are there word alignment problems?**/
  jack_ringbuffer_read(dad->samples,(void*)samples,rqstsz);

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

  free(samples);
  return;
}


