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

void zero(int zoom);
void write_dac(int zoom, int dac);
void single_sweep(int zoom);
void calibrate(int zoom, int gpib);
void sweep_ota(int zoom, int gpib, int ota_sweep_count);
void hold_ota(int zoom, int gpib, int ota_cmd);

#define info(x...) fprintf(stderr,x)

int g_quit = 0;
void handle_sig(int sig) { g_quit = 1; }

int main(int argc, char *argv[])
{
	char *zoomdev=strdup("/dev/serial/by-id/usb-FTDI_FT232R_"
                             "USB_UART_A6007wc5-if00-port0");
	char *gpibdev=strdup("/dev/serial/by-id/usb-Prologix_Prologix_"
                             "GPIB-USB_Controller_PXQQY20G-if00-port0");
	int getopt_index;
	int zoom, gpib;
	int do_write = 0;
	int do_zero = 0;
	int write_cmd = 32768;
	int do_single_sweep = 0;
	int ota_opt = 0;
	int ota_sweep_count = 64;
	int ota_cmd = 32768;
	char *end;

	static struct option long_opts[] = {
		{ "zoom-device", required_argument, NULL, 'Z' },
		{ "gpib-device", required_argument, NULL, 'G' },
		{ "write-dac", required_argument, NULL, 'w' },
		{ "zero", no_argument, NULL, 'z' },
		{ "single-sweep", no_argument, NULL, 's' },
		{ "ota-sweep", required_argument, NULL, 'o' },
		{ "ota-hold", required_argument, NULL, 'l' },
		{ "help", no_argument, NULL, 'h' },
		{ 0, 0, 0, 0}
	};
	int help=0;
	char c;

	while ((c = getopt_long(argc, argv, "Z:G:szw:o:l:h?",
				long_opts, &getopt_index)) != -1) {
		switch(c)
		{
		case 'Z':
			free(zoomdev);
			zoomdev = strdup(optarg);
			break;
		case 'G':
			free(gpibdev);
			gpibdev = strdup(optarg);
			break;
		case 'w':
			do_write = 1;
			write_cmd = strtoul(optarg, &end, 0);
			if (*end) {
				fprintf(stderr, "bad number %s\n", optarg);
				help = 1;
			}
			break;
		case 's':
			do_single_sweep = 1;
			break;
		case 'z':
			do_zero = 1;
			break;
		case 'o':
			ota_opt = 1;
			ota_sweep_count = strtoul(optarg, &end, 0);
			if (*end) {
				fprintf(stderr, "bad number %s\n", optarg);
				help = 1;
			}
			break;
		case 'l':
			ota_opt = 2;
			ota_cmd = strtoul(optarg, &end, 0);
			if (*end) {
				fprintf(stderr, "bad number %s\n", optarg);
				help = 1;
			}
			break;
		case 'h':

		case '?':
		default:
			help = 1;
			break;
		}
	}

	if (help) {
		fprintf(stderr, "Zoom Nilm Calibration Tool\n");
		fprintf(stderr, "usage: %s [options]\n\n", *argv);
		fprintf(stderr, "  -Z,  --zoom-device %-14s "
                        "zoom NILM serial port\n", "/dev/xxx" /*zoomdev*/);
		fprintf(stderr, "  -G,  --gpib-device %-14s "
                        "GPIB serial port\n", "/dev/xxx" /*gpibdev*/);
		fprintf(stderr, "  -w,  --write-dac %-14d   "
                        "write one value to the DAC constantly\n", write_cmd);
		fprintf(stderr, "  -s,  --single-sweep      "
                        "do a single sweep on the PIC\n");
		fprintf(stderr, "  -z,  --zero              "
                        "set DAC value such that ADC input is centered\n");
		fprintf(stderr, "  -o,  --ota-sweep %-14d   "
                        "sweep OTA\n", ota_sweep_count);
		fprintf(stderr, "  -l,  --ota-hold %-14d    "
                        "hold OTA constant\n", ota_cmd);
		fprintf(stderr, "  -h,  --help              "
                        "this help\n");
		return 1;
	}

	signal(SIGINT, handle_sig);

	if ((zoom = serial_open(zoomdev, 115200)) == -1)
		err(1, "failed to open zoom device %s", zoomdev);

	if (do_write) {
		write_dac(zoom, write_cmd);
		close(zoom);
		return 0;
	}

	if (do_zero) {
		zero(zoom);
		close(zoom);
		return 0;
	}

	if (do_single_sweep) {
		single_sweep(zoom);
		close(zoom);
		return 0;
	}

	if ((gpib = serial_open(gpibdev, 9600)) == -1)
		err(1, "failed to open gpib device %s", gpibdev);

	switch (ota_opt) {
	case 1:
		sweep_ota(zoom, gpib, ota_sweep_count);
		break;
	case 2:
		hold_ota(zoom, gpib, ota_cmd);
		break;
	default:
		calibrate(zoom, gpib);
		break;
	}

	close(zoom);
	close(gpib);
	return 0;
}

void calibrate(int zoom, int gpib)
{
	double idesired, iactual;
	int i;
	int zero;
	int r = 0;
	int dac[ZOOM_SWEEP_COUNT];
	int adc[ZOOM_SWEEP_COUNT];

	info("Initializing Zoom NILM\n");
	if (zoom_init(zoom) < 0) goto fail;

	info("Zeroing\n");
	if (zoom_zero_start(zoom) < 0) goto fail;

	info("Initializing GPIB\n");
	if (gpib_init(gpib) < 0) goto fail;

	info("Initializing Keithley\n");
	if (gpib_addr(gpib, 24) < 0) goto fail;
	if (keithley_init(gpib) < 0) goto fail;
	if (keithley_current(gpib, 0) < 0) goto fail;
	if (isnan(keithley_read(gpib))) goto fail;

	info("Stop zeroing\n");
	if (zoom_zero_stop(zoom) < 0) goto fail;

	info("Sweeping\n");
	for (idesired = -1.0; idesired <= 1.0 && !g_quit; idesired += 0.10) {
//	for (idesired = -0.2; idesired <= 0.2 && !g_quit; idesired += 0.02) {
		info("Zeroing\n");
		if (zoom_zero_start(zoom) < 0) goto fail;

		info("Setting current: %.8f\n", idesired);
		keithley_current(gpib, idesired);
		usleep(100000);
		iactual = keithley_read(gpib);
		info("Actual current: %.8f\n", iactual);

		info("Stop zeroing\n");
		if ((zero = zoom_zero_stop(zoom)) < 0) goto fail;
		info("DAC zero point = %d\n", zero);

		info("Sweeping...\n");
		if ((r = zoom_sweep(zoom, dac, adc)) < 0) goto fail;

		info("Done\n");
		for (i = 0; i < ZOOM_SWEEP_COUNT; i++) {
			printf("%.8f %d %d\n", iactual, dac[i], adc[i]);
		}
	}

safecleanup:
	zoom_zero_start(zoom);
	keithley_off(gpib);
	usleep(50000);
	zoom_zero_stop(zoom);
	return;

fail:
	info("Failed (code %d)\n", r);
	goto safecleanup;
}

void write_dac(int zoom, int dac)
{
	char buf[128];

	if (zoom_init(zoom) < 0)
		errx(1, "init failed");

	if (dac < 0)
		dac = 0;
	if (dac > 65535)
		dac = 65535;
	while (!g_quit) {
		sprintf(buf, "v%04x", dac);
		if (safewrite(zoom, buf, 5) != 5)
			errx(1, "write failed");
		if (fdgets(buf, 128, zoom, 1000) == NULL)
			errx(1, "read timeout");
		chomp(buf);
		printf("%s\n", buf);
	}
}

void single_sweep(int zoom)
{
	int i, r;
	int dac[ZOOM_SWEEP_COUNT];
	int adc[ZOOM_SWEEP_COUNT];

	info("Initializing Zoom NILM\n");
	if (zoom_init_nozero(zoom) < 0) goto fail;

	info("Sweeping\n");
	if ((r = zoom_sweep(zoom, dac, adc)) < 0) goto fail;

	info("Done\n");
	for (i = 0; i < ZOOM_SWEEP_COUNT; i++) {
		printf("%d %d\n", dac[i], adc[i]);
	}

	return;

fail:
	info("Failed (code %d)\n", r);
	return;
}

void zero(int zoom)
{
	int r = 0;
	int dac;

	info("Initializing Zoom NILM\n");
	if (zoom_init(zoom) < 0) goto fail;

	info("Starting zeroing, ^C to stop\n");
	if (zoom_zero_start(zoom) < 0) goto fail;

	while (!g_quit)
		usleep(100000);

	info("Stop zeroing\n");
	if ((dac = zoom_zero_stop(zoom)) < 0) goto fail;
	info("DAC zero point = 0x%04x %d\n", dac, dac);

	info("Done\n");
	return;

fail:
	info("Failed (code %d)\n", r);
	return;
}

void sweep_ota(int zoom, int gpib, int ota_sweep_count)
{
	double iactual;
	int i,j;
	int r = 0;
	char buf[128];
	char zcmd[128];
	int mycmd = 0;
	int cmd_inc = 4096;

	info("Initializing Zoom NILM\n");
	if (zoom_init(zoom) < 0) goto fail;

	info("Initializing GPIB\n");
	if (gpib_init(gpib) < 0) goto fail;

	info("Initializing Keithley 2002 Multimeter\n");
	if (gpib_addr(gpib, 23) < 0) goto fail;
	if (keithley2002_init(gpib) < 0) goto fail;
	if (isnan(keithley2002_read(gpib))) goto fail;

	buf[0] = '0';
	if (safewrite(zoom, buf, 1) != 1)
		errx(1, "write failed");
        if (fdgets(buf, 128, zoom, 1000) == NULL)
		errx(1, "read timeout");
	drain(zoom);



	info("Sweeping OTA\n");
	for (i = 0; i <= 16 && !g_quit; i++){

		for(j = -2; j <= 2; j++){
			if(mycmd+j > 65535 || mycmd+j < 0)
				continue;

			if(sprintf(zcmd,"v%.4x",(mycmd +j)) < 5)
				errx(1, "fail hex conversion");

			if (safewrite(zoom, zcmd, 5) != 5)
				errx(1, "write failed");
			if (fdgets(buf, 128, zoom, 1000) == NULL)
				errx(1, "read timeout");

			usleep(100);

			iactual = keithley2002_read(gpib);

			printf("%d %.8f\n",(int)(mycmd + j), iactual);
		}

		mycmd += cmd_inc;

	}

	// return to zero
	buf[0] = '0';
	if (safewrite(zoom, buf, 1) != 1)
		errx(1, "write failed");
        if (fdgets(buf, 128, zoom, 1000) == NULL)
		errx(1, "read timeout");



safecleanup:
	return;

fail:
	info("Failed (code %d)\n", r);
	goto safecleanup;
}


void hold_ota(int zoom, int gpib, int ota_cmd)
{
	double iactual;
	double tx;
	int i;
	int r = 0;
	char buf[128];
	char zcmd[128];

	info("Initializing Zoom NILM\n");
	if (zoom_init(zoom) < 0) goto fail;

	info("Initializing GPIB\n");
	if (gpib_init(gpib) < 0) goto fail;

	info("Initializing Keithley 2002 Multimeter\n");
	if (gpib_addr(gpib, 23) < 0) goto fail;
	if (keithley2002_init2(gpib) < 0) goto fail;
	if (isnan(keithley2002_read(gpib))) goto fail;

	buf[0] = '0';
	if (safewrite(zoom, buf, 1) != 1)
		errx(1, "write failed");
        if (fdgets(buf, 128, zoom, 1000) == NULL)
		errx(1, "read timeout");
	drain(zoom);

	if(ota_cmd > 65535 || ota_cmd < 0)
		errx(1, "ota command out-of-range");

	if(sprintf(zcmd,"v%.4x",(ota_cmd)) < 5)
		errx(1, "fail hex conversion");

	if (safewrite(zoom, zcmd, 5) != 5)
		errx(1, "write failed");
	if (fdgets(buf, 128, zoom, 1000) == NULL)
		errx(1, "read timeout");

	usleep(100);

	info("Holding OTA\n");
	for (i = 0; i <= 500 && !g_quit; i++){

		iactual = keithley2002_read2(gpib, &tx);

		printf("%.12f %.12f\n", tx, iactual);

	}

	// return to zero
	buf[0] = '0';
	if (safewrite(zoom, buf, 1) != 1)
		errx(1, "write failed");
        if (fdgets(buf, 128, zoom, 1000) == NULL)
		errx(1, "read timeout");



safecleanup:
	return;

fail:
	info("Failed (code %d)\n", r);
	goto safecleanup;
}
