#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "hidapi.h"

/* default vid and pid */
#define DEF_VID 0x04D8
#define DEF_PID 0x00DF

int vid = DEF_VID;
int pid = DEF_PID;

typedef enum cmd_e {
	CMD_INVALID,
	CMD_HELP,
	CMD_SEND,
	CMD_GPIO,
} cmd_t;

#define setcmd(c) \
do { \
	if (cmd != CMD_INVALID) { \
		fprintf(stderr, "Can not execute multiple operations in a single session.\n"); \
		exit(1); \
	} \
	cmd = c; \
} while(0)

#define setcmd_buff(c, buff, from, to) \
do { \
	int addr; \
	char *end; \
	setcmd(c); \
	for(n++, addr = from; (addr <= to) && (n < argc); n++,addr++) { \
		buff[addr] = strtol(argv[n], &end, 0); \
		if ((end == NULL) || (*end != '\0')) { \
			fprintf(stderr, "Error: invalid integer '%s'\n", argv[n]); \
			exit(1); \
		} \
	} \
} while(0)

void help()
{
	printf("mcp2200hid - low level access of the hid functions of mcp2200\n");
	printf("Usage: mcp2200hid [opts] op [args]\n");
	printf("ops are (one per invocation):\n");
	printf(" --help                print this help\n");
	printf(" --send 16 numbers     low level send and print answer\n");
	printf(" --configure 16 nums   set all configuration bytes\n");
	printf(" --read_all            read the configuration (0x80)\n");
	printf(" --write_ee addr val   write val into the eeprom at addr (0x40)\n");
	printf(" --read_ee addr        read the eeprom at addr (0x20)\n");
	printf(" --gpio                switch to interactive gpio mode\n");
	printf(" --setid [vid pid]     set device id to vid:pid (if omitted, to defaults, 0x00)\n");
	printf("\n");
	printf("opts are:\n");
	printf(" --noresponse          don't wait and print the response\n");
	printf("\n");
}

unsigned char buf[16];
hid_device *handle;
int verbose = 0;

void print_bits(const char *prefix, unsigned char data)
{
	int n;
	printf(prefix);
	for(n = 0; n < 8; n++) {
		printf(" %d", !!(data & 0x80));
		data <<= 1;
	}
	printf("\n");
}

int read_bits(const char *s, int *ones, int *zeros)
{
	int n;
	*zeros = 0;
	*ones = 0;
	for(n = 0; n < 8; n++) {
		*zeros <<= 1;
		*ones <<= 1;
		while(isspace(*s)) s++;
		switch(*s) {
			case '\0': return 0;
			case '0':
			case 'o': /* for output in dir */
			case 'O':
			case 'g': /* for gpio functionality in setalt */
				*zeros |= 1;
				break;
			case '1':
			case 'i': /* for input in dir */
			case 'I':
			case 'a': /* for alternate functionality in setalt */
				*ones |= 1;
				break;
			case 'x':
			case '-':
			case '.': break;
			default:
				fprintf(stderr, "invalid bitsetmap character ('%c', should be 0, 1 or x)\n", *s);
				return 1;
		}
		s++;
	}
	return 0;
}

int read_all()
{
	int res;
	buf[0] = 0x80;
	hid_write(handle, buf, 16);
	res = hid_read(handle, buf, 16);
	if (res < 0) {
		printf("ERROR: hid_read failed after a READ_ALL request\n");
		return 1;
	}
	return 0;
}

int gpio(FILE *f)
{
	char line[1024], *cmd, *end;

	handle = hid_open(vid, pid, NULL);

	while(!(feof(f))) {
		*line = '\0';
		fgets(line, sizeof(line), f);
		cmd = line;
		while(isspace(*cmd)) cmd++;
		if ((*cmd == '#') || (*cmd == '\n') || (*cmd == '\r') || (*cmd == '\0'))
			continue;
		if ((strncmp(cmd, "exit", 4) == 0) || (strncmp(cmd, "quit", 4) == 0))
			return 0;
		else if (strncmp(cmd, "usleep", 6) == 0) {
			int l;
			l = strtol(cmd+6, &end, 0);
			if ((*end != '\n') && (*end != '\r') && (*end != '\0'))
				fprintf(stderr, "syntax error: usleep requires an integer argument\n");
			else
				usleep(l);
		}
		else if (strncmp(cmd, "get", 3) == 0) {
			if (read_all(buf) == 0) {
				print_bits("in", buf[10]);
				print_bits("dir", buf[4]);
				print_bits("alt", buf[5]);
			}
		}
		else if (strncmp(cmd, "setdir", 6) == 0) {
			int ones, zeros;
			char *s;
			s = cmd + 6;
			if ((read_bits(s, &ones, &zeros) == 0) && (read_all(buf) == 0)) {
				/* current configuration is in the buffer */
				/* [5]=alt pins; [4]=iobmap */
				buf[4] |= ones;
				buf[4] &= ~zeros;
				buf[0] = 0x10;
				hid_write(handle, buf, 16);
			}
		}
		else if (strncmp(cmd, "setalt", 6) == 0) {
			int ones, zeros;
			char *s;
			s = cmd + 6;
			if ((read_bits(s, &ones, &zeros) == 0) && (read_all(buf) == 0)) {
				/* current configuration is in the buffer */
				/* [5]=alt pins; [4]=iobmap */
				buf[5] |= ones;
				buf[5] &= ~zeros;
				buf[0] = 0x10;
				hid_write(handle, buf, 16);
			}
		}
		else if (strncmp(cmd, "set", 3) == 0) {
			int bs, bc;
			char *s;
			s = cmd + 3;
			if (read_bits(s, &bs, &bc) == 0) {
				if (verbose)
					printf("set on=%02x off=%02x\n", bs, bc);
				buf[0] = 0x08;
				buf[11] = bs;
				buf[12] = bc;
				hid_write(handle, buf, 16);
				/* there is no response */
			}
		}
		else
			fprintf(stderr, "Syntax error: unknown command %s\n", cmd);
	}
	return 0;
}

int main(int argc, char *argv[])
{
	int n, res, noresp;
	cmd_t cmd = CMD_INVALID;
	char *c;

	memset(buf, 0, 16);
	noresp = 0;

	for(n = 1; n < argc; n++) {
		c = argv[n];
		while(*c == '-')
			c++;
		if (strcmp(c, "help") == 0) setcmd(CMD_HELP);
		if (strcmp(c, "gpio") == 0) setcmd(CMD_GPIO);
		if (strcmp(c, "send") == 0) setcmd_buff(CMD_SEND, buf, 0, 15);
		if (strcmp(c, "noresponse") == 0) noresp = 1;
		if (strcmp(c, "vid") == 0) {n++; vid = strtol(argv[n], NULL, 0); }
		if (strcmp(c, "pid") == 0) {n++; pid = strtol(argv[n], NULL, 0); }
		if (strcmp(c, "setid") == 0) {
			int new_vid = DEF_VID, new_pid = DEF_PID;
			n++;
			if (n < argc)
				new_vid = strtol(argv[n], NULL, 0);
			n++;
			if (n < argc)
				new_pid = strtol(argv[n], NULL, 0);

			buf[0] = 0x00;
			buf[1] = 0x01;
			buf[3] = (new_vid & 0x00FF);
			buf[4] = (new_vid & 0xFF00) >> 8;
			buf[5] = (new_pid & 0x00FF);
			buf[6] = (new_pid & 0xFF00) >> 8;
			setcmd(CMD_SEND);
			noresp = 1;
		}
		if (strcmp(c, "read_all") == 0) {
			setcmd_buff(CMD_SEND, buf, 0, 0);
			buf[0] = 0x80;
		}
		if (strcmp(c, "write_ee") == 0) {
			setcmd_buff(CMD_SEND, buf, 1, 2);
			buf[0] = 0x40;
			noresp = 1;
		}
		if (strcmp(c, "configure") == 0) {
			setcmd_buff(CMD_SEND, buf, 1, 15);
			buf[0] = 0x10;
			noresp = 1;
		}
		if (strcmp(c, "read_ee") == 0) {
			setcmd_buff(CMD_SEND, buf, 1, 1);
			buf[0] = 0x20;
		}
	}

	switch(cmd) {
		case CMD_INVALID:
			fprintf(stderr, "please specify an operation (or try --help)\n");
			return 1;
		case CMD_HELP:
			help();
			return 0;
		case CMD_GPIO:
			return gpio(stdin);
		case CMD_SEND:
			handle = hid_open(vid, pid, NULL);
			if (handle == NULL) {
				printf("ERROR: hid_open failed; is the device plugged in? Does it have a /dev/hidraw*?\n");
				return 1;
			}

			hid_write(handle, buf, 16);
			if (!noresp) {
				res = hid_read(handle, buf, 16);
				if (res < 0) {
					printf("ERROR: hid_read failed\n");
					return 1;
				}
				/* output the response */
				for (n = 0; n < res; n++)
					printf("response %d 0x%02x\n", n, buf[n]);
			}
	}

	return 0;
}
