/* xsession code is partly taken from robert ham's jack rack */

#ifdef HAVE_ALSA

#include <unistd.h>
#include <signal.h>
#include <poll.h>
#include <pthread.h>
#include <string.h>
#include <math.h>

#include "xsession.h"
#include "looperdata.h"

static void * xsession_run (void * data);

xsession_info_t* xsession_new (speciallist_t* looperdatalist, snd_seq_t *seq) {
  	xsession_info_t* xsinfo;
  	
  	xsinfo 			= malloc (sizeof (xsession_info_t));
  	xsinfo->seq      	= seq;
  	xsinfo->quit    		= 0;
	xsinfo->looperdatalist	= looperdatalist;

  	pthread_create (&xsinfo->xsessionthread, NULL, xsession_run, xsinfo);
  	
  	return xsinfo;  
}

void xsession_info_destroy (xsession_info_t* xsinfo){
  	xsinfo->quit = 1;
  	pthread_join (xsinfo->xsessionthread, NULL);
	free(xsinfo);
}

static int open_seq(const char* name, snd_seq_t *seq_handle) {
  	int portid, problem;
	snd_seq_client_info_t *cinfo;
        snd_seq_port_info_t *pinfo;
	snd_seq_addr_t addr;
        snd_seq_client_info_alloca(&cinfo);
        snd_seq_port_info_alloca(&pinfo);

  	if ((portid = snd_seq_create_simple_port(seq_handle, name,
            	SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
            	SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
    		fprintf(stderr, "Error creating sequencer port.\n");
    		return -1; // exit(1);
  	}

	/* try to auto-connect: */
	problem =  snd_seq_parse_address (seq_handle,&addr, "USB X-Session:MIDI 1");
	if (!problem){
		snd_seq_port_subscribe_t *subs;
                snd_seq_port_subscribe_alloca(&subs);
		snd_seq_port_subscribe_set_sender(subs, &addr);
		problem =  snd_seq_parse_address (seq_handle,&addr, "kluppe:1");
		snd_seq_port_subscribe_set_dest(subs, &addr);
                snd_seq_subscribe_port(seq_handle, subs);
		if (!problem) printf ("found x-session alsa device!!!\n");
	}
#if 0
	snd_seq_client_info_set_client(cinfo, -1);
	while (snd_seq_query_next_client(seq_handle, cinfo) >= 0) {
		int client = snd_seq_client_info_get_client(cinfo);
		snd_seq_port_info_set_client(pinfo, client);
		snd_seq_port_info_set_port(pinfo, -1);
		while (snd_seq_query_next_port(seq_handle, pinfo) >= 0) {
/*			printf("%3d:%-3d  %-32.32s %s\n",
			       snd_seq_port_info_get_client(pinfo),
			       snd_seq_port_info_get_port(pinfo),
			       snd_seq_client_info_get_name(cinfo),
			       snd_seq_port_info_get_name(pinfo));*/
			if (!strcmp(snd_seq_port_info_get_name(pinfo),"USB X-Session MIDI 1")){
				snd_seq_port_subscribe_t *subs;
				snd_seq_port_subscribe_alloca(&subs);
				addr.client = snd_seq_port_info_get_client(pinfo);
				addr.port = snd_seq_port_info_get_port(pinfo);
				snd_seq_port_subscribe_set_sender(subs, &addr);
				addr.client = snd_seq_port_info_get_client(&portid);
                                addr.port = snd_seq_port_info_get_port(&portid);
				snd_seq_port_subscribe_set_dest(subs, &addr);
				snd_seq_subscribe_port(seq_handle, subs);
				printf ("found x-session alsa device!!!\n");
			}
		}
	}
#endif


	return 1;
}

static double xsession2pitch (int note){
/*
	double pitch = pow(2.,(double)(note - 57)/12.);
*/
	double pitch = pow(2.,(double)(note - 48.)/12.);

	return pitch;
}

void setvol_helper(xsession_info_t *xsinfo, int id, float vol){
 	looper_data_t *ld;
	ld = speciallist_get_first_nolock (xsinfo->looperdatalist);
        while(ld){
    		if (ld->id == id){
			looperdata_lock(ld);
			looperdata_set_vol(ld, vol);
			looperdata_unlock(ld);
		}
		ld = speciallist_get_next_nolock (
			xsinfo->looperdatalist, ld);
	}
}

void setngrains_helper(xsession_info_t *xsinfo, int id, int ngrains){
        looper_data_t *ld;
        ld = speciallist_get_first_nolock (xsinfo->looperdatalist);
        while(ld){
                if (ld->id == id){
			looperdata_lock(ld);
			looperdata_set_ngrains(ld, ngrains);
			looperdata_unlock(ld);
		}
                ld = speciallist_get_next_nolock (
                        xsinfo->looperdatalist, ld);
        }
}

void graindensity_helper(xsession_info_t *xsinfo, int id, int density){
	looper_data_t *ld;
        ld = speciallist_get_first_nolock (xsinfo->looperdatalist);
        while(ld){
                if (ld->id == id){
			looperdata_lock(ld);
			looperdata_set_graindensity(ld, density);
			looperdata_unlock(ld);
		}
                ld = speciallist_get_next_nolock (
                        xsinfo->looperdatalist, ld);
        }
}

void pan_helper(xsession_info_t *xsinfo, int id, float pan){
        looper_data_t *ld;
        ld = speciallist_get_first_nolock (xsinfo->looperdatalist);
        while(ld){
                if (ld->id == id){
                        looperdata_lock(ld);
                        looperdata_set_pan(ld, pan);
                        looperdata_unlock(ld);
                }
                ld = speciallist_get_next_nolock (
                        xsinfo->looperdatalist, ld);
        }
}



void xsession_action(xsession_info_t *xsinfo) {
  	snd_seq_event_t *ev;
	double note = 0.0;
	double vol = 0.0;

  	do {
    		snd_seq_event_input(xsinfo->seq, &ev);
/*		printf ("event: type %d\n",ev->type);*/
    		switch (ev->type) {
      			case SND_SEQ_EVENT_CONTROLLER: 
/*
        			fprintf(stderr, "Control event on Channel %2d: %d, %5d       \n",
                			ev->data.control.channel, ev->data.control.param, ev->data.control.value);
*/
				switch (ev->data.control.param){
					case XSESSION_FADER_1: 
						setvol_helper(xsinfo, 1, (float)ev->data.control.value / 127.);
					break;
					case XSESSION_FADER_2:
						setvol_helper(xsinfo, 2, (float)ev->data.control.value / 127.);
                                               break;
					case XSESSION_FADER_3:
						setvol_helper(xsinfo, 3, (float)ev->data.control.value / 127.);
                                               break;
					case XSESSION_FADER_4:
						setvol_helper(xsinfo, 4, (float)ev->data.control.value / 127.);
                                               break;
					case XSESSION_POTI_1_1:
						setngrains_helper(xsinfo, 1, 
							(int)((float)ev->data.control.value * 15. / 127.));
					break;
					case XSESSION_POTI_2_1:
                                                       setngrains_helper(xsinfo, 2,
                                                              	(int)((float)ev->data.control.value * 15. / 127.));
                                               break;
					case XSESSION_POTI_3_1:
                                                       setngrains_helper( xsinfo, 3,
							(int)((float)ev->data.control.value * 15. / 127.));
                                               break;
					case XSESSION_POTI_4_1:
                                                       setngrains_helper(xsinfo, 4,
							(int)((float)ev->data.control.value * 15. / 127.));
                                               break;
					case XSESSION_POTI_1_2:
						graindensity_helper(xsinfo, 1, 
							(int)((float)ev->data.control.value * 100. / 127.));
					break;
					case XSESSION_POTI_2_2:
                                                       graindensity_helper(xsinfo, 2,
                                                               (int)((float)ev->data.control.value * 100. / 127.));
					break;
					case XSESSION_POTI_3_2:
                                                       graindensity_helper(xsinfo, 3,
                                                               (int)((float)ev->data.control.value * 100. / 127.));
					break;
					case XSESSION_POTI_4_2:
                                                       graindensity_helper(xsinfo, 4,
                                                               (int)((float)ev->data.control.value * 100. / 127.));
					break;
					case XSESSION_POTI_1_3:
                                                       pan_helper(xsinfo, 1, (float)ev->data.control.value / 64. - 1.);
                                               break;
                                               case XSESSION_POTI_2_3:
                                                       pan_helper(xsinfo, 2, (float)ev->data.control.value / 64. - 1.);
                                               break;
                                               case XSESSION_POTI_3_3:
                                                       pan_helper(xsinfo, 3, (float)ev->data.control.value / 64. - 1.);
                                               break;
                                               case XSESSION_POTI_4_3:
                                                       pan_helper(xsinfo, 4, (float)ev->data.control.value / 64. - 1.);
                                               break;	
				}

        		break;

      			case SND_SEQ_EVENT_PITCHBEND:
        			fprintf(stderr, "Pitchbender event on Channel %2d: %5d   \n", 
                			ev->data.control.channel, ev->data.control.value);
				break;
      			case SND_SEQ_EVENT_NOTEON:
				note = xsession2pitch(ev->data.note.note);
                                vol = (double)ev->data.note.velocity / 127.;

        			fprintf(stderr, "Note On event on Channel %2d: %d (%lf) (v:%d/%lf) \n",
					ev->data.control.channel, ev->data.note.note,note, ev->data.note.velocity,vol);
				break;	
			case SND_SEQ_EVENT_NOTEOFF:
				note = xsession2pitch(ev->data.note.note);
				fprintf(stderr, "Note Off event on Channel %2d: %d (%lf) (0) \n",
                                        ev->data.control.channel, ev->data.note.note,note);
				break;
    		}
    		snd_seq_free_event(ev);
  	} while (snd_seq_event_input_pending(xsinfo->seq, 0) > 0);
}

static void xsession_process (xsession_info_t *xsinfo){
	if (poll(xsinfo->pfds, xsinfo->seq_nfds, 40) > 0) {
      		xsession_action(xsinfo);
    	}    
}


static void* xsession_run (void * data) {
  	xsession_info_t* xsinfo = (xsession_info_t*) data;
  
  	/* this is dealt with by the jack thread */
  	signal (SIGHUP, SIG_IGN);

  	open_seq("xsession_input", xsinfo->seq);
/*	while (!xsinfo->seq && (count < 1000)){ usleep(10); count++;}*/
	if (xsinfo->seq < 0) return NULL;
	printf ("created xsession thread       \n");

	xsinfo->seq_nfds = snd_seq_poll_descriptors_count(xsinfo->seq, POLLIN);
	xsinfo->pfds = (struct pollfd*)alloca(xsinfo->seq_nfds * sizeof(struct pollfd));
	snd_seq_poll_descriptors(xsinfo->seq, xsinfo->pfds, xsinfo->seq_nfds, POLLIN);
	/* http://howtos.linux.com/howtos/MIDI-HOWTO-9.shtml */
  
  	while (!xsinfo->quit){
      		xsession_process (xsinfo);
  	}
  
  	free (xsinfo->pfds);
  	return NULL;
}

#endif /* HAVE_ALSA */
