// ==============================================================================
//	gac.c
//	
//	pd-Interface to [ guitare à crayon ]
//  Adapted by: Patrick Sébastien Coulombe
//	Website:	http://www.workinprogress.ca/guitare-a-crayon/
//
//	Original Author:	Michael Egger
//	Copyright:	2007 [ a n y m a ]
//	Website:	http://gnusb.sourceforge.net/
//	
//	License:	GNU GPL 2.0 www.gnu.org
//	Version:	2009-04-11
// ==============================================================================
// ==============================================================================

#include "m_pd.h"
#include "../common/gac_cmds.h" // codes used between gac client and host software, eg. between the pd external and the gac firmware
#include <usb.h> // http://libusb-win32.sourceforge.net

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// ==============================================================================
// Constants
// ------------------------------------------------------------------------------
#define USBDEV_SHARED_VENDOR    	0x16c0  /* VOTI */
#define USBDEV_SHARED_PRODUCT   	0x05dc  /* Obdev's free shared PID */
#define OUTLETS 					17
#define DEFAULT_CLOCK_INTERVAL		10
#define USBREPLYBUFFER 				21

// ==============================================================================
// Our External's Memory structure
// ------------------------------------------------------------------------------

typedef struct _gac				// defines our object's internal variables for each instance in a patch
{
	t_object 		p_ob;					// object header - ALL pd external MUST begin with this...
	usb_dev_handle	*dev_handle;			// handle to the gac usb device
	void			*m_clock;				// handle to our clock
	double 			m_interval;				// clock interval for polling gac
	double 			m_interval_bak;			// backup clock interval for polling gac
	int				is_running;				// is our clock ticking?
	int				do_10_bit;				// output analog values with 8bit or 10bit resolution?
	void 			*outlets[OUTLETS];		// handle to the objects outlets
	int 			values[10];				// stored values from last poll /////////////////////////////////
} t_gac;

void *gac_class;					// global pointer to the object class - so pd can reference the object 


// ==============================================================================
// Function Prototypes
// ------------------------------------------------------------------------------
void *gac_new(t_symbol *s);
void gac_assist(t_gac *x, void *b, long m, long a, char *s);
void gac_bang(t_gac *x);				
void gac_close(t_gac *x);
void gac_int(t_gac *x, t_floatarg n);
void gac_precision(t_gac *x, t_symbol *s);
void gac_bootloader(t_gac *x);
void gac_open(t_gac *x);
void gac_poll(t_gac *x, t_floatarg n);
void gac_pad(t_gac *x, t_floatarg n);
void gac_led(t_gac *x, t_floatarg n);
void gac_curve(t_gac *x, t_floatarg n);
void gac_start(t_gac *x);
void gac_stop(t_gac *x);

// functions used to find the USB device
static int  	usbGetStringAscii(usb_dev_handle *dev, int index, int langid, char *buf, int buflen);
void 			find_device(t_gac *x);


//--------------------------------------------------------------------------
// - Message: bootloader
//--------------------------------------------------------------------------
void gac_bootloader(t_gac *x)
{
	int cmd;
	int nBytes;
	unsigned char buffer[8];
	
	cmd = 0;
	
	cmd = gac_CMD_START_BOOTLOADER;
	if (!(x->dev_handle)) find_device(x);
	else {
		nBytes = usb_control_msg(x->dev_handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
							cmd, 0, 0, (char *)buffer, sizeof(buffer), 10);
	}
}

//--------------------------------------------------------------------------
// - Message: bang  -> poll gac
//--------------------------------------------------------------------------
void gac_bang(t_gac *x)	// poll gac
{
	int                 nBytes,i,n;
	int 				replymask,replyshift,replybyte;
	int					temp;
	unsigned char       buffer[USBREPLYBUFFER];
	
	if (!(x->dev_handle)) find_device(x);
	else {
			// ask gac to send us data
			nBytes = usb_control_msg(x->dev_handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
										gac_CMD_POLL, 0, 0, (char *)buffer, sizeof(buffer), 10);

			
			for (i = 0; i < OUTLETS; i++) {
				n = i;
				temp = buffer[n];

				if (x->do_10_bit) {
					if(n == 0) {
						replybyte = buffer[10];
						replyshift = ((0 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 1) {
						replybyte = buffer[10];
						replyshift = ((1 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 2) {
						replybyte = buffer[10];
						replyshift = ((2 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 3) {
						replybyte = buffer[10];
						replyshift = ((3 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					
					if(n == 4) {
						replybyte = buffer[11];
						replyshift = ((0 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 5) {
						replybyte = buffer[11];
						replyshift = ((1 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 6) {
						replybyte = buffer[11];
						replyshift = ((2 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 7) {
						replybyte = buffer[11];
						replyshift = ((3 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					

					if(n == 10) {
						temp = buffer[12];
						replybyte = buffer[19];
						replyshift = ((0 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 11) {
						temp = buffer[13];
						replybyte = buffer[19];
						replyshift = ((1 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 12) {
						temp = buffer[14];
						replybyte = buffer[19];
						replyshift = ((2 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 13) {
						temp = buffer[15];
						replybyte = buffer[19];
						replyshift = ((3 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					
					if(n == 14) {
						temp = buffer[16];
						replybyte = buffer[20];
						replyshift = ((4 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 15) {
						temp = buffer[17];
						replybyte = buffer[20];
						replyshift = ((5 % 4) * 2);
						replymask = (3 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
					if(n == 16) {
						temp = buffer[18];
						replybyte = buffer[20];
						replyshift = ((2 % 4) * 2);
						replymask = (6 << replyshift);
						temp = temp * 4 + ((replybyte & replymask) >> replyshift);
					}
				}

				outlet_float(x->outlets[i], temp);
			}
			
	}
}

//--------------------------------------------------------------------------
// - Message: poll 		-> set polling interval
//--------------------------------------------------------------------------
void gac_poll(t_gac *x, t_floatarg n){
	if (n > 0) { 
		x->m_interval = n;
		x->m_interval_bak = n;
		gac_start(x);
	} else {
		gac_stop(x);
	}
}

//--------------------------------------------------------------------------
// - Message: open 		-> open connection to gac
//--------------------------------------------------------------------------
void gac_open(t_gac *x)
{
	if (x->dev_handle) {
		post("There is already a connection to workinprogress/gac", 0);
	} else find_device(x);
}

//--------------------------------------------------------------------------
// - Message: close 	-> close connection to gac
//--------------------------------------------------------------------------
void gac_close(t_gac *x)
{
	if (x->dev_handle) {
		usb_close(x->dev_handle);
		x->dev_handle = NULL;
		post("Closed connection to workinprogress/gac",0);
	} else
		post("There was no open connection to workinprogress/gac",0);
}

//--------------------------------------------------------------------------
// - Message: precision 		-> 8 or 10 bit
//--------------------------------------------------------------------------
void gac_precision(t_gac *x, t_symbol *s)
{
	if (s == gensym("10bit")) x->do_10_bit = 1;
	else x->do_10_bit = 0;
}

//--------------------------------------------------------------------------
// - Message: int 		-> zero stops / nonzero starts
//--------------------------------------------------------------------------
void gac_int(t_gac *x, t_floatarg n) {
	if (n) {
		if (!x->is_running) gac_start(x);
	} else {
		if (x->is_running) gac_stop(x);
	}
}

//--------------------------------------------------------------------------
// - Message: start 	-> start automatic polling
//--------------------------------------------------------------------------
void gac_start (t_gac *x) { 
	if (!x->is_running) {
		clock_delay(x->m_clock,0.);
		x->is_running  = 1;
	}
} 

//--------------------------------------------------------------------------
// - Message: stop 		-> stop automatic polling
//--------------------------------------------------------------------------
void gac_stop (t_gac *x) { 
	if (x->is_running) {
		x->is_running  = 0;
		clock_unset(x->m_clock); 
		gac_close(x);
	}
}

//--------------------------------------------------------------------------
// - The clock is ticking, tic, tac...
//--------------------------------------------------------------------------
void gac_tick(t_gac *x) { 
	clock_delay(x->m_clock, x->m_interval); 	// schedule another tick
	gac_bang(x); 								// poll the gac
} 



//--------------------------------------------------------------------------
// - Message: pad 	-> number of pad to listen
//--------------------------------------------------------------------------
void gac_pad(t_gac *x, t_floatarg n) {
	int nBytes;
	unsigned char buffer[8];
	
	if (n < 0) n = 0;
	if (n > 5) n = 5;
	
	if (!(x->dev_handle)) find_device(x);
	else {
		nBytes = usb_control_msg(x->dev_handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
							gac_CMD_PAD, n, 0, (char *)buffer, sizeof(buffer), 10);
	}

}


//--------------------------------------------------------------------------
// - Message: led
//--------------------------------------------------------------------------
void gac_led(t_gac *x, t_floatarg n) {
	int nBytes;
	unsigned char buffer[8];
	
	if (!(x->dev_handle)) find_device(x);
	else {
		nBytes = usb_control_msg(x->dev_handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
							gac_CMD_LED, n, 0, (char *)buffer, sizeof(buffer), 10);
	}

}

//--------------------------------------------------------------------------
// - Message: curve  -> choosing a curve for table lookup
//--------------------------------------------------------------------------
void gac_curve(t_gac *x, t_floatarg n) {
	int nBytes;
	unsigned char buffer[8];

	if (n < 0) n = 0;
	if (n > 5) n = 5;

	if (!(x->dev_handle)) find_device(x);
	else {
		nBytes = usb_control_msg(x->dev_handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, 
							gac_CMD_CURVE, n, 0, (char *)buffer, sizeof(buffer), 10);
	}

}



//--------------------------------------------------------------------------
// - Object creation and setup
//--------------------------------------------------------------------------
int gac_setup(void)
{
	gac_class = class_new ( gensym("gac"),(t_newmethod)gac_new, 0, sizeof(t_gac), 	CLASS_DEFAULT,0);
	
	// Add message handlers
	class_addbang(gac_class, (t_method)gac_bang);
	class_addfloat(gac_class, (t_method)gac_int);
	class_addmethod(gac_class, (t_method)gac_open, gensym("open"), 0);		
	class_addmethod(gac_class, (t_method)gac_close, gensym("close"), 0);	
	class_addmethod(gac_class, (t_method)gac_poll, gensym("poll"), A_DEFFLOAT,0);	
	class_addmethod(gac_class, (t_method)gac_precision, gensym("precision"), A_DEFSYM,0);
	class_addmethod(gac_class, (t_method)gac_bootloader, gensym("bootloader"), A_DEFSYM,0);
	class_addmethod(gac_class, (t_method)gac_pad, gensym("pad"), A_DEFFLOAT,0);
	class_addmethod(gac_class, (t_method)gac_led, gensym("led"), A_DEFFLOAT,0);
	class_addmethod(gac_class, (t_method)gac_curve, gensym("curve"), A_DEFFLOAT,0);	
	class_addmethod(gac_class, (t_method)gac_start, gensym("start"), 0);	
	class_addmethod(gac_class, (t_method)gac_stop, gensym("stop"), 0);	
	post("gac version 0.1",0);
	return 1;
}

//--------------------------------------------------------------------------
void *gac_new(t_symbol *s)		// s = optional argument typed into object box (A_SYM) -- defaults to 0 if no args are typed
{
	t_gac *x;									// local variable (pointer to a t_gac data structure)
	x = (t_gac *)pd_new(gac_class);			 // create a new instance of this object
	x->m_clock = clock_new(x,(t_method)gac_tick); 	// make new clock for polling and attach gnsub_tick function to it

	//default to 10bit
	x->do_10_bit = 1;
	
	//if (s == gensym("10bit")) x->do_10_bit = 1;
	//else  x->do_10_bit = 0;
	
	x->m_interval = DEFAULT_CLOCK_INTERVAL;
	x->m_interval_bak = DEFAULT_CLOCK_INTERVAL;

	x->dev_handle = NULL;
	int i;
	
	// create outlets and assign it to our outlet variable in the instance's data structure
	for (i=0; i < OUTLETS; i++) {
		x->outlets[i] = outlet_new(&x->p_ob, &s_float);
	}	

	return x; // return a reference to the object instance 
}

//--------------------------------------------------------------------------
// - Object destruction
//--------------------------------------------------------------------------
void gac_free(t_gac *x)
{
	if (x->dev_handle) usb_close(x->dev_handle);
	freebytes((t_object *)x->m_clock, sizeof(x->m_clock));
}

//--------------------------------------------------------------------------
// - USB Utility Functions
//--------------------------------------------------------------------------
static int  usbGetStringAscii(usb_dev_handle *dev, int index, int langid, char *buf, int buflen)
{
char    buffer[256];
int     rval, i;

    if((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, langid, buffer, sizeof(buffer), 1000)) < 0)
        return rval;
    if(buffer[1] != USB_DT_STRING)
        return 0;
    if((unsigned char)buffer[0] < rval)
        rval = (unsigned char)buffer[0];
    rval /= 2;
    /* lossy conversion to ISO Latin1 */
    for(i=1;i<rval;i++){
        if(i > buflen)  /* destination buffer overflow */
            break;
        buf[i-1] = buffer[2 * i];
        if(buffer[2 * i + 1] != 0)  /* outside of ISO Latin1 range */
            buf[i-1] = '?';
    }
    buf[i-1] = 0;
    return i-1;
}

//--------------------------------------------------------------------------
void find_device(t_gac *x) {
	usb_dev_handle      *handle = NULL;
	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 == USBDEV_SHARED_VENDOR && dev->descriptor.idProduct == USBDEV_SHARED_PRODUCT){
                char    string[256];
                int     len;
                handle = usb_open(dev); /* we need to open the device in order to query strings */
                if(!handle){
                    error ("Warning: cannot open USB device: %s", usb_strerror());
                    continue;
                }
                /* now find out whether the device actually is gac */
                len = usbGetStringAscii(handle, dev->descriptor.iManufacturer, 0x0409, string, sizeof(string));
                if(len < 0){
                    post("gac: warning: cannot query manufacturer for device: %s", usb_strerror());
                    goto skipDevice;
                }
                
                if(strcmp(string, "workinprogress") != 0)
                    goto skipDevice;
                len = usbGetStringAscii(handle, dev->descriptor.iProduct, 0x0409, string, sizeof(string));
                if(len < 0){
                    post("gac: warning: cannot query product for device: %s", usb_strerror());
                    goto skipDevice;
                }
                if(strcmp(string, "Gac") == 0)
                    break;
				skipDevice:
                usb_close(handle);
                handle = NULL;
            }
        }
        if(handle)
            break;
    }
	
    if(!handle){
        post("Could not find USB device workinprogress/gac");
		x->dev_handle = NULL;
		if (x->m_interval < 10000) x->m_interval *=2; // throttle polling down to max 20s if we can't find gac
	} else {
		x->dev_handle = handle;
		 post("Found USB device workinprogress/gac");
		 x->m_interval = x->m_interval_bak;			// restore original polling interval
		 if (x->is_running) gac_tick(x);
		 else gac_bang(x);
	}
}
