#include <ftdi.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// FTDI Vendor ID
#define VID 0x0403
// FTDI Product ID
#define PID 0x6001

#define DMX_CHANNELS 6
//#define DMX_CHANNELS 512
//#define DMX_FRAMETIME 24000000
#define DMX_FRAMETIME 0
#define DMX_BREAK 110000
#define DMX_MAB 16000

#define MAX_STR 64

inline void ts_diffadd(struct timespec *res, struct timespec *a, struct timespec *b, struct timespec *c) {
        time_t rem;

        res->tv_nsec=a->tv_nsec-b->tv_nsec+c->tv_nsec;
        rem=res->tv_nsec/1000000000;
        res->tv_nsec=res->tv_nsec%1000000000;
        res->tv_sec=a->tv_sec-b->tv_sec+c->tv_sec+rem;

        if (res->tv_nsec<0 && res->tv_sec>0) { res->tv_sec--; res->tv_nsec+=1000000000; }
        if (res->tv_nsec>0 && res->tv_sec<0) { res->tv_nsec-=1000000000; res->tv_sec++; }
}

void e(int res, const char *funcname, struct ftdi_context *ftdi, int exit_val) {
	char *reason;

	if ( res < 0 ) {
		reason=ftdi_get_error_string(ftdi);
		fprintf(stderr, "Error in %s : %s\n", funcname, reason);
		exit(exit_val);
	}

}

/*
struct ftdi_device_list
{
    // pointer to next entry
    struct ftdi_device_list *next;
    // pointer to libusb's usb_device
    struct usb_device *dev;
}
*/

int dmx_universe_write(struct ftdi_context *ftdi, char *universe, int uni_len) {
	static struct timespec ts_frame_time={0,DMX_FRAMETIME},ts_dmx_break={0,DMX_BREAK}, ts_dmx_mab={0,DMX_MAB}, ts_trame_begin={0,0};
	struct timespec ts_to_sleep, ts_now;

	// http://www.opendmx.net/index.php/DMX512-A#Timings
	// http://www.erwinrol.com/?s=dmx
	// symbo_perdiod is 4us (250kHz)
	// Full packet minimal timing : DMX_BREAK+DMX_MAB+(1+8+2)*513*symbol_period=22698us

	ftdi_async_complete(ftdi,0);
	// Wait the end of the previous timeslot
/*
	clock_gettime(CLOCK_MONOTONIC, &ts_now);
	ts_diffadd(&ts_to_sleep,&ts_frame_time,&ts_now,&ts_trame_begin);

	//fprintf(stderr, "DEBUG : ts_to_sleep==%li.%09li\n", ts_to_sleep.tv_sec, ts_to_sleep.tv_nsec);
//	ts_to_sleep.tv_sec=0;
//	ts_to_sleep.tv_nsec=23000000;
	nanosleep(&ts_to_sleep,NULL);

	clock_gettime(CLOCK_MONOTONIC, &ts_trame_begin);
*/
	e(ftdi_set_line_property2(ftdi, BITS_8, STOP_BIT_2, NONE, BREAK_ON),"ftdi_set_line_property2",ftdi,30);
	nanosleep(&ts_dmx_break,NULL);
	e(ftdi_set_line_property2(ftdi, BITS_8, STOP_BIT_2, NONE, BREAK_OFF),"ftdi_set_line_property2",ftdi,31);
	nanosleep(&ts_dmx_mab,NULL);


	//return ftdi_write_data(ftdi, universe, uni_len);
	return ftdi_write_data_async(ftdi, universe, uni_len);
}

int main() {
	int i,res;

	char *manufacturer, *description, *serial;
	char universe[DMX_CHANNELS+1];

	struct ftdi_context ftdi;
	struct ftdi_device_list *devlist=NULL;
	struct usb_device *dev=NULL;

	struct timespec ts_to_sleep;

	(void) memset(universe, 0, sizeof(universe));
	(void) memset(&ftdi, 0, sizeof(ftdi));

	manufacturer=malloc(MAX_STR);
	description=malloc(MAX_STR);
	serial=malloc(MAX_STR);

	e(ftdi_init(&ftdi),"ftdi_init",&ftdi,10);
	e(ftdi_usb_find_all(&ftdi, &devlist, VID, PID),"ftdi_usb_find_all",&ftdi,11);

	//Always take the first device
	if (devlist==NULL || devlist->dev==NULL) {
		fprintf(stderr, "No usb device detected (looking for USB ID 0x%04X:0x%04X)\n", VID, PID);
		exit(2);
	}
	dev=devlist->dev;

//	fprintf(stderr, "DEBUG : next==%p\n", devlist->next);
	
	e(ftdi_usb_get_strings(&ftdi, dev, manufacturer, MAX_STR, description, MAX_STR, serial, MAX_STR),"ftdi_usb_get_strings",&ftdi,12);
	fprintf(stderr, "Connecting to USB device ('%s','%s','%s')\n", manufacturer, description, serial);
	e(ftdi_usb_open_dev(&ftdi,dev),"ftdi_usb_open_dev",&ftdi,13);
	fprintf(stderr, "Connected\n");
	
	//Init the device
	e(ftdi_usb_reset(&ftdi),"ftdi_usb_reset",&ftdi,15);
	e(ftdi_set_baudrate(&ftdi, 250000),"ftdi_set_baudrate",&ftdi,16);
	e(ftdi_set_line_property(&ftdi, BITS_8, STOP_BIT_2, NONE),"ftdi_set_line_property",&ftdi,17);
	e(ftdi_setflowctrl(&ftdi, SIO_DISABLE_FLOW_CTRL),"ftdi_setflowctrl",&ftdi,18);
	e(ftdi_usb_purge_buffers(&ftdi),"ftdi_usb_purge_buffers",&ftdi,19);
	e(ftdi_setrts(&ftdi, 0),"ftdi_setrts",&ftdi,20);

	// 1ms pause for initialization completion
	ts_to_sleep.tv_sec=0;
	ts_to_sleep.tv_nsec=1000000;
	nanosleep(&ts_to_sleep,NULL);

//	fprintf(stderr, "DEBUG : res==%i\n", res);
//	fprintf(stderr, "DEBUG : universe==%s\n", universe);
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=i-1;
		universe[3]=0;
		universe[4]=0;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=256-i;
		universe[3]=0;
		universe[4]=0;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=0;
		universe[3]=i-1;
		universe[4]=0;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=0;
		universe[3]=256-i;
		universe[4]=0;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=0;
		universe[3]=0;
		universe[4]=i-1;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}
	//for(i=1;i<=256;i=i<<1) {
	for(i=1;i<=256;i++) {
		universe[2]=0;
		universe[3]=0;
		universe[4]=256-i;
		res=dmx_universe_write(&ftdi, universe, DMX_CHANNELS);
	}

	e(ftdi_usb_close(&ftdi),"ftdi_usb_close",&ftdi,50);
	ftdi_list_free(&devlist);
	ftdi_deinit(&ftdi);


	free(manufacturer);
	free(description);
	free(serial);

	exit(0);
}