/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2017 Satlab ApS <satlab@satlab.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>

#include <csp/csp.h>
#include <csp/csp_cmp.h>
#include <csp/csp_endian.h>
#include <slash/slash.h>
#include <util/timer.h>

#include <util/csp_client_commands.h>

#ifdef CSP_USE_CSPERF
#include <csp/csp_perf.h>
#endif /* CSP_USE_CSPERF */

slash_command_group(csp, "CSP commands");

static int parse_flags(const char *flagstr, uint8_t *flags)
{
	while (*flagstr) {
		switch (*flagstr) {
		case 'h':
			*flags |= CSP_O_HMAC;
			break;
		case 'x':
			*flags |= CSP_O_XTEA;
			break;
		case 'c':
			*flags |= CSP_O_CRC32;
			break;
		case 'r':
			*flags |= CSP_O_RDP;
			break;
		default:
			printf("unknown option flag '%c'\n", *flagstr);
			return -EINVAL;
		}
		flagstr++;
	}

	return 0;
}

int cmd_csp_ping(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int c, i, ret;
	char *endptr;

	uint32_t timeout = 1000;
	uint8_t node;
	unsigned int sent, lost, received = 0, size = 1, count = 1;
	uint8_t flags = CSP_O_NONE;
	bool quiet = false, report = false;
	uint64_t start, stop;
	unsigned int interval = 1000;
	float rtt, min = 0, max = 0, avg = 0;
	csp_conn_t *conn;
	csp_packet_t *packet;

	while ((c = slash_getopt(slash, "c:t:s:o:i:qr")) != -1) {
		switch (c) {
		case 'c':
			count = atoi(slash->optarg);
			break;
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case 'i':
			interval = atoi(slash->optarg);
			break;
		case 's':
			size = atoi(slash->optarg);
			break;
		case 'o':
			ret = parse_flags(slash->optarg, &flags);
			if (ret < 0)
				return SLASH_EUSAGE;
			break;
		case 'q':
			quiet = true;
			break;
		case 'r':
			report = true;
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc - slash->optind != 1)
			return SLASH_EUSAGE;

		node = (uint8_t)strtoul(slash->argv[slash->optind], &endptr, 10);
		if (*endptr != '\0' || node > 30)
			return SLASH_EUSAGE;
	}

	/*
	 * csp_ping has multiple problems, including low resolution
	 * timers and fixed packet contents, so just create the ping
	 * packets manually.
	 */

	conn = csp_connect(CSP_PRIO_NORM, node, CSP_PING, timeout, flags);
	if (!conn) {
		printf("error: could not connect to host\n");
		return SLASH_ENOMEM;
	}

	for (sent = 0; sent < count || count < 1; sent++) {
		/*
		 * Create a buffer and fill the data with the low bits of the
		 * sent frame value so we can check the return packet.
		 */
		packet = csp_buffer_get(size);
		if (!packet) {
			printf("error: no CSP buffer available\n");
			break;
		}

		packet->length = size;
		memset(packet->data, (sent & 0xff), size);

		/* Start the timer here to measure the actual RTT */
		start = timer_getmonotonic();
		
		if (!csp_send(conn, packet, 0)) {
			csp_buffer_free(packet);
			printf("error: could not send ping packet\n");
			break;
		}

		packet = csp_read(conn, timeout);
		stop = timer_getmonotonic();
		ret = 0;

		if (packet) {
			/* Check that packet was echoed */
			if (packet->length != size)
				ret = -2;
			for (i = 0; i < packet->length; i++) {
				if (packet->data[i] != (sent & 0xff))
					ret = -2;
			}
			csp_buffer_free(packet);
		} else {
			ret = -1;
		}

		if (ret < 0) {
			if (!quiet) {
				if (ret == -2) {
					printf("error: malformed reply data\n");
				} else {
					printf("error: timeout\n");
				}
			}
		} else {
			received++;

			rtt = (stop - start) / 1e6;
			if (rtt < min || min == 0)
				min = rtt;
			if (rtt > max || max == 0)
				max = rtt;

			/* Cumulative average */
			avg = avg + (rtt - avg) / (sent + 1);

			if (!quiet)
				printf("Reply in %.3f ms\n", rtt);
		}

		if (sent + 1 < count || count < 1) {
			ret = slash_wait_interruptible(slash, interval);
			if (ret > 0) {
				sent++;
				break;
			}
		}
	}

	csp_close(conn);

	if (report) {
		lost = sent - received;
		printf("Sent: %u Received: %u (%.1f%%) Lost: %u (%.1f%%)\n",
		       sent, received, received * 100.0 / sent, lost, lost * 100.0 / sent);
		printf("RTT: min=%.3f max=%.3f avg=%.3f\n", min, max, avg);
	}

	return 0;
}

int cmd_csp_reboot(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	uint8_t node;
	char *endptr;

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc < 2)
			return SLASH_EUSAGE;

		node = (uint8_t)strtoul(slash->argv[1], &endptr, 10);
		if (*endptr != '\0' || node > 30)
			return SLASH_EUSAGE;
	}

	csp_reboot(node);

	return 0;
}

int cmd_csp_shutdown(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	uint8_t node;
	char *endptr;

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc < 2)
			return SLASH_EUSAGE;

		node = (uint8_t)strtoul(slash->argv[1], &endptr, 10);
		if (*endptr != '\0' || node > 30)
			return SLASH_EUSAGE;
	}

	csp_shutdown(node);

	return 0;
}

int cmd_csp_ident(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int c, ret;
	uint8_t node = csp_get_address();
	uint32_t timeout = 1000;
	struct csp_cmp_message msg;

	while ((c = slash_getopt(slash, "t:")) != -1) {
		switch (c) {
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc - slash->optind > 0)
			node = atoi(slash->argv[slash->optind]);
	}

	ret = csp_cmp_ident(node, timeout, &msg);
	if (ret < 0) {
		printf("error: timeout\n");
		return SLASH_EIO;
	}

	printf("Hostname:   %s\n", msg.ident.hostname);
	printf("Model:      %s\n", msg.ident.model);
	printf("Revision:   %s\n", msg.ident.revision);
	printf("Build date: %s\n", msg.ident.date);
	printf("Build time: %s\n", msg.ident.time);

	return 0;
}

int cmd_csp_clock(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int c, ret;
	uint8_t node = csp_get_address();
	uint32_t timeout = 1000;
	uint64_t now;
	time_t t;
	struct csp_cmp_message msg;

	while ((c = slash_getopt(slash, "t:")) != -1) {
		switch (c) {
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
		/* Pretend node was passed as option */
		slash->optind--;
	} else {
		if (slash->argc - slash->optind > 0)
			node = atoi(slash->argv[slash->optind]);
	}

	msg.clock.tv_sec = 0;
	msg.clock.tv_nsec = 0;
	if (slash->argc - slash->optind > 1) {
		if (!strcmp(slash->argv[slash->optind + 1], "now")) {
			now = timer_getrealtime();
			msg.clock.tv_sec = csp_hton32(now / NSEC_PER_SEC);
			msg.clock.tv_nsec = csp_hton32(now % NSEC_PER_SEC);
		} else {
			msg.clock.tv_sec = csp_hton32(atoi(slash->argv[slash->optind + 1]));
			if (slash->argc - slash->optind > 2)
				msg.clock.tv_nsec = csp_hton32(atoi(slash->argv[slash->optind + 2]));
		}
	}

	ret = csp_cmp_clock(node, timeout, &msg);
	if (ret < 0) {
		printf("error: timeout\n");
		return SLASH_EIO;
	}

	msg.clock.tv_sec = csp_ntoh32(msg.clock.tv_sec);
	msg.clock.tv_nsec = csp_ntoh32(msg.clock.tv_nsec);
	t = msg.clock.tv_sec;

	printf("Time: %s", ctime(&t));

	return 0;
}

int cmd_csp_buffree(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int ret, c;
	uint8_t node = csp_get_address();
	uint32_t timeout = 1000;
	uint32_t size = 0;

	while ((c = slash_getopt(slash, "t:")) != -1) {
		switch (c) {
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc - slash->optind > 0)
			node = atoi(slash->argv[slash->optind]);
	}

	/* csp_buf_free() insists on printing the reply... */
	ret = csp_transaction(CSP_PRIO_NORM, node, CSP_BUF_FREE, timeout, NULL, 0, &size, sizeof(size));
	if (!ret) {
		printf("error: timeout\n");
		return SLASH_EIO;
	}

	size = csp_ntoh32(size);
	printf("%"PRIu32"\n", size);

	return 0;
}

int cmd_csp_memfree(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int ret, c;
	uint8_t node = csp_get_address();
	uint32_t timeout = 1000;
	uint32_t size = 0;

	while ((c = slash_getopt(slash, "t:")) != -1) {
		switch (c) {
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc - slash->optind > 0)
			node = atoi(slash->argv[slash->optind]);
	}

	/* csp_memfree() insists on printing the reply... */
	ret = csp_transaction(CSP_PRIO_NORM, node, CSP_MEMFREE, timeout, NULL, 0, &size, sizeof(size));
	if (!ret) {
		printf("error: timeout\n");
		return SLASH_EIO;
	}

	size = csp_ntoh32(size);
	printf("%"PRIu32" bytes\n", size);

	return 0;
}

int cmd_csp_uptime(struct slash *slash)
{
	struct sl_csp_client_command_context *ctx = slash->context;

	int ret, c;
	uint8_t node = csp_get_address();
	uint32_t timeout = 1000;
	uint32_t uptime = 0;

	while ((c = slash_getopt(slash, "t:")) != -1) {
		switch (c) {
		case 't':
			timeout = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	if (ctx) {
		node = *ctx->node;
	} else {
		if (slash->argc - slash->optind > 0)
			node = atoi(slash->argv[slash->optind]);
	}

	/* csp_uptime() insists on printing the reply... */
	ret = csp_transaction(CSP_PRIO_NORM, node, CSP_UPTIME, timeout, NULL, 0, &uptime, sizeof(uptime));
	if (!ret) {
		printf("error: timeout\n");
		return SLASH_EIO;
	}

	uptime = csp_ntoh32(uptime);
	printf("%"PRIu32" seconds\n", uptime);

	return 0;
}

/* Generate generic 'csp ...' commands */
SL_CSP_CLIENT_COMMANDS_GENERATE_GENERIC(csp, NULL, "<node>", "[node]");

slash_command_subgroup(csp, route, "Routing table commands");

static int cmd_csp_route_show(struct slash *slash)
{
	csp_rtable_print();

	return 0;
}
slash_command_subsub(csp, route, show, cmd_csp_route_show,
		     NULL, "Show routing table");

static int cmd_csp_if(struct slash *slash)
{
	csp_iflist_print();

	return 0;
}
slash_command_sub(csp, if, cmd_csp_if, NULL,
		  "Show interfaces and stats");

#ifdef CSP_USE_CSPERF
static uint8_t parse_flags(const char *flagstr)
{
	uint8_t flags = CSP_O_NONE;

	if (strchr(flagstr, 'r'))
		flags |= CSP_O_RDP;
	if (strchr(flagstr, 'c'))
		flags |= CSP_O_CRC32;
	if (strchr(flagstr, 'h'))
		flags |= CSP_O_HMAC;
	if (strchr(flagstr, 'x'))
		flags |= CSP_O_XTEA;

	return flags;
}

static int perf_cb(struct csp_perf_config *conf, void *arg)
{
	struct slash *slash = arg;

	if (slash_wait_interruptible(slash, 0) == '\x03')
		csp_perf_stop(conf);

	return 0;
}

static int cmd_csp_perf(struct slash *slash)
{
	int ret, c;
	struct csp_perf_config perf_conf;

	int server_mode = 0;
	int client_mode = 0;

	csp_perf_set_defaults(&perf_conf);
	perf_conf.cb = perf_cb;
	perf_conf.cb_arg = slash;

	while ((c = slash_getopt(slash, "b:c:i:o:p:st:T:hz:")) != -1) {
		switch (c) {
		case 'b':
			perf_conf.bandwidth = atoi(slash->optarg);
			break;
		case 'c':
			client_mode = 1;
			perf_conf.server = atoi(slash->optarg);
			break;
		case 'i':
			perf_conf.update_interval = atoi(slash->optarg);
			break;
		case 'p':
			perf_conf.port = atoi(slash->optarg);
			break;
		case 's':
			server_mode = 1;
			break;
		case 't':
			perf_conf.runtime = atoi(slash->optarg);
			break;
		case 'T':
			perf_conf.timeout_ms = atoi(slash->optarg);
			break;
		case 'o':
			perf_conf.flags = parse_flags(slash->optarg);
			break;
		case 'h':
			return SLASH_EUSAGE;
		case 'z':
			perf_conf.data_size = atoi(slash->optarg);
			break;
		case '?':
		default:
			return SLASH_EUSAGE;
		}
	}

	/* Either server or client must be selected */
	if (!(server_mode ^ client_mode))
		return SLASH_EUSAGE;

	if (server_mode) {
		ret = csp_perf_server(&perf_conf);
	} else {
		ret = csp_perf_client(&perf_conf);
	}

	return ret ? SLASH_EIO : 0;
}
slash_command_sub(csp, perf, cmd_csp_perf, "[-s|-c host]",
		  "CSP performance tool");
#endif /* CSP_USE_CSPERF */
