/*Copyright (C) 2005 Michael Kasianowicz (xtravar@yahoo.com)

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "p2kusb.h"

/* this is a typical unix header, and will need changing for win32 */
#include <fcntl.h>
#include <usb.h>

#include <stdio.h>

static int _usb_is_init = 0;

/* a lot of the USB specific code was inspired and/or borrowed from:
	libp2kmoto http://moto4lin.sf.net written by Dmitry Nezhevenko 

	for example, this below is almost directly copied */
struct usb_device * usb_find_device(uint16_t vendor, uint16_t product) {
	if(!_usb_is_init) {
		usb_init();
		_usb_is_init = 1;
	}
	struct usb_bus *busses;
	struct usb_bus *bus;

	usb_find_busses();
	usb_find_devices();

	busses = usb_get_busses();

	for(bus = busses; bus; bus = bus->next) {
		struct usb_device *dev;
		for (dev = bus->devices; dev; dev = dev->next) {
			if(dev->descriptor.idVendor==vendor &&
					dev->descriptor.idProduct==product) {
				return dev;
			}
		}
	}
	return NULL;
}

/* magic numbers and such borrowed from p2kmoto */
p2k_phone * p2k_phone_connect(struct usb_device *device) {
	if(device == NULL) {
		printf("***Null device\n");
		return NULL;
	}

	p2k_phone *retval = usb_open(device);
	if(usb_set_configuration(retval, 1) < 0) {
		usb_close(retval);
		printf("***Can't set config\n");
		return NULL;
	}
	if(usb_claim_interface(retval, 0x08) < 0) {
		usb_close(retval);
		printf("***Can't claim interface\n");
		return NULL;
	}

	return retval; 
}

/* this will need changing if we ever get this working on win32 */
int p2k_phone_change_mode(const char *dev_file) {
	int fd = open(dev_file, O_RDWR);
	if(fd < 0) {
		return -1;
	}
	
#ifdef __APPLE__
	int retval = write(fd, "AT+MODE=8\r", 10);
#else
	int retval = write(fd, "AT+MODE=8\n", 10);
#endif
	close(fd);
	return retval;
}

/* this is the easy solution, and should only be used in simple
	applications. there are no error codes, and it does not handle more
	than one phone of the same type */
p2k_phone * p2k_phone_connect_simple(uint16_t at_vendor, uint16_t at_product, 
		uint16_t p2k_vendor, uint16_t p2k_product, const char *dev_file) {
	struct usb_device *device;
	device = usb_find_device(p2k_vendor, p2k_product);

	if(device == NULL) {
		/* didn't find p2k phone */
		device = usb_find_device(at_vendor, at_product);
		if(device == NULL) {
			printf("***No device whatsoever\n");
			/* no device whatsoever */
			return NULL;
		}

		if(p2k_phone_change_mode(dev_file) < 0) {
			printf("***Cant change mode\n");
			return NULL;
		}
		
		time_t timeout = time(0) + 20;
		do {
			sleep(1);
			device = usb_find_device(p2k_vendor, p2k_product);
		}while(device == NULL && time(0) < timeout);
	}

	if(device == NULL) {
		printf("***Time out converting to P2K\n");
		return NULL;
	}

	return p2k_phone_connect(device);
}

int p2k_phone_disconnect(p2k_phone *phone) {
	return usb_close(phone);
}

/* inspired by p2kmoto */
int p2k_phone_send(p2k_phone *phone, p2k_packet *packet) {
	int sz = p2k_packet_get_data_size(packet) + P2K_HEADER_SIZE;
	return usb_control_msg(phone, 0x41, 0x02, 0x00, 0x08, (void*)packet, sz, 1000);
}
p2k_packet *p2k_phone_recv(p2k_phone *phone) {
	uint16_t size_buff[16*2];
	uint8_t data_buff[P2K_HEADER_SIZE + P2K_PACKET_DATA_MAX];
	int retval;
	int packet_size;

	time_t timeout = time(0) + 5;
	/* first we retrieve the size of the packet */
	while(time(0) < timeout) {
		retval = usb_control_msg(phone, 0xC1, 0x00, 
			0x00, 0x08,  (void*)size_buff, sizeof(size_buff), 1000);
		if(retval < 0) {
			return NULL;
		}
		if(retval == 6) {
			return NULL;
		}
		if(retval == 4) {
			break;
		}
		if(retval != 2) {
			printf("Getting packet size, unknown return value: %d\n", retval);
		}
	}
	packet_size = ntohs(size_buff[0]) * 2 + ntohs(size_buff[1]) + 4;

	retval = usb_control_msg(phone, 0xc1, 0x01, 0x01, 0x08,  
		(void*)data_buff, packet_size, 1000);

	if(retval < 0) {
		return NULL;
	}
	if(data_buff[0] != 0x01) {
		return NULL;
	}

	int p=6;
	return p2k_packet_from_buff(data_buff, &p);
}
