/**
  ******************************************************************************
  * @file	commands.c
  * @brief  command.c file, the command interpreter, a generic one
 ******************************************************************************
  * @author: Thomas Kuschel KW4NZ
  * created 2022-06-04
  *
 ******************************************************************************/
#include "main.h"
#include "at1_defines.h"
#include "cmsis_os.h"
#include <stdint.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h> //strchr
#include <stdlib.h> //bsearch
#include "helper.h" //ltrim
#include "si5351.h"

#include "commands.h"

/*
 typedef struct command_ctx_s {
	osThreadId_t	thread_id;
	osMutexId_t		mutex;
	char			*last_cmd;
	size_t			last_cmd_size;
} command_ctx_t;
*/

typedef enum {
	CMD_NO_FLAG = 0x00,
	CMD_HIDDEN  = 0x01,		/*!< Not shown in help but listed with "help all" or with "?" */
	CMD_SECRET  = 0x02,		/*!< Not shown in help - hidden and secret commands */
	CMD_ADMIN   = 0x04,		/*!< Only an administrator or privileged person can use this command */
	CMD_NOT_IMPLEMENTED = 0x80	/*!< A command which is not implemented yet */
} cmd_flags_t;

#define CMD_LENGTH 		16
#define MAXLINELENGTH	40

typedef struct {
	const char name[CMD_LENGTH];		/*!< command name */
	const uint8_t significantlength;	/*!< length of command for the compare function */
	const uint8_t flag;					/*!< command flags, i.e. CMD_HIDDEN, CMD_SECRET, CMD_ADMIN, etc. */
	int (*func)(/*command_ctx_t *ctx,*/ char *args);
} cmd_table_t;

// the entries must be in alphabetical order due to the binary search
static const cmd_table_t cmd_table[] = {
	{"admin",    2, CMD_ADMIN,   do_test },
	{"altitude", 1, CMD_NO_FLAG, do_test },
	{"date",     1, CMD_NO_FLAG, do_time },
	{"devid",	 3, CMD_HIDDEN,  do_devid },
	{"help",     1, CMD_NO_FLAG, do_help },
	{"hidden",   2, CMD_HIDDEN,  do_test },
	{"info",     1, CMD_NO_FLAG, do_info },
	{"sinfo",   1, CMD_SECRET,  do_sinfo },

	{"time",     1, CMD_NO_FLAG, do_time },
};

int do_test(char *args) {
	(void) *args;
	printf("not implemented.\n");
	return 0;
}

/*!
 * @brief compares two strings up to n characters, and next gets the pointer to the 2nd word in the string s1
 *
 * @pre s1, s2 and n not allowed to be 0
 *
 * @param s1
 * @param next
 * @param s2
 * @param n
 * @return int if the s1 and s2 are the same till n characters its 0, if s1 > s2 it becomes 1, else -1
 */
int cmdncasecmp(const char *s1, char **next, const char *s2, size_t n)
{
	if (n == 0 || s1 == NULL || s2 == NULL) {
		if (next) *next = NULL;
		if (s2 == NULL && s1 != NULL) return 1;
		if (s1 == NULL && s2 != NULL) return -1;
		return 0;
	}
	int ret;
	while ((ret = ((unsigned char)tolower(*s1) - (unsigned char)tolower(*s2))) == 0
		   && *s1 != ' ' && *s1 && *s2) {
		if (n) n--;
		s1++;
		s2++;
	}
	if (next) *next = (char *) s1;
	if (n == 0 && (*s1 == ' ' || !*s1))
		return 0;
	return ret;
}

static int compar(const void *a, const void *b)
{
	cmd_table_t * cmd = (cmd_table_t *)b;
	return cmdncasecmp(a, NULL, cmd->name, cmd->significantlength);
}


int cmd_interpreter(char * cmdline) {
	char * ptr;
	const cmd_table_t *entry;
	int rv = 0;

	ptr = strchr(cmdline, ' ');
	if (ptr) {
		*ptr = '\0';
		ptr++;
		ptr = ltrim(ptr);
	}
	entry = bsearch(cmdline, cmd_table, sizeof(cmd_table)/sizeof(cmd_table[0]), sizeof(cmd_table[0]), compar);
	if (entry == NULL) {
		printf("Command \"%s\" not found.\n", cmdline);
	} else {
		printf("Command \"%s\":\n", entry->name);
		if (*ptr != '\0')
			printf("called with parameter: \"%s\"\n", ptr);
		rv = entry->func(ptr);
	}
	return rv;
}

int do_devid(char *args) {

	(void)args;
	union myUnion {
	    uint32_t myint[2];
	    char    mychar[8];
	} my;
	//char lot[sizeof(my) + 1];

	printf("DEVID: 0x%08lx\n", HAL_GetDEVID());
	printf("REVID: 0x%08lx\n", HAL_GetREVID());
	printf("UID: 0x%08lx %08lx %08lx\n", HAL_GetUIDw2(), HAL_GetUIDw1(), HAL_GetUIDw0());

	my.myint[1] = HAL_GetUIDw2();
	my.myint[0] = HAL_GetUIDw1();
	//memcpy(lot, my.mychar, sizeof(my.mychar));
	//lot[sizeof(my)] = '\0';

	printf("=> Lot Number: %.8s\n", my.mychar);
	printf("=> Wafer Number: %u\n", (uint8_t)(HAL_GetUIDw1() & 0xff));
	return 0;
}

int do_info(char *args) {
	(void)args;
	print_system_info();
	return 0;
}

int do_sinfo(char *args) {
	int rv = 0;
	(void)args;
	si5351_inst_t inst = NULL;
	rv = si5351_get_instance(&inst);
	printf("%d Si5351 instance%s found.\n", rv, (rv==1)?"":"s");
	while (rv > 0) {
		printf("Si5351 Handle: 0x%08lx\n", (uint32_t)inst);

		rv = si5351_get_instance(&inst);
	}
	return rv;
}

int do_time(char *args) {

	HAL_StatusTypeDef status;
	RTC_TimeTypeDef stime = {0};
	RTC_DateTypeDef sdate = {0};
	int rv = 0;

	if (args) {

		struct _tm_ tp;
		rv = strtotime (args, &tp);
		if (rv > 0) {
			printf("%02d%02d-%02d-%02d %02d:%02d:%02d\n", (tp.year < DATE_COMPILE_YEAR) ? DATE_COMPILE_CENTURY + 1 : DATE_COMPILE_CENTURY, \
				tp.year, tp.mon, tp.day, tp.hour, tp.min, tp.sec);
		  /** Initialize RTC and set the Time and Date */
			stime.Hours = (uint8_t)tp.hour;
			stime.Minutes = (uint8_t)tp.min;
			stime.Seconds = (uint8_t)tp.sec;
			stime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
			stime.StoreOperation = RTC_STOREOPERATION_RESET;
			if (HAL_RTC_SetTime(&hrtc, &stime, RTC_FORMAT_BIN) != HAL_OK) {
				Error_Handler();
			}
			sdate.WeekDay = RTC_WEEKDAY_MONDAY;
			sdate.Month = (uint8_t)tp.mon;
			sdate.Date = (uint8_t)tp.day;
			sdate.Year = (uint8_t)tp.year;
			//uint8_t weekday;
			//weekday = dayofweek((tp.year < DATE_COMPILE_YEAR) ? DATE_COMPILE_CENTURY + 1 : DATE_COMPILE_CENTURY, sdate.Year, sdate.Month, sdate.Date);
			//printf("Wochentag: %d\n", weekday);
			if (HAL_RTC_SetDate(&hrtc, &sdate, RTC_FORMAT_BIN) != HAL_OK) {
				Error_Handler();
			}
		}
	}
	status = HAL_RTC_GetTime(&hrtc, &stime, RTC_FORMAT_BIN);
	if (status != HAL_OK)
		puts("HAL_RTC_GetTime problem...");
	status = HAL_RTC_GetDate(&hrtc, &sdate, RTC_FORMAT_BIN);
	printf("%02d%02d-%02d-%02d %02d:%02d:%02d\n", (sdate.Year < DATE_COMPILE_YEAR) ? DATE_COMPILE_CENTURY + 1 : DATE_COMPILE_CENTURY, \
			sdate.Year, sdate.Month, sdate.Date, stime.Hours, stime.Minutes, stime.Seconds);
	return rv;
}

int do_help(char *args) {

	int all = cmdncasecmp(args, NULL, "all", 1);
	const cmd_table_t *cmd = cmd_table;
	int j = 0;
	int n = 0;
	int rv = 0;

	puts("Commands:");

	for (size_t i = 0; i < (sizeof(cmd_table)/sizeof(cmd_table[0])); i++, cmd++) {
		if (cmd->flag != CMD_SECRET && (cmd->flag != CMD_HIDDEN || !all)) {
			if (n >= MAXLINELENGTH) {
				puts(",");
				n = 0;
			}
			// n += printf("%s%s (%.*s)", n?", ":"", cmd->name, cmd->significantlength, cmd->name);
			n += printf("%s(%.*s)%s", n?", ":"", cmd->significantlength, cmd->name, cmd->name + cmd->significantlength);
			j++;
		}
	}
	printf("\n%d commands found.\n", j);
	return rv;
}
