#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <ctype.h>

#include "mdetect.h"

extern int opt_xf86config;

static int
flush(struct device *dev, char *buffer, unsigned int size)
{
	char	x;
	int	j, n = 0;

	fcntl(dev->fd, F_SETFL, O_NDELAY);
	while (n < size && read(dev->fd, &x, 1) > 0) {
		if (buffer)
			buffer[n] = x;
		n++;
	}
	fcntl(dev->fd, F_SETFL, 0);

	if (n && opt_verbose > 2) {
		printf("Read %d chars from %s: ", n, dev->name);
		for (j = 0; j < n; j++)
			printf(isprint(buffer[j])? "%c" : "\\%03o",
				(unsigned char) buffer[j]);
		printf("\n");
	}

	return n;
}

static void
_setflags(struct device *dev, int flags)
{
	struct termios	tty;

	tcgetattr(dev->fd, &tty);
	tty.c_iflag	= IGNBRK | IGNPAR;
	tty.c_oflag	= 0;
	tty.c_lflag	= 0;
	tty.c_cflag	= flags | CREAD | CLOCAL | HUPCL;
/* c_line is not POSIX */
#ifdef __linux__
	tty.c_line	= 0;
#endif
	tty.c_cc[VTIME]	= 0;
	tty.c_cc[VMIN]	= 1;

	tcsetattr(dev->fd, TCSAFLUSH, &tty);
}

static void
setflags(struct device *dev, int flags)
{
	_setflags(dev, flags | B1200);
	flush(dev, NULL, 10);
}

void
serialdev_init(struct device *dev)
{
	int	n, speed[4] = { B9600, B4800, B2400, B1200 };
	char	buffer[10];

	/* Tell the mouse at what speed it should report.
	 * We need the loop so we're sure it understands what we
	 * tell it. */
	for (n = 0; n < 4; n++) {
		_setflags(dev, CSTOPB|speed[n]);
		write(dev->fd, "*q", 2);
		usleep(10000);
	}

	/* gpm hack */
	if (flush(dev, buffer, 10) == 2 && buffer[1] == 0x33) {
		struct mouse	*driver;

		driver = (struct mouse *) dev->driver->alt;
		while (driver) {
			if (!strcmp(driver->name, "microsoft"))
				driver->name = "mouseman";
			driver = driver->next;
		}
	}
}

static int
serialdev_event(struct mouse *driver)
{
	if (driver->good == 0) {
		driver->lx = driver->dx;
		driver->ly = driver->dy;
	}
	if (send_event(driver) >= 0 || driver->good) {
		driver->got = 0;
	} else {
		driver->buffer[0] = driver->buffer[1];
		driver->buffer[1] = driver->buffer[2];
		driver->buffer[2] = driver->buffer[3];
		driver->got--;
	}

	driver->lx = driver->dx;
	driver->ly = driver->dy;
	return 0;
}

static int
init_logitech(struct device *dev, struct mouse *driver)
{
	setflags(dev, CSTOPB | CS8 | PARENB | PARODD);

	/* logitech magic */
	if (write(dev->fd, "S", 1) < 0)
		return -1;

	/* 60 samples per sec */
	if (write(dev->fd, "R", 1) < 0)
		return -1;
	return 0;
}

static int
packet_logitech(struct device *dev, struct mouse *driver, unsigned char c)
{
	unsigned char	*data = driver->buffer;

/*
printf("%02x ", c); fflush(stdout);
 */
	driver->buffer[driver->got++] = c;
	switch (driver->got) {
	case 1:
		if ((c & 0xE0) != 0x80) {
			driver->got = 0;
			return 0;
		}
		return 0;
	case 2:
		if ((c & 0x80) != 0x00) {
			driver->got = 0;
			return -1;
		}
		return 0;
	}

	driver->buttons = data[0] & 0x7;
	driver->dx = (data[0] & 0x10)?  data[1] : -data[1];
	driver->dy = (data[0] & 0x20)? -data[2] :  data[2];

	return serialdev_event(driver);
}

static int
init_ms(struct device *dev, struct mouse *driver)
{
	setflags(dev, CS7);

	/* 60 samples per sec */
	if (write(dev->fd, "R", 1) < 0)
		return -1;
	return 0;
}

/* When testing this with some logitechs, I noticed that they send
 * an additional byte for middle-button (0x20 while button is pressed,
 * 0x00 once when released). Wheark.
 * (I later found out that this is the mouse man protocol; so replace
 * s/weirdo/mouseman/g below).
 */
static int
packet_ms(struct device *dev, struct mouse *driver, unsigned char c)
{
	unsigned char	*data = driver->buffer;
	int		middledown = 0, weirdo = 0;

	middledown = (driver->buttons & BUTTON_MIDDLE);
	weirdo = driver->flags & FLAG_MS_WEIRDO;

	driver->buffer[driver->got++] = c;
	switch (driver->got) {
	case 1:
		if ((c & 0x40) != 0x40) {
			if (c == 0x20 && middledown && !weirdo)
				driver->flags |= FLAG_MS_WEIRDO;
			driver->got = 0;
			return 0;
		}
		return 0;
	case 2:
		if ((c & 0x40) != 0x00) {
			driver->got = 0;
			return -1;
		}
		return 0;
	case 3:
		/* waiting for fourth byte? */
		if (weirdo && middledown)
			return 0;
	}

	/* M$ 3button extension: a packet of 0x40 0x00 0x00, valid
	 * only if the other two buttons have been released. */
	if (data[0] == 0x40 && !(driver->buttons | data[1] | data[2])) {
		driver->buttons = BUTTON_MIDDLE;
	} else {
		driver->buttons = ((data[0] & 0x20) >> 3)
				| ((data[0] & 0x10) >> 4);
		if (weirdo && middledown && data[3] == 0x20)
			driver->buttons |= BUTTON_MIDDLE;
	}

	driver->dx = (char) ((data[0] & 0x3) << 6) | (data[1] & 0x3f);
	driver->dy = (char) ((data[0] & 0xC) << 4) | (data[2] & 0x3f);

	return serialdev_event(driver);
}

static const char *
report_ms(struct mouse *driver)
{
	if (driver->flags & FLAG_MS_WEIRDO)
		return "mouseman";
	return driver->name;
}

/* Mouse-systems compatible
 */
static int
init_msc(struct device *dev, struct mouse *driver)
{
	setflags(dev, CS8|CSTOPB);

	/* 60 samples per sec */
	if (write(dev->fd, "R", 1) < 0)
		return -1;
	return 0;
}

static int
packet_msc(struct device *dev, struct mouse *driver, unsigned char c)
{
	unsigned char	*data = driver->buffer;

/* printf("msc: %02x\n", c); */
	driver->buffer[driver->got++] = c;
	switch (driver->got) {
	case 1:
		if ((c & 0xf8) != 0x80) {
			driver->got = 0;
			return 0;
		}
		return 0;
	case 2:
	case 3:
	case 4:
		return 0;
	}

	driver->buttons = (~data[0]) & 0x7;
	driver->dx =  (char) data[1] + (char) data[3];
	driver->dy = -(char) data[2] + (char) data[4];

	return serialdev_event(driver);
}

static void
next_serial(struct device *dev, struct mouse *driver)
{
	struct mouse	*sub;

	sub = (struct mouse *) driver->data;

	if (!(sub = sub->next))
		sub = driver->alt;

	while (sub && sub->init(dev, sub) < 0)
		sub = sub->next;
	driver->data = sub;

	if (opt_verbose) {
		printf("\n    %s\r", sub->name);
		fflush(stdout);
	}

	/* Now clean all driver state */
	driver->total = 0;
	for (sub = driver->alt; sub; sub = sub->next) {
		sub->good = sub->bad = 0;
		sub->got  = sub->total = 0;
		sub->timeout = 0;
		sub->flags = 0;
	}
}

static int
init_serial(struct device *dev, struct mouse *driver)
{
	struct mouse	*sub;

	/* Initialize the serial device */
	serialdev_init(dev);

	/* Remove any devices we can't initialize */
	while ((sub = driver->alt) != NULL && sub->init(dev, sub) < 0)
		driver->alt = sub->next;
	if (!(driver->data = sub))
		return -1;
	return 0;
}

static int
packet_serial(struct device *dev, struct mouse *driver, unsigned char c)
{
	struct mouse	*sub;
	int		okay = 0;

	if (!(sub = driver->alt))
		return -1;
	sub = (struct mouse *) driver->data;
	sub->total++;

	if (opt_verbose) {
		printf("%3d %s\r", sub->total, sub->name);
		fflush(stdout);
	}

	driver->total++;
	driver->good = 0;
	dev->timeout = 0;
	if (!sub->bad && sub->packet(dev, sub, c) >= 0) {
		if (sub->good > driver->good) {
			driver->good = sub->good;
			if (sub->good >= MIN_GOOD_EVENTS) {
				dev->driver = sub;
				dev->timeout = 0;
				return 0;
			}
		}
		if (sub->got)
			dev->timeout = 10;
		okay++;
	}

	if (driver->good == 0) {
		if (driver->total > SERIAL_NOISE_MAX)
			next_serial(dev, driver);
		else if (dev->timeout == 0)
			dev->timeout = 100;
	}

	return 0;
}

static int
timeout_serial(struct device *dev)
{
	next_serial(dev, dev->driver);
	return 0;
}

static int
init_psaux(struct device *dev, struct mouse *driver)
{
	static unsigned char	query[] = { 0xF2 };
	static unsigned char	init[] = { 243, 200,
	       				   243, 100,
					   243, 80 };
	char			buffer[20];
	int			n;
	
	if (opt_verbose > 1)
		printf("%s sending intellimouse init sequence.\n", dev->name);
	write(dev->fd, init, sizeof(init));
	usleep(10000);
	flush(dev, buffer, 20);

	if (opt_verbose > 1)
		printf("%s querying PS/2 ID.\n", dev->name);
	write(dev->fd, query, sizeof(query));
	usleep(10000);
	if ((n = flush(dev, buffer, 20)) > 0) {
		const char	*type = NULL;
		struct mouse	*m;

		switch(buffer[n-1]) {
		case 0:
			type = "psaux";
			break;
		case 3:	/* 3button + Z format */
		case 4:	/* 5button + Z format */
			type = "intellimouse";
			break;
		}

		if (type && opt_verbose)
			printf("%s says it's a %s mouse\n", dev->name, type);
		if (type && !opt_nopnp) {
			for (m = driver; m; m = m->next) {
				if (!strcmp(m->name, type)) {
					dev->driver = m;
					dev->flags |= DEV_DRIVER_FOUND;
					return 0;
				}
			}
		}
	}
	
	/* Detection failed. Try to reset mouse */
#if 0
	{
		static unsigned char	reset[] = { 0xff };

		write(dev->fd, &reset, 1);
		close(dev->fd);
		if ((dev->fd = open(dev->name, O_RDWR)) < 0) {
			dev->fd = -1;
			return -1;
		}
	}
#endif

	return 0;
}

static int
packet_psaux(struct device *dev, struct mouse *driver, unsigned char c)
{
	driver->buffer[driver->got++] = c;
	driver->total++;

	if (driver->got == 3) {
		unsigned char	*d = driver->buffer;

		/* The top two bits are the overflow bits.
		 * They get set when the mouse delta exceeds 255.
		 */
		if (d[0] & 0xc0)
			goto bad;

		/* The PS/2 docs on the M$ hwdev web pages say
		 * this bit is always on.
		 */
		if (!(d[0] & 8))
			goto bad;

		driver->buttons = ((d[0] & 1) << 2)
				| ((d[0] & 4) >> 1)
				| ((d[0] & 2) >> 1);
		driver->dx = (d[0] & 0x10)? d[1] - 256 : d[1];
		driver->dy = (d[0] & 0x20)? d[2] - 256 : d[2];
		if (send_event(driver) < 0)
			goto bad;

		driver->got = 0;
	}

out:
	dev->timeout = driver->got? 10 : 0;
	return 0;
	
bad:
	if (driver->total > 20) {
#if 0
		if ((driver = driver->next) == NULL)
			return -1;
		if (opt_verbose > 2)
			printf("psaux out of sync, switching next.\n");
		dev->driver = driver;
		if (driver->init)
			driver->init(dev, driver);
#else
		return -1;
#endif
	} else {
		/* Eat the first character and discard it */
		if (opt_verbose > 2)
			printf("psaux out of sync, trying to resync.\n");
		memmove(driver->buffer, driver->buffer+1, driver->got - 1);
		driver->got--;
	}
	goto out;
}

static int
packet_usb(struct device *dev, struct mouse *driver, unsigned char c)
{
	driver->buffer[driver->got++] = c;
	driver->total++;

	if (driver->got == 3) {
		unsigned char	*d = driver->buffer;

		/* The top two bits are the overflow bits.
		 * They get set when the mouse delta exceeds 255.
		 */
		if (d[0] & 0xc0)
			goto bad;

		driver->buttons = ((d[0] & 1) << 2)
				| ((d[0] & 4) >> 1)
				| ((d[0] & 2) >> 1);
		driver->dx = (d[0] & 0x10)? d[1] - 256 : d[1];
		driver->dy = (d[0] & 0x20)? d[2] - 256 : d[2];
		if (send_event(driver) < 0)
			goto bad;

		driver->got = 0;
	}

out:
	dev->timeout = driver->got? 10 : 0;
	return 0;
	
bad:
	if (driver->total > 20) {
		return -1;
	}
	/* Eat the first character and discard it */
	if (opt_verbose > 2)
		printf("psaux out of sync, trying to resync.\n");
	memmove(driver->buffer, driver->buffer+1, driver->got - 1);
	driver->got--;
	goto out;
}

static int
timeout_psaux(struct device *dev)
{
	struct mouse	*driver = dev->driver;

	if (driver->total < 20) {
		driver->got = 0;
		dev->timeout = 0;
		return 0;
	}
	return -1;
}

static int
packet_sunmouse(struct device *dev, struct mouse *driver, unsigned char c)
{
	/* if we get bytes from driver then we detected a sun mouse... */
	dev->flags |= DEV_DRIVER_FOUND;
	return 0;
}

static int
init_usb24(struct device *dev, struct mouse *driver)
{
	dev->flags |= DEV_DRIVER_FOUND;
	return 0;
}

static int
packet_usb24(struct device *dev, struct mouse *driver, unsigned char c)
{
	return 0;
}


static struct mouse	mouse_msc = {
	"mousesystems",
	"MouseSystems",		/* XFree86 mouse driver protocol name */
	init_msc,		/* init code */
	packet_msc,		/* packet handler */
	NULL,			/* timeout */
	NULL,			/* report */

	NULL,			/* next */
	NULL,			/* alt */
	NULL,			/* data */
};

static struct mouse	mouse_ms = {
	"microsoft",
	"Microsoft",		/* XFree86 mouse driver protocol name */
	init_ms,		/* init code */
	packet_ms,		/* packet handler */
	NULL,			/* timeout */
	report_ms,		/* report */

	&mouse_msc,		/* next */
	NULL,			/* alt */
	NULL,			/* data */
};

static struct mouse	mouse_logitech = {
	"logitech",
	"Logitech",		/* XFree86 mouse driver protocol name */
	init_logitech,		/* init code */
	packet_logitech,	/* packet handler */
	NULL,			/* timeout */
	NULL,			/* report */

	&mouse_ms,		/* next */
	NULL,			/* alt */
	NULL,			/* data */
};

static struct mouse	mouse_serial = {
	"serial",
	"Auto",			/* XFree86 mouse driver protocol name */
	init_serial,		/* init code */
	packet_serial,		/* packet handler */
	timeout_serial,		/* timeout */
	NULL,			/* report */

	NULL,			/* next */
	&mouse_logitech,	/* alt */
	&mouse_logitech,	/* currently initialized */
};

static struct mouse	mouse_intelli = {
	"intellimouse",
	"IntelliMouse",		/* XFree86 mouse driver protocol name */
	init_psaux,		/* init code */
	packet_psaux,		/* packet handler */
	timeout_psaux,		/* timeout */
	NULL,			/* report */
};

static struct mouse	mouse_psaux = {
	"psaux",
	"PS/2",			/* XFree86 mouse driver protocol name */
	init_psaux,		/* init code */
	packet_psaux,		/* packet handler */
	timeout_psaux,		/* timeout */
	NULL,			/* report */
	
	&mouse_intelli,
};

static struct mouse	mouse_usb = {
	"USB",
	"USB",			/* XFree86 mouse driver protocol name */
	NULL,			/* init code */
	packet_usb,		/* same as ps/2 mice */
	timeout_psaux,		/* timeout */
	NULL,			/* report */
};

static struct mouse	mouse_sunmouse = {
	"sunmouse",
	"SysMouse",		/* XFree86 mouse driver protocol name XXX correct? */
	NULL,			/* init code */
	packet_sunmouse,	/* packet handler */
	NULL,			/* timeout */
	NULL,			/* report */
};

static struct mouse	mouse_usb24 = {
	"USB",
	"ImPS/2",		/* XFree86 mouse driver protocol name */
	init_usb24,		/* init code */
	packet_usb24,		/* packet handler */
	NULL,			/* timeout */
	NULL,			/* report */
};

/* Return the mouse driver for a device file name */
static struct mouse *
find_mouse(const char *name)
{
	if (!strcmp(name, "serial"))
		return &mouse_serial;
	if (!strcmp(name, "psaux"))
		return &mouse_psaux;
	if (!strcmp(name, "usb") || !strcmp(name, "hidbp-mse-0"))
		return &mouse_usb;
	if (!strncmp(name, "input/mouse", 11))
		return &mouse_usb24;
	if (!strcmp(name, "sunmouse"))
		return &mouse_sunmouse;
	return NULL;
}

static struct mouse *
clone_mouse(struct mouse *driver)
{
	struct mouse	*tmp;

	tmp = (struct mouse *) malloc(sizeof(*driver));
	memcpy(tmp, driver, sizeof(*tmp));
	driver = tmp;

	if (driver->alt)
		driver->alt = clone_mouse(driver->alt);
	if (driver->next)
		driver->next = clone_mouse(driver->next);
	return driver;
}

struct mouse *
get_mouse(const char *name)
{
	struct mouse	*driver;

	if (!(driver = find_mouse(name)))
		return NULL;
	return clone_mouse(driver);
}
