/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * The Initial Developer of this code is David Baum.
 * Portions created by David Baum are Copyright (C) 1999 David Baum.
 * All Rights Reserved.
 *
 * Portions created by John Hansen are Copyright (C) 2005 John Hansen.
 * All Rights Reserved.
 *
 */#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <dev/usb/usb.h>

#include "RCX_Pipe.h"

#include "PDebug.h"

#define kVendorID 1684
#define kProductID 1
#define kConfiguration 0
#define kReadPipe 1
#define kWritePipe 2

// these should come from a LEGO include
#define LTW_REQ_GET_PARM	1
#define LTW_REQ_SET_PARM	2
#define LTW_PARM_RANGE		2
#define LTW_RANGE_SHORT		1
#define LTW_RANGE_MEDIUM	2


#define LTW_REQ_SET_TX_SPEED	0xEF
#define LTW_REQ_SET_RX_SPEED	0xF1

#define SPEED_COMM_BAUD_2400	8
#define SPEED_COMM_BAUD_4800	0x10

#define LTW_REQ_SET_TX_CARRIER_FREQUENCY	0xF4

#define UInt32	uint32_t
#define UInt16	uint16_t
#define UInt8	uint8_t
#define IOReturn	int

typedef struct LTW_REQ_REPLY_HEADER {
	UInt16 wNoOfBytes;			// Number of bytes in the reply
	UInt8 bErrCode;				// Request return code
	UInt8 bValue;				// Request return value
} LTW_REQ_REPLY_HEADER;

typedef LTW_REQ_REPLY_HEADER LTW_REQ_GET_SET_PARM_REPLY;

class RCX_USBTowerPipe_fbsd : public RCX_Pipe
{
public:
	RCX_USBTowerPipe_fbsd();
	~RCX_USBTowerPipe_fbsd()	{ Close(); }

	virtual RCX_Result	Open(const char *name, int mode);
	virtual void		Close();

	virtual int		GetCapabilities() const;
	virtual RCX_Result	SetMode(int mode);

	virtual long		Read(void *ptr, long count, long timeout_ms);
	virtual long		Write(const void *ptr, long count);
        virtual bool            IsUSB() const { return true; };

private:
	enum {
		kReadPacketSize = 8
	};

	IOReturn	OpenDevice(short vendorID, short productID);
	IOReturn	OpenInterface();
	IOReturn	ControlRequest(UInt8 request, UInt16 value);
	IOReturn	ControlRequest(UInt8 request, UInt8 loByte, UInt8
hiByte) {
		return ControlRequest(request, loByte + (hiByte << 8));
	}

	void		SetTimeout(long timeout_ms);
	int fdCntrl;
	int fdRd;
	int fdWr;

	char *devname;
};


RCX_Pipe* RCX_NewUSBTowerPipe()
{
	return new RCX_USBTowerPipe_fbsd();
}


RCX_USBTowerPipe_fbsd::RCX_USBTowerPipe_fbsd()
	: fdCntrl(0), devname(0)
{
}


RCX_Result RCX_USBTowerPipe_fbsd::Open(const char *name, int mode)
{
	IOReturn err;

	err = OpenDevice(kVendorID, kProductID);
	PREQUIRENOT(err, Fail_OpenDevice);

	err = OpenInterface();
	PREQUIRENOT(err, Fail_OpenInterface);

	UInt8 range;
	range = (strcmp(name, "short")==0) ? LTW_RANGE_SHORT : LTW_RANGE_MEDIUM;
	ControlRequest(LTW_REQ_SET_PARM, LTW_PARM_RANGE,range);

        RCX_Result err2;
        err2 = SetMode(mode);
        if (err2 != kRCX_OK)
        {
          Close();
          return err2;
        }
        return kRCX_OK;

Fail_OpenInterface:
Fail_OpenDevice:
	Close();
	return kRCX_OpenSerialError;
}


void RCX_USBTowerPipe_fbsd::Close()
{
	if (devname != NULL) {
		close(fdWr);
		close(fdRd);
		close(fdCntrl);
		devname = NULL;
	}
}


int RCX_USBTowerPipe_fbsd::GetCapabilities() const
{
	return kNormalIrMode | kFastIrMode | kSpyboticsMode | kFastOddParityFlag | kAbsorb55Flag;
}


RCX_Result RCX_USBTowerPipe_fbsd::SetMode(int mode)
{
	switch(mode) {
	case kNormalIrMode:
		ControlRequest(LTW_REQ_SET_TX_SPEED, SPEED_COMM_BAUD_2400);
		ControlRequest(LTW_REQ_SET_RX_SPEED, SPEED_COMM_BAUD_2400);
		return kRCX_OK;
	case kFastIrMode:
		ControlRequest(LTW_REQ_SET_PARM, LTW_PARM_RANGE, LTW_RANGE_SHORT);
		ControlRequest(LTW_REQ_SET_TX_SPEED, SPEED_COMM_BAUD_4800);
		ControlRequest(LTW_REQ_SET_RX_SPEED, SPEED_COMM_BAUD_4800);
		ControlRequest(LTW_REQ_SET_TX_CARRIER_FREQUENCY, 38);
		return kRCX_OK;
        case kSpyboticsMode:
                ControlRequest(LTW_REQ_SET_PARM, LTW_PARM_RANGE, LTW_RANGE_SHORT);
                ControlRequest(LTW_REQ_SET_TX_SPEED, SPEED_COMM_BAUD_4800);
                ControlRequest(LTW_REQ_SET_RX_SPEED, SPEED_COMM_BAUD_4800);
                return kRCX_OK;
	default:
		return kRCX_PipeModeError;
	}
}


#define MAX_PACKET 200

long RCX_USBTowerPipe_fbsd::Write(const void *ptr, long length)
{
	const unsigned char *data = (const unsigned char *)ptr;

	int total = 0;

//	printf("Write %ld\n", length);

	while(length > 0) {
		IOReturn err;
		int count = length;
		if (count > MAX_PACKET)
			count = MAX_PACKET;
		err = write(fdWr, (void *)data, count);
		if (err == count)
			err = 0;
		PREQUIRENOT(err, Fail_WritePipe);

		length -= count;
		data += count;
		total += count;
	}

Fail_WritePipe:
//	printf("Done %d\n", total);
	return total;
}


void RCX_USBTowerPipe_fbsd::SetTimeout(long timeout_ms)
{
	usleep(timeout_ms * 1000 / 2);
}


long RCX_USBTowerPipe_fbsd::Read(void *data, long length, long timeout_ms)
{
	const unsigned char *rdptr = (const unsigned char *)data;
	IOReturn err;
	int count;
	int total = 0;
	struct pollfd pfd;

//        SetTimeout(timeout_ms);

//	printf("Read %ld, timeout %ld\n", length, timeout_ms);
	while(length > 0) {
		pfd.fd = fdRd;
		pfd.events = POLLIN;
		usleep(timeout_ms * 1000 / 8);
		err = poll(&pfd, 1, timeout_ms);
		if (err == 0)
			break;
		if (err == -1) {
			if(errno == EINTR)
				continue;
			else
				break;
		}
		count = length;
		if (count > MAX_PACKET)
			count = MAX_PACKET;
		err = read(fdRd, (void *)rdptr, count);
		if (err == -1)
			break;

//		printf("err %d\n", err);
		if (err == 0 || err == -1)
			goto Fail_ReadPipe;

		count = err;
		length -= count;
		rdptr += count;
		total += count;
	}

Fail_ReadPipe:
//	printf("Got %d\n", total);
	return total;
}


IOReturn RCX_USBTowerPipe_fbsd::OpenDevice(short vendorID, short productID)
{
	char *dn = "/dev/ugen0";
	int err, fd;
	usb_device_descriptor_t usbdd;

	fd = open(dn, O_RDWR);
	if (fd == -1)
		return errno;
	err = ioctl(fd, USB_GET_DEVICE_DESC, &usbdd);
	if (err == -1) {
		perror("ioctl error");
		return errno;
	}
//	printf("idVendor %d\n", UGETW(usbdd.idVendor));
//	printf("idProduct %d\n", UGETW(usbdd.idProduct));
	if (UGETW(usbdd.idVendor) != kVendorID ||
	    UGETW(usbdd.idProduct) != kProductID) {
		fprintf(stderr, "This is not an USB IR Tower!\n");
		return -1;
	}

	devname = dn;
	fdCntrl = fd;

	return 0;
}


IOReturn RCX_USBTowerPipe_fbsd::OpenInterface()
{
	char *dn;
	int err, iov;

	dn = (char *)malloc(strlen(devname) + 2 + 1);
	strcpy(dn, devname);
	strcat(dn, ".1");
	fdRd = open(dn, O_RDONLY|O_NONBLOCK);
	if (fdRd == -1) {
		perror(dn);
		return -1;
	}
	iov = 1;
	err = ioctl(fdRd, USB_SET_SHORT_XFER, &iov);
	if (fdRd == -1) {
		perror("ioctl USB_SET_SHORT_XFER");
		return -1;
	}
	iov = 5000;
	err = ioctl(fdRd, USB_SET_TIMEOUT, &iov);
	if (fdRd == -1) {
		perror("ioctl USB_SET_TIMEOUT");
		return -1;
	}

	strcpy(dn, devname);
	strcat(dn, ".2");
	fdWr = open(dn, O_WRONLY);
	iov = 5000;
//	ioctl(fdWr, USB_SET_TIMEOUT, &iov);

	return 0;
}


IOReturn RCX_USBTowerPipe_fbsd::ControlRequest(UInt8 request, UInt16 value)
{
	IOReturn err;
	struct usb_ctl_request req;
	LTW_REQ_GET_SET_PARM_REPLY reply;

	req.ucr_request.bmRequestType = UT_READ_VENDOR_DEVICE;
	req.ucr_request.bRequest = request;
	USETW(req.ucr_request.wValue, value);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, sizeof(reply));
	req.ucr_data = &reply;

	err = ioctl(fdCntrl, USB_DO_REQUEST, &req);

	// size of actual reply in req.wLenDone
	//printf("%d %d %d\n", reply.wNoOfBytes, reply.bErrCode, reply.bValue);

	return err;
}

