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

#include <linux/input.h>

#define CODE_LEN 128
#define MIN_PULSE 50
#define MAX_KEYS 100

static float pulses[10];

static int input;

struct keys {
	char *code;
	char *cmd;
};

static struct keys keys[MAX_KEYS];
static char xor_code[CODE_LEN];

/* read from input layer, output time difference == pulse length */
static int next_pulse()
{
	int length;
	static struct input_event iev, iev_next;
	do {
		int r;
		r = read(input, &iev_next, sizeof(iev));
		if (r <= 0) {
			perror("read input dev");
			fprintf(stderr, "r = %i, error reading\n", r);
			exit(-1);
		}
		length =
		    (iev_next.time.tv_sec - iev.time.tv_sec) * 1000000 +
		    iev_next.time.tv_usec - iev.time.tv_usec;

		iev = iev_next;
	} while (length < MIN_PULSE);

	return length;
}

static int compare_int(const void *a, const void *b)
{
	return *(int *) a - *(int *) b;
}

/* a bit of magic, to detects pulse lengths */
void get_pulse_lengths()
{
	float lengths[256];
	int i = 256;
	int p, count;
	float sum;

	printf("Hold a button...");
	fflush(stdout);
	while (--i >= 0)
		lengths[i] = next_pulse();
	qsort(lengths, 256, sizeof(int), compare_int);

	p = 0;
	i = 1;
	sum = lengths[0];
	count = 1;
	while (i < 256 && p < 10) {
		/* 1.3 is totally random */
		if (lengths[i - 1] * 1.3 < lengths[i]) {
			/* button presses, noise */
			if (count > 5)
				pulses[p++] = sum / count;
			sum = 0;
			count = 0;
		}
		sum += lengths[i];
		count++;
		i++;
	}
	if (p == 10)
		printf("ran out of pulse ranges!?\n");
	printf(" OK\nDetected:\n");
	for (i = 0; i < 10 && pulses[i] > 0.1; i++)
		printf("%d - %.0f\n", i, pulses[i]);
}

/* wait for next pulse, and output its number, 10 for too long, -1 for error */
int get_pulse()
{
	float length = next_pulse();
	int i;
	for (i = 0; i < 10 && pulses[i] > 0.1; i++) {
		float low, high;
		if (i == 0)
			low = pulses[i] / 5;
		else
			low = (pulses[i - 1] + pulses[i]) / 2;

		if (pulses[i + 1] < 0.1)
			high = pulses[i] * 5;
		else
			high = (pulses[i] + pulses[i + 1]) / 2;

		if (length >= low && length < high)
			return i;
	}
	/* long one, button repeat, next button etc. */
	if (length > pulses[i - 1])
		return 10;

	return -1;
}

static char last_code[CODE_LEN];
/* read code, and return it; returns 0 on ok*/
int get_code(char code[CODE_LEN])
{
	int i = 0, err = 0;
	while (i < CODE_LEN - 1) {
		int pulse = get_pulse();
		if (pulse == -1)
			err = -1;

		if (pulse == 10)
			break;

		code[i++] = pulse + '0';
	}
	code[i] = '\0';

	/* some remotes repeat with a short code */
	if (i < 10)
		strcpy(code, last_code);
	else if (err == 0)
		strcpy(last_code, code);

//	printf("debug: %s\n", code);
	return err;
}

int codes_match(char c1[CODE_LEN], char c2[CODE_LEN], char xor[CODE_LEN]){
	int i = 0;
	int match = 1;
	while (c1[i]) {
		if (c1[i] != c2[i]) {
			match = 0;
			break;
		}
		i++;
	}
	if (match)
		return 1;

	i = 0; match = 1;
	while (c1[i]) {
		if (c1[i] != (c2[i] ^ xor[i])) {
			match = 0;
			break;
		}
		i++;
	}
	return match;
}

int scan_remote(int fd) {
	char codes[MAX_KEYS][CODE_LEN];
	int n_codes;
	char xor_code[CODE_LEN] = { };
	int retryes = 5;
	int i = 0, bits = 0;

	char buf[CODE_LEN*2];
	int len;

	get_pulse_lengths();
	printf("Press same button a few times...");
	fflush(stdout);

	for (i=0; i<5; i++)
		while (get_code(codes[i]) != 0 && retryes > 0)
			retryes--;
	if (retryes <= 0) {
		fprintf(stderr, "I keep getting errors\n");
		return -1;
	} else
		printf(" OK\n");

	/* some remotes have two codes for each button, to differentiate
	   press and hold from many presses */
	for (i=1; i<5; i++)
		if (strcmp(codes[0], codes[i]) != 0) {
			int j;
			for (j=0; j<CODE_LEN && codes[0][j] && codes[i][j]; j++) {
				xor_code[j] = codes[0][j] ^ codes[i][j];
				bits += xor_code[j];
			}
		}

/*	for (i=0; i<5; i++) {
		printf("blah %s\n", codes[i]);
	}
*/
	if (bits > 5) {
		fprintf(stderr, "Weird, try debug mode (if it's already implemented :-) )\n");
	}

	printf("Now start pressing buttons in order you want them in your file.\n"
		"Repeatings are not counted. Stop by pressing the first button.\n");

	sleep(2);
	i = 0;
	printf("Button %i: ", i);
	fflush(stdout);
	do {
		retryes = 5;
		while (get_code(codes[i]) != 0 && retryes > 0)
			retryes--;
		if (retryes <= 0) {
			fprintf(stderr, "I keep getting errors\n");
			return -1;
		}
		if (i > 1 && codes_match(codes[0], codes[i], xor_code)) {
			printf("\nEnding\n");
			break;
		}
		if (i == 0 || !codes_match(codes[i-1], codes[i], xor_code)) {
			printf("%s\nButton %i: ", codes[i], i + 1);
			fflush(stdout);
			i++;
		}
	} while (1);
	n_codes = i;

	printf("Writing to file...\n");
	i = 0;
	/* write pulse lengths */
	do {
		len = sprintf(buf, "Pulse %i: %f\n", i, pulses[i]);
		if (write(fd, buf, len) == -1) {
			perror("remote file");
			return -1;
		}
		i++;
	} while (pulses[i] > 0.1);

	len = CODE_LEN - 1;
	while (len && xor_code[len] == '\0')
		len--;
	while (len >= 0)
		xor_code[len--] += '0';

	len = sprintf(buf, "XOR code: %s\n", xor_code);
	write(fd, buf, len);

	/* write keycodes */
	for (i=0; i<n_codes; i++) {
		len = sprintf(buf, "%s key_%i echo \"key %i\"\n", codes[i], i, i);
		write(fd, buf, len);
	}
	return 0;
}

/* ugly, unreliable, but should work :-) */
int read_remote_file(int fd)
{
	char *buf = malloc(1024*1024);
	char tmpbuf[010];
	char *tmp;
	int i;
	if (!buf)
		return -1;

	i = read(fd, buf, 1024*1024);
	buf[i] = '\0';

	for (i=0; i<10; i++) {
		sprintf(tmpbuf, "Pulse %i: ", i);
		tmp = strstr(buf, tmpbuf);
		if (!tmp)
			break;
		tmp = strchr(tmp, ':');
		if (!tmp)
			return -1;
		tmp += 2;
		if (sscanf(tmp, "%f", &pulses[i]) == 0) {
			pulses[i] = 0.0f;
			break;
		}
	}
	for (i=0; pulses[i] > 0.1f; i++)
		printf("debug %f\n", pulses[i]);
	tmp = strstr(buf, "XOR code:");
	if (!tmp)
		goto out;
	while (!isdigit(*tmp) && *tmp)
		tmp++;
	i = 0;
	while (isdigit(*tmp))
		xor_code[i++] = *tmp++;
	i = 0;
	while (i < MAX_KEYS) {
		tmp = strpbrk(tmp, "0123456789");
		if (!tmp)
			break;
		keys[i].code = tmp;
		tmp = strchr(tmp, ' ');
		if (!tmp)
			break;
		*tmp++ = '\0';

		/* discard key name */
		tmp = strchr(tmp, ' ');
		if (!tmp)
			break;
		*tmp++ = '\0';

		/* command */
		keys[i].cmd = tmp;
		tmp = strchr(tmp, '\n');
		if (!tmp)
			break;
		*tmp++ = '\0';
		i++;
	}
 out:
//	free(buf);
	return 0;
}

int main(int argc, char **argv)
{
	int scan = 0;
	char *file = NULL;
	char *dev = "/dev/input/remote";
	int remotefd;
	int c;
	while ((c = getopt(argc, argv, "f:d:s")) != -1)
		switch (c) {
		case 's':
			scan = 1;
			break;
		case 'f':
			file = optarg;
			break;
		case 'd':
			dev = optarg;
			break;
		}

	if (!file) {
		fprintf(stderr, "-f required\n");
		return -1;
	}

	input = open(dev, O_RDWR);
	if (input == -1) {
		perror(dev);
		return -1;
	}

	if (scan) {
		remotefd = open(file, O_CREAT | O_EXCL | O_WRONLY, 0600);
		if (remotefd == -1) {
			perror(file);
			close(input);
			return -1;
		}
		scan_remote(remotefd);
		close(remotefd);
	} else {
		remotefd = open(file, O_RDONLY);
		read_remote_file(remotefd);
		close(remotefd);
		while (1) {
			char code[CODE_LEN];
			int i;
			if (get_code(code) != 0)
				continue;
			for (i=0; keys[i].code; i++) {
				if (codes_match(code, keys[i].code, xor_code))
					system(keys[i].cmd);
			}
		}
	}

	close(input);
	return 0;
}
