/*
 * Copyright © 2012 Keith Packard <keithp@keithp.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#include "sky_flash.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <getopt.h>
#include <unistd.h>
#include "cc.h"

static const struct option options[] = {
	{ .name = "tty", .has_arg = 1, .val = 'T' },
	{ .name = "device", .has_arg = 1, .val = 'D' },
	{ .name = "firmware", .has_arg = 1, .val = 'f' },
	{ .name = "query", .has_arg = 0, .val = 'q' },
	{ .name = "raw", .has_arg = 0, .val = 'r' },
	{ .name = "quiet", .has_arg = 0, .val = 'Q' },
	{ 0, 0, 0, 0},
};

static uint8_t	query_version[] = {
	0xa0, 0xa1, 0x00, 0x02, 0x02, 0x01, 0x03, 0x0d, 0x0a
};

static void
usage(char *program)
{
	fprintf(stderr,
		"usage: %s [--tty <tty-name>]\n"
		"          [--device <device-name>]\n"
		"          [--firmware <binary firmware file>]\n"
		"          [--query]\n"
		"          [--quiet]\n"
		"          [--raw]\n", program);
	exit(1);
}

static int
skytraq_expect(int fd, uint8_t want, int timeout) {
	int	c;

	c = skytraq_waitchar(fd, timeout);
	if (c < 0)
		return -1;
	if (c == want)
		return 1;
	return 0;
}

static int
skytraq_wait_reply(int fd, uint8_t reply, uint8_t *buf, uint8_t reply_len) {

	for(;;) {
		uint8_t	a, b;
		uint8_t	cksum_computed;
		int	len;
		switch (skytraq_expect(fd, 0xa0, 10000)) {
		case -1:
			return -1;
		case 0:
			continue;
		case 1:
			break;
		}
		switch (skytraq_expect(fd, 0xa1, 1000)) {
		case -1:
			return -1;
		case 0:
			continue;
		}
		a = skytraq_waitchar(fd, 1000);
		b = skytraq_waitchar(fd, 1000);
		switch (skytraq_expect(fd, reply, 1000)) {
		case -1:
			return -1;
		case 0:
			continue;
		}
		len = (a << 16) | b;
		if (len != reply_len)
			continue;
		*buf++ = reply;
		len--;
		cksum_computed = reply;
		while (len--) {
			a = skytraq_waitchar(fd, 1000);
			if (a < 0)
				return a;
			cksum_computed ^= a;
			*buf++ = a;
		}
		switch (skytraq_expect(fd, cksum_computed, 1000)) {
		case -1:
			return -1;
		case 0:
			continue;
		}
		switch (skytraq_expect(fd, 0x0d, 1000)) {
		case -1:
			return -1;
		case 0:
			continue;
		}
		switch (skytraq_expect(fd, 0x0a, 1000)) {
		case -1:
			return -1;
		case 0:
			continue;
		}
		break;
	}
	return 0;
}

int
main(int argc, char **argv)
{
	int	fd;
	int	ret;
	int	c;
	char	*tty = NULL;
	char	*device = NULL;
	char	*file = NULL;
	int	query = 0;
	int	raw = 0;

	while ((c = getopt_long(argc, argv, "T:D:l:f:qQr", options, NULL)) != -1) {
		switch (c) {
		case 'T':
			tty = optarg;
			break;
		case 'D':
			device = optarg;
			break;
		case 'f':
			file = optarg;
			break;
		case 'q':
			query = 1;
			break;
		case 'Q':
			skytraq_verbose = 0;
			break;
		case 'r':
			raw = 1;
			break;
		default:
			usage(argv[0]);
			break;
		}
	}

	if (!tty)
		tty = cc_usbdevs_find_by_arg(device, "TeleMetrum");
	if (!tty)
		tty = getenv("ALTOS_TTY");
	if (!tty)
		tty="/dev/ttyACM0";
	fd = skytraq_open(tty);
	if (fd < 0)
		exit(1);

	if (raw) {
		/* Set the baud rate to 115200 */
		skytraq_setcomm(fd, 115200);
		sleep(1);
		skytraq_setspeed(fd, 115200);
	} else {
		/* Connect TM to the device */
		skytraq_write(fd, "U\n", 2);
	}

	/* Wait for the device to stabilize after baud rate changes */
	for (c = 0; c < 6; c++) {
		skytraq_flush(fd);
		sleep(1);
	}

	if (query) {
		uint8_t	query_reply[14];

		uint8_t		software_type;
		uint32_t	kernel_version;
		uint32_t	odm_version;
		uint32_t	revision;

		skytraq_write(fd, query_version, 9);
		if (skytraq_wait_reply(fd, 0x80, query_reply, sizeof (query_reply)) != 0) {
			fprintf(stderr, "query reply failed\n");
			exit(1);
		}

#define i8(o)	query_reply[(o)-1]
#define i32(o)	((i8(o) << 24) | (i8(o+1) << 16) | (i8(o+2) << 8) | (i8(o+3)))
		software_type = i8(2);
		kernel_version = i32(3);
		odm_version = i32(7);
		revision = i32(11);
		skytraq_dbg_printf(0, "\n");
		printf ("Software Type %d. Kernel Version %d.%d.%d. ODM Version %d.%d.%d. Revision %d.%d.%d.\n",
			software_type,
			kernel_version >> 16 & 0xff,
			kernel_version >> 8 & 0xff,
			kernel_version >> 0 & 0xff,
			odm_version >> 16 & 0xff,
			odm_version >> 8 & 0xff,
			odm_version >> 0 & 0xff,
			revision >> 16 & 0xff,
			revision >> 8 & 0xff,
			revision >> 0 & 0xff);
		exit(0);
	}

	if (!file)
		usage(argv[0]);

	ret = skytraq_send_srec(fd, "srec_115200.bin");
	skytraq_dbg_printf (0, "srec ret %d\n", ret);
	if (ret < 0)
		exit(1);

	sleep(2);

//	ret = skytraq_send_bin(fd, "STI_01.04.42-01.10.23_4x_9600_Bin_20100901.bin");
	ret = skytraq_send_bin(fd, "STI_01.06.10-01.07.23_balloon_CRC_7082_9600_20120913.bin");

	printf ("bin ret %d\n", ret);
	if (ret < 0)
		exit(1);

	return 0;
}
