#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>




/* 7 6 5 4 3 2 1 0
* Touchpad Mode
* byte 0: 1 1 1 1 1 mid0 rig0 lef0
* byte 1: 0 x6 x5 x4 x3 x2 x1 x0
* byte 2: 0 x10 x9 x8 x7 0 fin ges
* byte 3: 0 y9 y8 y7 1 mid1 rig1 lef1
* byte 4: 0 y6 y5 y4 y3 y2 y1 y0
* byte 5: 0 z6 z5 z4 z3 z2 z1 z0
*
* Trackstick Mode
* byte 0: 0 0 sy sx 1 mid0 rig0 lef0
* byte 1: x7 x6 x5 x4 x3 x2 x1 x0
* byte 2: x7 y6 y5 y4 y3 y2 y1 y0
*/


void flushmouse(int fd)
{
	for (;;) {
		fd_set fds;
		struct timeval tm;
		char buffer[16];

		FD_ZERO(&fds);
		FD_SET(fd, &fds);
		tm.tv_sec = 0;
		tm.tv_usec = 1;
		if (select(fd + 1, &fds, NULL, NULL, &tm) <= 0 ||
		    read(fd, buffer, sizeof(buffer)) <= 0) {
			break;
		}
	}
}
int pscmd(int fd, unsigned char *cmd, int clen, unsigned char *resp, int rlen)
{
	/* write 'cmd' to fd, receiving FA for each byte.
	 * then read rlen bytes into resp
	 * Don't wait more than 1 second though
	 */
	char b;
	if (resp==NULL) resp = &b;
	while (clen+rlen) {
		fd_set fds;
		struct timeval to;
		if (clen) {
			if (write(fd, cmd, 1) != 1)
				return -1;
			cmd++;
		}
		FD_ZERO(&fds);
		FD_SET(fd, &fds);
		to.tv_sec=0;
		to.tv_usec=800000;
		if (select(fd+1, &fds, NULL, NULL, &to) != 1)
			return -1;
		if (read(fd, resp, 1) != 1)
			return -1;
		if (clen) {
			if (*resp != 0xFA)
				return -1;
			clen--;
		} else {
			resp++;
			rlen--;
		}
	}
	return 0;
}

int test_alps(int fd)
{
	/* write Disable(F5) stream(EA)
	 * set-scale-2:1(E7) 3 times, then Status Request(E9).
	 * If the status isn't disabled, stream, 2:1, then
	 * it could be an alps
	 */
	unsigned char b[3];
	unsigned char *ptr;

	/* pass throug mode.... */
#if 0
/* e7 e7 e7 f5 for off */
 for (ptr = "\xE6\xE6\xE6\xF3\x13"; *ptr; ptr++) { 
	/* for (ptr = "\xE7\xE7\xE7\xF5"; *ptr; ptr++) { */
		unsigned char ch;
		if (write(fd, ptr, 1) != 1 ||
		    read(fd, &ch, 1) != 1 ||
		    ch != 0xFA) {
			fprintf(stderr, "Failed to set ALPS passthrough\n");
		}
	}
#endif
 /* disable, setstream, setscale21*3, getinfo */
 pscmd(fd, "\xF5\xEA\xE7\xE7\xE7\xE9", 6, b, 3);
 
	if ((b[0]&0x40) ||  /* remote - not stream */
	    (b[0]&0x20) ||  /* Enabled - not disabled */
	    !(b[0]&0x10))   /* 1:1 - not 2:1 */
	{
		printf("ALPS detected: %02x %02x %02x\n", b[0], b[1], b[2]);
		return 1;
	} else {
		printf("Not an ALPS device: %02x %02x %02x\n", b[0], b[1], b[2]);
		return 0;
	}
}


void init_alps(int fd, int x)
{
	int i= 0;
	unsigned char b[4];

	/* getinfo(i3) disable*2 setres(o=0) */
	pscmd(fd, "\xE9", 1, b, 3);
	pscmd(fd, "\xF5\xF5\xE8\x00", 4, NULL, 0);

	/* disable*4 enable */
	pscmd(fd, "\xF5\xF5\xF5\xF5\xF4", 5, NULL, 0);
	return;
}




void init_ps2(int fd) 
{
	unsigned char *ptr;
	for (ptr = "\xF6\xE6\xF4\xF3\x64\xE8\x03"; *ptr; ptr++) {
		unsigned char ch;
		if (write(fd, ptr, 1) != 1 ||
		    read(fd, &ch, 1) != 1 ||
		    ch != 0xFA) {
			fprintf(stderr, "Failed to initialize ALPS pad\n");
		}
	}
	return;
}

void init_imps(int fd)
{
	pscmd(fd, "\xF3\xc8\xf3\x64\xf3\x50", 6, NULL, 0);
}



int main(int argc, char *argv[])
{
	int init_type = 1;
	char *dev = "/dev/psaux";
	int fd;

	if (argc > 2) 
		dev = argv[2];

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		perror(dev);	
		exit(1);
	}

	if (argc > 1) {
		if (!strcmp(argv[1], "noinit")) {
			init_type = 0;
		} else if (!strcmp(argv[1], "alps")) {
			init_type = 1;
		} else if (!strcmp(argv[1], "ps2")) {
			init_type = 2;
		} else if (!strcmp(argv[1], "imps")) {
			init_type = 3;
		} else {
			fprintf(stderr, "Unknown command line argument: \"%s\"\n",
				argv[1]);
			exit(1);
		}
	}

	if (fd < 0) {
		fprintf(stderr, "Failed to open /dev/psaux\n");
		exit(1);
	}

 retry:

	flushmouse(fd);

	switch (init_type) {
	case 1:
		if (test_alps(fd)) {
			init_alps(fd, 0);
			/*			test_alps(fd);*/
			init_alps(fd, 1);
		} else
			init_ps2(fd);
		break;
	case 2:
		init_ps2(fd);
		break;
	case 3: init_imps(fd);
		break;
	default:
		break;
	}

	for (;;) {
		unsigned char buffer[6];
		int i, j = 6;
		if (init_type==3)
			j=4;
		for (i = 0; i < j; i++) {
			if (read(fd, buffer+i, 1) != 1) {
				fprintf(stderr, "Failed to read from /dev/psaux\n");
				exit(1);
			}
			printf("0x%02x ", buffer[i]);
			fflush(stdout);
			if (init_type==3)
				;
			else if (i == 0) {
				j = (buffer[0] & 0x80) != 0x80 ? 3 : 6;
				if (( (buffer[0] & 0x08) != 0x08) ||
				    (j == 3 && (buffer[0] & 0xC8) != 0x08) ||
				    (j == 6 && (buffer[0] & 0xF8) != 0xF8)) {
					fprintf(stderr, "unknown code\n");
					goto retry;
				}
			} else if (j == 6) {
				if ((buffer[i] & 0x80) != 0x00) {
					fprintf(stderr, "unknown code\n");
					goto retry;
				}
			}
		}
		if (j == 3 || j==4) {
			int dx = buffer[1];
			int dy = buffer[2];
			if (buffer[0] & 0x10)
				dx |= -1 & ~0xFF;
			if (buffer[0] & 0x20)
				dy |= -1 & ~0xFF;
			printf("dx = %3d, dy = %3d", dx, dy);
			if (buffer[0] & 0x1)
				printf(" leftA");
			if (buffer[0] & 0x4)
				printf(" middleA");
			if (buffer[0] & 0x2)
				printf(" rightA");
			if ((buffer[0] & 0xc8) != 8) 
				printf(" %x", buffer[0] & 0xc8);
			if (j==4) printf(" dz=%3d", buffer[3]);
		} else {
			int x = buffer[1] | ((buffer[2] & 0x78) << 4);
			int y = buffer[4] | ((buffer[3] & 0x70) << 3);
			int z = buffer[5];
			printf("x = %3d, y = %3d, z = %3d", x, y, z);
			if ((buffer[0]) & 0x1)
				printf(" leftA");
			if ((buffer[3]) & 0x1)
				printf(" leftB");
			if ((buffer[0]) & 0x4)
				printf(" middleA");
			if ((buffer[3]) & 0x4)
				printf(" middleB");
			if ((buffer[0]) & 0x2)
				printf(" rightA");
			if ((buffer[3]) & 0x2)
				printf(" rightB");
			if (buffer[2] & 0x01)
				printf(" gesture");
			if (buffer[2] & 0x02)
				printf(" finger");
		}
		printf("\n");
	}
}

