#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <stdint.h>
#include <syslog.h>
#include <err.h>
#include <linux/serial.h>
#include "serial-util.h"
#include <string.h>
#include <ctype.h>

int hex = 0;
int dec = 0;
int screen = 0;
int unprocessed = 0;

int process(const uint8_t *buf, int len);

int main(int argc, char *argv[])
{
	char *device=strdup("/dev/serial/by-id/usb-FTDI_FT232R_"
                            "USB_UART_A6007wc5-if00-port0");
	int rate=500000;
	int fd;
	int getopt_index;
	char buf[1024];
	int len;
	int calibrate = 1;

	static struct option long_opts[] = {
		{ "device", required_argument, NULL, 'd' },
		{ "rate", required_argument, NULL, 'r' },
		{ "hex", no_argument, NULL, 'x' },
		{ "dec", no_argument, NULL, 'D' },
		{ "unprocessed", no_argument, NULL, 'u' },
		{ "no-calibrate", no_argument, NULL, 'n' },
		{ "screen", no_argument, NULL, 's' },
		{ "help", no_argument, NULL, 'h' },
		{ 0, 0, 0, 0 }
	};
	int help=0;
	char c;

	while ((c = getopt_long(argc, argv, "d:r:xDunsh?",
				long_opts, &getopt_index)) != -1) {
		switch(c)
		{
		case 'd':
			free(device);
			device = strdup(optarg);
			break;
		case 'r':
			rate = atoi(optarg);
			if(rate == 0)
				errx(1, "invalid rate: %s",optarg);
			break;
		case 'x':
			hex = 1;
			break;
		case 'D':
			dec = 1;
			break;
		case 'u':
			unprocessed = 1;
			break;
		case 'n':
			calibrate = 0;
			break;
		case 's':
			screen = 1;
			break;
		case 'h':
		case '?':
		default:
			help = 1;
			break;
		}
	}

	if (help) {
		fprintf(stderr, "Zoom Nilm Client\n");
		fprintf(stderr, "usage: %s [options]\n\n", *argv);
		fprintf(stderr, "  -d,  --device %-14s serial port\n", device);
		fprintf(stderr, "  -r,  --rate %-16d baud rate\n", rate);
		fprintf(stderr, "  -x,  --hex                   hex out\n");
		fprintf(stderr, "  -D,  --dec                   dec out\n");
		fprintf(stderr, "  -u,  --unprocessed           "
                        "dump raw unprocessed data\n");
		fprintf(stderr, "  -n,  --no-calibrate          "
                        "skip calibration routine\n");
		fprintf(stderr, "  -s,  --screen                "
                        "send \\r instead of \\n\n");
		fprintf(stderr, "  -h,  --help                  this cruft\n");
		return 1;
	}

	if ((fd = serial_open(device, rate)) == -1)
		err(1, "serial_open failed for %s", device);

	if (calibrate) {
		fprintf(stderr, "performing calibration\n");
		char c = 'c';
		write(fd, &c, 1);
		drain(fd);
	}

	len = 0;
	while (1) {
		int processed, n;
		n = read(fd, buf + len, sizeof(buf) - len);
		if (n <= 0)
			err(1, "read");
		len += n;
		processed = process((uint8_t *) buf, len);
		memmove(buf, buf + processed, len - processed);
		len -= processed;
	}

	return 1;
}

int process_adc_dac(const uint8_t *buf)
{
	uint16_t dac, tmp;
	int16_t adc;
	int overflow;

	/* data OK? */
	if ((buf[0] & 0xC0) != 0)
		return 0;

	/* extract */

	if (buf[0] & 0x10)
		overflow = 1;
	else
		overflow = 0;

	tmp = ((buf[0] & 0x0F) << 8) | buf[1];
	/* sign-extend ADC value */
	if (tmp & 0x0800)
		tmp |= 0xF000;
	else
		tmp &= ~0xF000;
	adc = (int16_t)tmp;

	dac = (buf[2] << 8) | buf[3];

	/* send it out */
	if (hex) {
		printf("%04x %03x", dac, adc & 0x0FFF);
	} else if (dec) {
		printf("%d %d", dac, adc);
	} else {
		printf("DAC: %5d   ADC: % 5d  Total: xxx", dac, adc);
		if (overflow)
			printf("   **** adc may have clamped");
	}

	if (screen)
		printf("\033[K\r");
	else
		printf("\n");

	return 1;
}

int process_calibration(const uint8_t *buf)
{
	float f = *(float *)buf;

	fprintf(stderr, "got calibration value: %f\n", f);

	return 1;
}

int process(const uint8_t *buf, int len)
{
	int n = 0;

	if (unprocessed) {
		n = write(fileno(stdout), buf, len);
		if (n >= 0)
			return n;
		return 0;
	}

	/* Process blocks */
 retry:
	for (; (n + 5) <= len; buf += 5, n += 5) {
		int ok = 0;
		switch (buf[0]) {
		case 0xA0:
			if (process_adc_dac(buf + 1)) ok = 1;
			break;
		case 0xA1:
			if (process_calibration(buf + 1)) ok = 1;
			break;
		default:
			break;
		}
		if (!ok) {
			/* badly formed data; eat one byte and retry */
			fprintf(stderr,"throwing away 0x%02x '%c'\n",
                                buf[0], isprint(buf[0]) ? buf[0] : '.');
			buf++;
			n++;
			goto retry;
		}
	}

	return n;
}

