/*
 * Copyright (c) 2016-2021 Satlab A/S <satlab@satlab.com>
 * All rights reserved. Do not distribute without permission.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>

#include <util/colorprint.h>
#include <util/string.h>

#include <util/csp_client_commands.h>
#include <slash/slash.h>

#include <satlab/srs3.h>
#include <prop-client/prop_client_commands.h>

#include <btp/types.h>
#include <btp/cmd_btp.h>

/* Default node and timeout */
static uint8_t srs3_node = SL_SRS3_DEFAULT_ADDRESS;
static uint32_t srs3_timeout = 1000;

slash_command_group(srs3, "SRS-3 subsystem commands");

static int cmd_srs3_node(struct slash *slash)
{
	uint8_t node;
	char *endptr;

	if (slash->argc < 2) {
		printf("Current node address: %hhu\n", srs3_node);
		return 0;
	}

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

	srs3_node = node;

	return 0;
}
slash_command_sub(srs3, node, cmd_srs3_node, "[address]",
		  "Set/read node address");

static int cmd_srs3_timeout(struct slash *slash)
{
	uint32_t timeout;
	char *endptr;

	if (slash->argc < 2) {
		printf("Current timeout: %"PRIu32"\n", srs3_timeout);
		return 0;
	}

	timeout = (uint32_t)strtoul(slash->argv[1], &endptr, 10);
	if (*endptr != '\0')
		return SLASH_EUSAGE;

	srs3_timeout = timeout;

	return 0;
}
slash_command_sub(srs3, timeout, cmd_srs3_timeout, "[ms]",
		  "Set/read communication timeout");

slash_command_subgroup(srs3, gwdt, "Ground watchdog commands");

static int cmd_srs3_gwdt_reset(struct slash *slash)
{
	int ret;

	ret = sl_srs3_gwdt_reset(srs3_node, srs3_timeout, SL_SRS3_GWDT_RESET_KEY);
	if (ret < 0) {
		printf("Failed to reset GWDT: %d\n", ret);
		return SLASH_EIO;
	}

	return 0;
}
slash_command_subsub(srs3, gwdt, reset, cmd_srs3_gwdt_reset, NULL,
		     "Reset ground watchdog");

static int cmd_srs3_gwdt_counter(struct slash *slash)
{
	int ret;
	uint32_t counter;

	ret = sl_srs3_gwdt_counter(srs3_node, srs3_timeout, &counter);
	if (ret < 0) {
		printf("Failed to read GWDT: %d\n", ret);
		return SLASH_EIO;
	}

	printf("%"PRIu32" seconds remaining\n", counter);

	return 0;
}
slash_command_subsub(srs3, gwdt, counter, cmd_srs3_gwdt_counter, NULL,
		     "Read ground watchdog counter");

static int cmd_srs3_run(struct slash *slash)
{
	int ret;
	const char *cmd;

	if (slash->argc != 2)
		return SLASH_EUSAGE;

	cmd = slash->argv[1];

	ret = sl_srs3_shell_run(srs3_node, srs3_timeout, cmd);
	if (ret < 0) {
		printf("Failed to run command: %s\n", strerror(-ret));
		return SLASH_EIO;
	}

	return 0;
}
slash_command_sub(srs3, run, cmd_srs3_run, "<command>",
		  "Run command on remote system");

static int cmd_srs3_tm(struct slash *slash)
{
	int ret;
	struct sl_prop_query query;
	uint8_t txbuf[64], rxbuf[128];
	size_t chunksize = 200;

	uint32_t bootcount, gwdt_counter;
	uint16_t mv, mw;
	int16_t ma, temp;
	float pwr;
	uint32_t frames;
	char gwdt_string[20];

	ret = sl_prop_remote_query_create_static(&query, txbuf, sizeof(txbuf), rxbuf, sizeof(rxbuf));
	if (ret < 0) {
		printf("Failed to create query\n");
		return SLASH_ENOMEM;
	}

	/* Set chunksize */
	query.chunksize = chunksize;

	/* Fetch entire TM group */
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_TM_GROUP);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_SYS_BOOTCOUNT);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_SYS_GWDT_COUNTER);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_TX_FRAMES);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_TX_PWR_FWD);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_RX_FRAMES);
	ret += sl_prop_remote_query_get(&query, SL_SRS3_PROP_RX_RSSI);

	/* Check for errors */
	if (ret < 0) {
		printf("Failed to add properties to query\n");
		return SLASH_ENOMEM;
	}

	/* Send query message */
	ret = sl_prop_remote_query_send(&query, srs3_node, SL_PROP_DEFAULT_PORT, srs3_timeout);
	if (ret < 0) {
		printf("Failed to send query\n");
		return SLASH_ENOMEM;
	}

	/* System info */
	cprintf(COLOR_BOLD, "System Info:\n");

	printf("%-15s", "Bootcount");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_SYS_BOOTCOUNT, &bootcount, sizeof(bootcount));
	cprintf(COLOR_GREEN, " %7"PRIu32, bootcount);
	printf(" boots\n");
	printf("%-15s", "GWDT counter");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_SYS_GWDT_COUNTER, &gwdt_counter, sizeof(gwdt_counter));
	sec_to_time_string(gwdt_counter, gwdt_string, sizeof(gwdt_string));
	cprintf(COLOR_GREEN, "%s", gwdt_string);
	printf(" (%"PRIu32" seconds)\n", gwdt_counter);

	/* Power consumption */
	cprintf(COLOR_BOLD, "\nPower Rails:\n");

	printf("%-13s", "VIN");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_VOLT_VIN, &mv, sizeof(mv));
	cprintf(COLOR_GREEN, " %9.2f", (float)mv);
	printf(" mV ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_CUR_VIN, &ma, sizeof(ma));
	cprintf(COLOR_GREEN, " %7.2f", (float)ma);
	printf(" mA ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_POWER_VIN, &mw, sizeof(mw));
	cprintf(COLOR_GREEN, " %7.2f", (float)mw);
	printf(" mW\n");

	printf("%-13s", "VREG");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_VOLT_VREG, &mv, sizeof(mv));
	cprintf(COLOR_GREEN, " %9.2f", (float)mv);
	printf(" mV ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_CUR_VREG, &ma, sizeof(ma));
	cprintf(COLOR_GREEN, " %7.2f", (float)ma);
	printf(" mA ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_POWER_VREG, &mw, sizeof(mw));
	cprintf(COLOR_GREEN, " %7.2f", (float)mw);
	printf(" mW\n");

	printf("%-13s", "3V3");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_VOLT_3V3, &mv, sizeof(mv));
	cprintf(COLOR_GREEN, " %9.2f", (float)mv);
	printf(" mV ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_CUR_3V3, &ma, sizeof(ma));
	cprintf(COLOR_GREEN, " %7.2f", (float)ma);
	printf(" mA ");
	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_POWER_3V3, &mw, sizeof(mw));
	cprintf(COLOR_GREEN, " %7.2f", (float)mw);
	printf(" mW\n");

	/* Temperatures */
	cprintf(COLOR_BOLD, "\nTemperature Sensors:\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_TEMP_MCU, &temp, sizeof(temp));
	printf("%-15s", "MCU");
	cprintf(COLOR_GREEN, " %7.2f", temp/100.0);
	printf(" C\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_TEMP_POWER, &temp, sizeof(temp));
	printf("%-15s", "Power");
	cprintf(COLOR_GREEN, " %7.2f", temp/100.0);
	printf(" C\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_TEMP_LNA, &temp, sizeof(temp));
	printf("%-15s", "LNA");
	cprintf(COLOR_GREEN, " %7.2f", temp/100.0);
	printf(" C\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TM_TEMP_PA, &temp, sizeof(temp));
	printf("%-15s", "PA");
	cprintf(COLOR_GREEN, " %7.2f", temp/100.0);
	printf(" C\n");

	/* Transmit */
	cprintf(COLOR_BOLD, "\nTransmitter:\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TX_FRAMES, &frames, sizeof(frames));
	printf("%-15s", "Frames");
	cprintf(COLOR_GREEN, " %7"PRIu32, frames);
	printf(" frames\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_TX_PWR_FWD, &pwr, sizeof(pwr));
	printf("%-15s", "Forward power");
	cprintf(COLOR_GREEN, " %7.2f", pwr);
	printf(" dBm\n");

	/* Receive */
	cprintf(COLOR_BOLD, "\nReceiver:\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_RX_FRAMES, &frames, sizeof(frames));
	printf("%-15s", "Frames");
	cprintf(COLOR_GREEN, " %7"PRIu32, frames);
	printf(" frames\n");

	sl_prop_remote_query_get_reply(&query, SL_SRS3_PROP_RX_RSSI, &pwr, sizeof(pwr));
	printf("%-15s", "Last RSSI");
	cprintf(COLOR_GREEN, " %7.2f", pwr);
	printf(" dBm\n");

	sl_prop_remote_query_destroy(&query);

	return 0;
}
slash_command_sub(srs3, tm, cmd_srs3_tm, NULL,
		  "Show telemetry data");

/* Bootloader commands */
slash_command_subgroup(srs3, boot, "Bootloader commands");

static int cmd_srs3_boot_flash(struct slash *slash)
{
	int ret;
	const char *filename;
	uint32_t crc;

	if (slash->argc < 2)
		return SLASH_EUSAGE;

	filename = slash->argv[1];

	printf("Flashing \"%s\" to alternate boot partition\n", filename);

	ret = sl_srs3_bootloader_flash_alternate(srs3_node, srs3_timeout, filename, &crc);
	if (ret < 0) {
		printf("Failed to flash image: %s\n", strerror(-ret));
		return SLASH_EIO;
	}

	printf("Flashed image, checksum = %08"PRIx32"\n", crc);

	return 0;
}
slash_command_subsub(srs3, boot, flash, cmd_srs3_boot_flash, "<filename>",
		     "Flash image from NOR-flash to alternate partition");

static int cmd_srs3_boot_erase(struct slash *slash)
{
	int ret;

	ret = sl_srs3_bootloader_erase_alternate(srs3_node, srs3_timeout);
	if (ret < 0) {
		printf("Failed to erase image: %s\n", strerror(-ret));
		return SLASH_EIO;
	}

	printf("Success - alternate image was erased\n");

	return 0;
}
slash_command_subsub(srs3, boot, erase, cmd_srs3_boot_erase,
		     NULL, "Erase alternate partition");

static int cmd_srs3_boot_verify(struct slash *slash)
{
	int ret;
	uint8_t partition = 1;
	uint32_t size, checksum;

	if (slash->argc > 1) {
		if (!strcmp(slash->argv[1], "primary")) {
			partition = 0;
		} else if (!strcmp(slash->argv[1], "alternate")) {
			partition = 1;
		} else {
			return SLASH_EUSAGE;
		}
	}

	ret = sl_srs3_bootloader_verify(srs3_node, srs3_timeout, partition, &size, &checksum);
	if (ret < 0) {
		printf("Failed to verify image: %s\n", strerror(-ret));
		return SLASH_EIO;
	}

	printf("%s image is %s! (size %u, checksum %08X)\n",
	       partition == 0 ? "Primary" : "Alternate",
	       ret == 0 ? "OK" : "corrupt",
	       size, checksum);

	return ret == 0 ? 0 : SLASH_EIO;
}
slash_command_subsub(srs3, boot, verify, cmd_srs3_boot_verify,
		     "[partition]", "Verify image in partition");

static int cmd_srs3_boot_set(struct slash *slash)
{
	int ret;
	uint8_t boots = 1;
	unsigned long boots_parsed;
	char *endptr;

	if (slash->argc > 1) {
		boots_parsed = strtoul(slash->argv[1], &endptr, 10);
		if (*endptr != '\0')
			return SLASH_EUSAGE;

		if (boots_parsed > 255) {
			fprintf(stderr, "Maximum number of boots is 255\n");
			return SLASH_EUSAGE;
		}

		boots = boots_parsed;
	}

	printf("Setting alternate firmware boot for %hhu boots\n", boots);

	ret = sl_srs3_bootloader_set_alternate(srs3_node, srs3_timeout, boots);
	if (ret < 0) {
		fprintf(stderr, "Failed to set boot image: %s\n", strerror(-ret));
		return SLASH_EIO;
	}

	printf("Success - Reboot to boot alternate image\n");

	return 0;
}
slash_command_subsub(srs3, boot, set, cmd_srs3_boot_set,
		     "[boots]", "Set number of boots on alternate partition");

/* CSP commands */
SL_CSP_CLIENT_COMMANDS_GENERATE(srs3, &srs3_node, &srs3_timeout);

/* Properties commands */
SL_PROP_CLIENT_COMMANDS_GENERATE(srs3, &srs3_node, SL_PROP_DEFAULT_PORT, &srs3_timeout, &srs3_props);

/* BTP commands */
SL_BTP_CLIENT_COMMANDS_GENERATE(srs3, &srs3_node, BTP_DEFAULT_PORT);
