/*
 * alphatrack.c 
 * alphatrack callback interface
 * based on transport.c by arthur@artcmusic.com
 */

#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "alphatrack.h"

#define POLLSIZE 12
#define POLL_INTERVAL 40 /* ms */

void alphatrack_lock(alphatrack_t* z){
        pthread_mutex_lock (&z->alphatrackmutex);
}

void alphatrack_unlock(alphatrack_t* z){
        pthread_mutex_unlock (&z->alphatrackmutex);
}

static int alphatrack_write_core(alphatrack_t *z, uint8_t *cmd){
        int val;

        val = usb_interrupt_write(z->udev, ALPHATRACK_WRITE_ENDPOINT, (char*)cmd, 8, z->polltimeout);
        if (val < 0) return val;
        if (val != 8) return -1;
        return 0;
}

static int alphatrack_set_nativemode(alphatrack_t *z){
        uint8_t nativemodemessage[8];

        nativemodemessage[0] = 0xf0;
        nativemodemessage[1] = 0x00;
        nativemodemessage[2] = 0x01;
        nativemodemessage[3] = 0x40;
        nativemodemessage[4] = 0x20;/* tranzport = 0x10 , manual says f0 00 01 40 20 01 00 f7 */
        nativemodemessage[5] = 0x01;
        nativemodemessage[6] = 0x00;
        nativemodemessage[7] = 0xf7;
        return alphatrack_write_core(z, nativemodemessage);
}

int alphatrack_poll(alphatrack_t *z)
{
	uint8_t buf[POLLSIZE];
	uint64_t newbuttons = 0x0LL;
	int val;

	memset(buf, 0, POLLSIZE);
	val = usb_interrupt_read(z->udev, ALPHATRACK_READ_ENDPOINT, (char*)(&buf), POLLSIZE, z->polltimeout);
	
	if (val < 0){return val;}
	if (val != POLLSIZE) {if (val != -110) printf ("poll failed:%d\n",val); return -1;}

	/* buf[0] empty ??? */
	/* buf[1] status ? */
	/* buf[2-4] buttons */
	/* buf[5] touch + encioder_L + foot*/
	/* buf[6-7] fader pos*/
	/* buf[8-10 encoders 1 - 3 */
	/* touch-strip pos */
	
	z->status = buf[1]; /* 00 for alphatrack, interesting for tranzport */
	
	newbuttons |= buf[5] << 24;
	newbuttons |= buf[4] << 16;
	newbuttons |= buf[3] << 8;
	newbuttons |= buf[2];
	newbuttons &= 0xffffffffLL; /* hack:make sure that only bit 0-32 are in use */
	
/*	if (newbuttons) printf ("newbuttons: 0x%010Lx\n",newbuttons);*/

	if (buf[8] < 128) 	z->encoderdelta_l = (int)buf[8];
	else if (buf[8] > 127) 	z->encoderdelta_l = (256 - (int)buf[8]) * -1;
	if (buf[9] < 128)   	z->encoderdelta_m = (int)buf[9];
        else if (buf[9] > 127)  z->encoderdelta_m = (256 - (int)buf[9]) * -1;
	if (buf[10] < 128)    	z->encoderdelta_r = (int)buf[10];
        else if (buf[10] > 127) z->encoderdelta_r = (256 - (int)buf[10]) * -1;

	if (z->encoderdelta_l) newbuttons |= ALPHATRACK_DELTA_ENCODER_L;
	if (z->encoderdelta_m) newbuttons |= ALPHATRACK_DELTA_ENCODER_M;
	if (z->encoderdelta_r) newbuttons |= ALPHATRACK_DELTA_ENCODER_R;

	z->buttondiff = z->buttons ^ newbuttons; // XOR
	z->buttons = newbuttons;

	if (z->touchpos != buf[11]){
		z->touchpos = buf[11];
		z->buttondiff |= ALPHATRACK_POS_TOUCHSTRIP;
	}

	val = (buf[6] << 2) | (buf[7] >> 2);
	/* this only happens when fader was moved manually: */
	if ((z->faderpos != val) && (buf[5] & 0x40)){
		z->faderpos_dirty = val;
		z->buttondiff |= ALPHATRACK_POS_FADER;
	}
	
	return 0;
}

int alphatrack_set_faderpos(alphatrack_t *z, int faderpos){
	alphatrack_lock(z);
	z->faderpos_dirty = faderpos;
	alphatrack_unlock(z);

	return 0;
}


static int alphatrack_write_queued (alphatrack_t *z){ /* try to send lcd data */
	uint8_t cmd[8];
	int count = -1;
	int realcount = 0;
	int faderpos_dirty;

	while (count < 25){
		count++;
		if (z->led_dirty[z->led_ptr]){
		        cmd[0] = 0x00;
        		cmd[1] = 0x00;
        		cmd[2] = z->led_ptr;
        		if (z->leds[z->led_ptr]) cmd[3] = 0x01;
        		else cmd[3] = 0x00;
        		cmd[4] = 0x00;
        		cmd[5] = 0x00;
        		cmd[6] = 0x00;
        		cmd[7] = 0x00;
			if (alphatrack_write_core(z, cmd)){
				return realcount;
			}
			alphatrack_lock(z);
			z->led_dirty[z->led_ptr] = 0;
			alphatrack_unlock(z);
                	realcount++;
		}
		alphatrack_lock(z);
		z->led_ptr = ++z->led_ptr % MAXLED;
		alphatrack_unlock(z);
	}

	count = -1;
	while (count < 19){
		count++;
		if (z->lcd_dirty[z->lcd_ptr]){
	        	cmd[0] = 0x00;
        		cmd[1] = 0x01;
        		cmd[2] = z->lcd_ptr;
			cmd[3] = z->lcd[(z->lcd_ptr * 4)];
			cmd[4] = z->lcd[(z->lcd_ptr * 4) + 1];
			cmd[5] = z->lcd[(z->lcd_ptr * 4) + 2];
			cmd[6] = z->lcd[(z->lcd_ptr * 4) + 3];
        		cmd[7] = 0x00;
			if (alphatrack_write_core(z, cmd)){
/*				printf ("short write in lcd (%d,%d)\n",count,realcount);*/
				return realcount;
			}
			alphatrack_lock(z);
			z->lcd_dirty[z->lcd_ptr] = 0;
			alphatrack_unlock(z);
			realcount++;
		}
		alphatrack_lock(z);
		z->lcd_ptr = ++z->lcd_ptr % 10;
		alphatrack_unlock(z);
	}

	faderpos_dirty = z->faderpos_dirty;
	if (faderpos_dirty < 0) faderpos_dirty = 0;
        if (faderpos_dirty > 1023) faderpos_dirty = 1023;

	/*update faderposition if not touched: */
	if ((z->faderpos != faderpos_dirty) && 
		(!(z->buttons & ALPHATRACK_CONTACT_FADER)) 
		&& (!(z->buttons & ALPHATRACK_CONTACT_TOUCHSTRIP_2))){
		/*printf ("setting faderpos\n");*/
        	cmd[0] = 0x00;
        	cmd[1] = 0x02;
        	cmd[2] = (faderpos_dirty & 0xff0) >> 2;
        	cmd[3] = (faderpos_dirty & 0xf) << 2;
        	cmd[4] = 0x00;
        	cmd[5] = 0x00;
        	cmd[6] = 0x00;
        	cmd[7] = 0x00;

        	if (alphatrack_write_core(z, cmd)){
/*                	printf ("short write\n");*/
                	return -1;
        	}

        	alphatrack_lock(z);
        	z->faderpos = z->faderpos_dirty = faderpos_dirty;
        	alphatrack_unlock(z);
	}
	return realcount;
}

static void* alphatrack_run (void *ptr){
	alphatrack_t *z = (alphatrack_t*)ptr;
	int val;

	if (!z) return NULL;

	while (!z->quit){
		alphatrack_poll(z);
		if (z->buttondiff || z->encoderdelta_l || z->encoderdelta_m || z->encoderdelta_r ){
			/* create a new event */
			alphatrack_event_t *ev = malloc (sizeof(alphatrack_event_t));

			ev->mask = z->buttondiff;
			ev->states = z->buttons;
			memcpy(ev->leds, z->leds, sizeof(int) * MAXLED);
                     	ev->touchpos = z->touchpos;
                     	ev->faderpos = z->faderpos_dirty;
                     	ev->encoderdelta_l = z->encoderdelta_l;
                     	ev->encoderdelta_m = z->encoderdelta_m;
                     	ev->encoderdelta_r = z->encoderdelta_r;
			
			if (z->callbackfunc) z->callbackfunc(ev, z->callbackptr);

			free(ev);
		}
		alphatrack_write_queued (z);
		usleep(POLL_INTERVAL * 1000);
	}/* end while not quit */

        val = usb_release_interface(z->udev, 0);
        if (val < 0) printf("unable to release alphatrack");

        val = usb_close(z->udev);
        if (val < 0) printf("unable to close alphatrack");

	free(z);

	return NULL;
}

void alphatrack_set_event_callback(alphatrack_t *z, void (*callback)(alphatrack_event_t *ev, void *ptr) ,void *ptr){
	if (!callback) return;

	z->callbackfunc = callback;
	z->callbackptr = ptr;
}

static alphatrack_t *open_alphatrack_core(struct usb_device *dev){
	alphatrack_t *z;
	int val;

	z = malloc(sizeof(alphatrack_t));
	if (!z){
		printf("not enough memory");
		return NULL;
	}
	memset(z, 0, sizeof(alphatrack_t));

	z->dev = dev;
	z->udev = usb_open(z->dev);
	if (!z->udev){
		printf("unable to open alphatrack");
		free(z);
		return NULL;
	}

	usb_reset (z->udev);

	val = usb_claim_interface(z->udev, 0);
	if (val < 0){
		printf("unable to claim alphatrack (%d)\n",val);
		free(z);
		return NULL;
	}

	alphatrack_set_nativemode(z);

	z->lcd = calloc(41,sizeof(char));
	memset(z->lcd_dirty,0,sizeof(int) * 10);
	memset(z->leds,0,sizeof(int) * MAXLED);
	memset(z->led_dirty,0,sizeof(int) * MAXLED);

	z->lcd_ptr = 0;
	z->led_ptr = 0;
	z->status = 0;
	z->buttons = 0;
	z->buttondiff = 0;
	z->encoderdelta_l = 0;
	z->encoderdelta_m = 0;
	z->encoderdelta_r = 0;
	z->faderpos = -1;
	z->faderpos_dirty = -1;
	z->polltimeout = 15;

	z->quit = 0;

	pthread_mutex_init (&z->alphatrackmutex, NULL);
        pthread_create (&z->pollthread, NULL, alphatrack_run, z);
	
	return z;
}

alphatrack_t *alphatrack_new(){
	struct usb_bus *bus;
	struct usb_device *dev;

	usb_init();
	usb_find_busses();
	usb_find_devices();

	for(bus=usb_busses; bus; bus=bus->next) {
		for(dev=bus->devices; dev; dev=dev->next) {
			if (dev->descriptor.idVendor != ALPHATRACK_VENDORID)
				continue;
			if (dev->descriptor.idProduct != ALPHATRACK_PRODUCTID)
				continue;

			printf ("alphatrack device found\n");
			return open_alphatrack_core(dev);
		}
	}

	printf("can't find alphatrack\n");
	return NULL;
}

uint8_t alphatrack_get_status(alphatrack_t *z){
	return z->status;
}

void alphatrack_close(alphatrack_t *z){
	z->quit = 1;
}

/* ***************** LEDs *************************************/

int alphatrack_set_led(alphatrack_t *z, uint8_t led, int status){
	
	if (led > MAXLED) return -1;
	alphatrack_lock(z);
	z->leds[led] = status;
	z->led_dirty[led] = 1;
	alphatrack_unlock(z);
	return 0;
}

int alphatrack_get_led(alphatrack_t *z, uint8_t led){
	if (led > MAXLED) return -1;
	return z->leds[led];
}

void alphatrack_clear_leds(alphatrack_t *z){
        alphatrack_set_led(z, ALPHATRACK_LED_EQ, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_RIGHT, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_F2, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_SEND, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_LEFT, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_F1, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_PAN, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_EMPTY, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_EMPTY2, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_SHIFT, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_MUTE, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_SOLO, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_REC, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_READ, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_WRITE, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_ANY, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_AUTO, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_F4, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_PUNCH, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_FLIP, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_PLUGIN, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_F3, 0);
        alphatrack_set_led(z, ALPHATRACK_LED_LOOP, 0);
}

/*

int alphatrack_pressed(alphatrack_t *z, int button){
	if ((z->buttons & button) && (z->buttondiff & button)) return 1;
	else return 0;
}

int alphatrack_buttonstate (alphatrack_t *z, int button){
	if (z->buttons & button) return 1;
	else return 0;
}
*/

/************************* PUBLIC LCD FUNCTIONS: ****************************/

void alphatrack_lcd_readcell(alphatrack_t *z, uint8_t cell, char *text){
	strncpy(text,(char*)(z->lcd + (cell * 4)),4);
}

int alphatrack_clear_lcd(alphatrack_t *z){

	alphatrack_lock(z);
	memset(z->lcd,0x20,16);
	memset(z->lcd_dirty,1,16);
	alphatrack_unlock(z);
	return 0;
}

int alphatrack_write_lcd (alphatrack_t *z, const char* txt, int x, int y){
	int i = 0;
	int j;
	int length = strlen(txt);

	if (y > 1) y = 1;
	if (y < 0) y = 0;
	if (x < 0) x = 0;
	if ((length + x) > 16) length = 16 - x;

	alphatrack_lock(z);
	for (i = 0; i < length;i++){
		j = x + i + (y * 16);
		if ((j < 40) && (j >= 0)){
			z->lcd[j] = txt[i];
			z->lcd_dirty[(int)(j / 4)] = 1;
/*			printf ("j:%d,txt[i]:%c\n",j,(int)txt[i]);*/
		}
	}
	alphatrack_unlock(z);
	return 0;
}
