/****************************************************************************
(c) 2006 Melanie Thielker

This file is part of libdjconsole.

libdjconsole is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

libdjconsole is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along with
libdjconsole; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 
****************************************************************************/

#include "djconsole.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb.h>

DJConsole::DJConsole(bool load_data)
{
	Leds.setSize(3);
	pthread_mutex_init(&EventLock, 0);
	pthread_mutex_init(&StartLock, 0);
	pthread_cond_init(&StartSignal, 0);

	Worker1Started=false;
	Worker2Started=false;
	Worker1Running=false;
	Worker2Running=false;

	Callback=0;
	UserData=0;
	Vendor=0;
	Product=0;

	Opened=false;
	dev=0;

	struct usb_bus *busses;
	struct usb_bus *bus;
    int c, i;
	int n=1;
	
	usb_init();
	usb_find_busses();
	usb_find_devices();

	busses = usb_get_busses();

	for (bus = busses; bus; bus = bus->next) {

		for (dev = bus->devices; dev; dev = dev->next) {

			/* Check for Mfg code "Hercules" */
			if(dev->descriptor.idVendor != 0x6f8)
				continue;

			/* If it's a single device, it cannot be a DJ console */
			if (dev->descriptor.bDeviceClass != 0) {
				continue;
			}

			/* Loop through all of the configurations */
			for (c = 0; c < dev->descriptor.bNumConfigurations; c++) {
				/* Loop through all of the interfaces */
				for (i = 0; i < dev->config[c].bNumInterfaces; i++) {
					if (dev->config[c].interface[i].altsetting[0].bInterfaceClass == 3) {
						if(!n)
						{
							MouseInterface=i;
							break;
						}
						ControlInterface=i;
						--n;
					}
				}
				if(i < dev->config[c].bNumInterfaces)
					break;
			}
			if(c < dev->descriptor.bNumConfigurations)
				break;
		}
		if(dev)
			break;
	}
	
	if(!bus)
	{
		dev=0;
		printf("No Hercules DJ Console found\n");
		return;
	}

	printf("Hercules Console found at libusb:%s:%s (0x%04x-0x%04x)\n", bus->dirname, dev->filename, dev->descriptor.idVendor, dev->descriptor.idProduct);

	Vendor=dev->descriptor.idVendor;
	Product=dev->descriptor.idProduct;

	hdev1=usb_open(dev);

	if(hdev1 < 0)
	{
		printf("Error opening DJ Console device, check permissions\n");
		return;
	}

	hdev2=usb_open(dev);

	if(hdev2 < 0)
	{
		printf("Error opening DJ Console device, check permissions\n");
		return;
	}

	char kdrv[32];

#ifdef linux
	if(!usb_get_driver_np(hdev1, dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber, kdrv, 32))
	{
		usb_detach_kernel_driver_np(hdev1, dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber);

	}
#endif

	int ret;
	if((ret=usb_claim_interface(hdev1, dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber)) < 0)
	{
		printf("Error claiming event interface, unload conflicting HID/joystick kernel driver\n", ret);
		usb_close(hdev1);
		usb_close(hdev2);
		return;
	}

#ifdef linux
	if(!usb_get_driver_np(hdev2, dev->config[c].interface[MouseInterface].altsetting[0].bInterfaceNumber, kdrv, 32))
	{
		usb_detach_kernel_driver_np(hdev2, dev->config[c].interface[MouseInterface].altsetting[0].bInterfaceNumber);
	}
#endif

	if((ret=usb_claim_interface(hdev2, dev->config[c].interface[MouseInterface].altsetting[0].bInterfaceNumber)) < 0)
	{
		printf("Error claiming mouse interface, unload conflicting mouse kernel driver\n", ret);
		usb_release_interface(hdev1, dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber);
		usb_close(hdev1);
		usb_close(hdev2);
		return;
	}

	Opened=true;
	Configuration=c;


	usb_control_msg(hdev1,
		USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
		9,
		513,
		dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber,
		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16, 1000000);

	usleep(400000);
	for(int i=0;i<6;++i)
	{
		usleep(100000);
		usb_control_msg(hdev1,
			USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
			9,
			513,
			dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber,
			"\x01\xff\xff", 3, 1000000);

		usleep(100000);
		usb_control_msg(hdev1,
			USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
			9,
			513,
			dev->config[c].interface[ControlInterface].altsetting[0].bInterfaceNumber,
			"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16, 1000000);
	}

	usleep(500000);
	printf("Successfully attached DJ Console\n");
	startThreads();

	if(load_data)
		loadData();
}

void DJConsole::loadData()
{
	char tfile[1024];

	sprintf(tfile, "%s/%04x-%04x.buttons", DATADIR, vendor(), product());
	ControlDelta.load(tfile);
	sprintf(tfile, "%s/%04x-%04x.mouse", DATADIR, vendor(), product());
	MouseDelta.load(tfile);
	sprintf(tfile, "%s/%04x-%04x.leds", DATADIR, vendor(), product());
	Leds.load(tfile);
}

DJConsole::~DJConsole()
{
	stopThreads();

	if(Opened)
	{
		usb_control_msg(hdev1,
			USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
			9,
			513,
			dev->config[Configuration].interface[ControlInterface].altsetting[0].bInterfaceNumber,
			"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16, 1000000);

		usleep(50000);

		usb_release_interface(hdev1, dev->config[Configuration].interface[ControlInterface].altsetting[0].bInterfaceNumber);
		usb_release_interface(hdev2, dev->config[Configuration].interface[MouseInterface].altsetting[0].bInterfaceNumber);
		usb_close(hdev1);
		usb_close(hdev2);

		Opened=false;
		dev=0;
	}
	pthread_mutex_destroy(&EventLock);
	pthread_mutex_destroy(&StartLock);
	pthread_cond_destroy(&StartSignal);
}


bool DJConsole::detected()
{
	if(dev)
		return true;
	return false;
}

bool DJConsole::ready()
{
	if(Opened)
		return true;
	return false;
}

void *DJConsole::worker1Helper(void *p)
{
	DJConsole *t=(DJConsole *)p;
	
	pthread_cleanup_push(worker1Reset, p);

	t->worker1();

	pthread_cleanup_pop(true);
}

void *DJConsole::worker2Helper(void *p)
{
	DJConsole *t=(DJConsole *)p;
	
	pthread_cleanup_push(worker2Reset, p);

	t->worker2();

	pthread_cleanup_pop(true);
}

void DJConsole::worker1Reset(void *p)
{
	DJConsole *t=(DJConsole *)p;
	t->Worker1Running=false;
}

void DJConsole::worker2Reset(void *p)
{
	DJConsole *t=(DJConsole *)p;
	t->Worker2Running=false;
}


void DJConsole::worker1()
{
	pthread_mutex_lock(&StartLock);
	pthread_cond_signal(&StartSignal);
	pthread_mutex_unlock(&StartLock);

	unsigned char *data=new unsigned char[dev->config[Configuration].interface[ControlInterface].altsetting[0].endpoint[0].wMaxPacketSize];

	while(1)
	{
		pthread_testcancel();

		int bytes=usb_interrupt_read(hdev1, dev->config[Configuration].interface[ControlInterface].altsetting[0].endpoint[0].bEndpointAddress, (char *)data, dev->config[Configuration].interface[ControlInterface].altsetting[0].endpoint[0].wMaxPacketSize, 100);
		if(Leds.dirty())
			setLeds();
		if(bytes < 1)
			continue;
		ControlDelta.set(data, dev->config[Configuration].interface[ControlInterface].altsetting[0].endpoint[0].wMaxPacketSize);
		DeltaEvent ev;
		while((ev=ControlDelta.event()).code())
		{
			pthread_mutex_lock(&EventLock);
			processEvent(ev);
			pthread_mutex_unlock(&EventLock);
		}
	}
}

void DJConsole::worker2()
{
	pthread_mutex_lock(&StartLock);
	pthread_cond_signal(&StartSignal);
	pthread_mutex_unlock(&StartLock);

	unsigned char *data=new unsigned char[dev->config[Configuration].interface[MouseInterface].altsetting[0].endpoint[0].wMaxPacketSize];

	memset(data, 0, dev->config[Configuration].interface[MouseInterface].altsetting[0].endpoint[0].wMaxPacketSize);

	while(1)
	{
		pthread_testcancel();

		int bytes=usb_interrupt_read(hdev2, dev->config[Configuration].interface[MouseInterface].altsetting[0].endpoint[0].bEndpointAddress, (char *)data, dev->config[Configuration].interface[MouseInterface].altsetting[0].endpoint[0].wMaxPacketSize, 10);
		if(bytes < 1)
		{
			data[0]=1;
			if(MouseDelta.rawbits())
				data[1]=*(MouseDelta.rawbits()+1);
			else
				data[1]=0;
			data[2]=0;
			data[3]=0;
		}
		MouseDelta.set(data, dev->config[Configuration].interface[MouseInterface].altsetting[0].endpoint[0].wMaxPacketSize);
		DeltaEvent ev;
		while((ev=MouseDelta.event()).code())
		{
			pthread_mutex_lock(&EventLock);
			processEvent(ev);
			pthread_mutex_unlock(&EventLock);
		}
	}
}


bool DJConsole::startThreads()
{
	pthread_mutex_lock(&StartLock);
	Worker1Running=true;
	if(pthread_create(&Worker1, 0, &worker1Helper, this))
	{
		Worker1Running=false;
		pthread_mutex_unlock(&StartLock);
		return false;
	}
	else
	{
		Worker1Started=true;
		pthread_cond_wait(&StartSignal, &StartLock);
		pthread_mutex_unlock(&StartLock);
	}

	pthread_mutex_lock(&StartLock);
	Worker2Running=true;
	if(pthread_create(&Worker2, 0, &worker2Helper, this))
	{
		Worker2Running=false;
		pthread_mutex_unlock(&StartLock);
		stopThreads();
		return false;
	}
	else
	{
		Worker2Started=true;
		pthread_cond_wait(&StartSignal, &StartLock);
		pthread_mutex_unlock(&StartLock);
	}
	return true;
}

void DJConsole::stopThreads()
{
	if(Worker1Started)
	{
		if(Worker1Running)
			pthread_cancel(Worker1);
		pthread_join(Worker1, 0);
		Worker1Started=false;
	}

	if(Worker2Started)
	{
		if(Worker2Running)
			pthread_cancel(Worker2);
		pthread_join(Worker2, 0);
		Worker2Started=false;
	}
}

void DJConsole::setLeds()
{
	if(!Opened)
		return;
	usb_control_msg(hdev1,
		USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
		9,
		513,
		dev->config[Configuration].interface[ControlInterface].altsetting[0].bInterfaceNumber,
		(char*)Leds.bits(), Leds.size(), 10000);
}

void DJConsole::setCallback(void (*callback)(void *, int, int), void *userdata)
{
	Callback=callback;
	UserData=userdata;
}

void DJConsole::processEvent(DeltaEvent ev)
{
	if(Callback)
		(*Callback)(UserData, ev.code(), ev.value());
	else
		printf("Warning: Event %d:%d ignored\n", ev.code(), ev.value());
}

unsigned short DJConsole::vendor()
{
	return Vendor;
}

unsigned short DJConsole::product()
{
	return Product;
}

