#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#include <sys/param.h>

#include <jack/jack.h>

const char *default_in_port_basename = "jsc_in";
const char *default_out_port_basename = "jsc_out";
const char *default_client_name = "jsc";
const char *default_server_name = "jack_srv";
jack_port_t **input_ports;
jack_port_t **output_ports;
jack_client_t *client;
jack_options_t options = JackNullOption;
jack_nframes_t bufsize;
jack_default_audio_sample_t **tmpbuffer;

short n_channels = 8
;
int jack_active = 0;
int current_stage = 0;


// callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// JACK process()
int process(jack_nframes_t nframes, void *arg){
    int i;
	jack_default_audio_sample_t* tmp;	
	
	for(i=0;i<n_channels;i++){ 
        tmp = jack_port_get_buffer(input_ports[i], nframes);
		memcpy(tmpbuffer[i], tmp, nframes*sizeof(jack_default_audio_sample_t));	
	}
	
	for(i=0;i<n_channels;i++){
        tmp = jack_port_get_buffer(output_ports[i], nframes);
		memcpy(tmp, tmpbuffer[i], nframes*sizeof(jack_default_audio_sample_t));
	}
	
	return 0;
}

void jack_shutdown (void *arg){
	printf("Kicked out by JACK server\n");
}

void jack_error_handler(const char *arg){
	if(jack_active){
		printf("JACK died\n");
		jack_active = 0;
		current_stage = 0;
	}
}

// stage 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// mainloop
int roll(){
	while(jack_active){
		sleep(1);
	}
	
	return 1;
}

// stage 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int process_init(){

	int i;
	tmpbuffer = calloc(n_channels, sizeof(jack_default_audio_sample_t*));
	for(i=0; i<n_channels; i++){
		tmpbuffer[i] = calloc(bufsize, sizeof(jack_default_audio_sample_t));
	}	
	
	jack_on_shutdown(client, jack_shutdown, 0);
	jack_set_process_callback(client, process, 0);
	jack_set_error_function(jack_error_handler);

	if(jack_activate(client)){
        fprintf ( stderr, "cannot activate client" );
		return 1;
    }

	jack_active = 1;
	
	printf("JACK Client activated\n");

	return 0;

}

// stage 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// JACK client init
int jack_init(){
    
	short i;
	char* client_name;
	jack_status_t status;
	
	client = jack_client_open (default_client_name, options, &status, default_server_name);

	if (client == NULL){
		return 1;
    }

    if (status & JackServerStarted){
        fprintf(stderr, "JACK server started\n");
    }

    if (status & JackNameNotUnique){
        client_name = jack_get_client_name ( client );
        fprintf ( stderr, "unique name `%s' assigned\n", client_name );
    }	

    input_ports = (jack_port_t**)calloc(n_channels, sizeof(jack_port_t*));
    output_ports = (jack_port_t**)calloc(n_channels, sizeof(jack_port_t*));

	char port_name[16];

	for(i=0;i<n_channels;i++){

			sprintf(port_name, "%s%d", default_in_port_basename, i+1);
			input_ports[i] = jack_port_register(client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
			if(input_ports[i] == NULL){
            	fprintf ( stderr, "no more JACK ports available\n" );
	            return 1;
			}
	
			sprintf(port_name, "%s%d", default_out_port_basename, i+1);
			output_ports[i] = jack_port_register(client, port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
			if(output_ports[i] == NULL){
            	fprintf ( stderr, "no more JACK ports available\n" );
				return 1;
			}
	}

	bufsize = jack_get_buffer_size(client);
	printf("JACK Client initialized\n");
	printf("JACK Buffer size = %d\n", bufsize);
	printf("Audio Channels: %d\n", n_channels);

	return 0;
}

// stage 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

int cleanall(){
	
	if(client){
		jack_deactivate(client);	
		jack_client_close(client);
		printf("JACK client closed\n");
	}
	
	free(input_ports);	
	free(output_ports);
	free(tmpbuffer);
	
	return 0;
}


// parse CLI options - - - - - - - - - - - - - - - - - - - - - - -
int parse_options(int argc, char** argv){

	int opt;
	int n;
	extern char *optarg;
	const char* optstring = "c:p:vh";

	while((opt = getopt(argc, argv, optstring)) != -1){
	
		switch(opt){
		
			case 'c':
				n_channels = atoi(optarg);
				break;
			case 'h':
				printf("\nUsage: router [OPTIONS]\n\n");
				printf("-c CHANNELS\t number of channels (default=8)\n");
				printf("\n");
				exit(0);
				break;
			case ':':
				break;
			case '?':
				break;
		
		}	
	}
	
}


// - - - - - - -
//     MAIN
// - - - - - - -

int main(int argc, char** argv){
	
	const int n_stages = 4;
	int (*stage[n_stages])();
	stage[0] = cleanall;
	stage[1] = jack_init;
	stage[2] = process_init;
	stage[3] = roll;

	parse_options(argc, argv);

	while(1){

		if((*stage[current_stage])() == 0){
			current_stage = (current_stage + 1) % n_stages;
		}
		else{
			current_stage = 0;
		}
	}	

	return 0;
}
