/*
 * Copyright (c) 2018 Oticon A/S
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <zephyr/arch/posix/posix_trace.h>
#include "posix_board_if.h"
#include <zephyr/types.h>
#include "cmdline_common.h"

/**
 * Check if <arg> is the option <option>
 * The accepted syntax is:
 *   * For options without a value following:
 *       [-[-]]<option>
 *   * For options with value:
 *       [-[-]]<option>{:|=}<value>
 *
 * Returns 0 if it is not, or a number > 0 if it is.
 * The returned number is the number of characters it went through
 * to find the end of the option including the ':' or '=' character in case of
 * options with value
 */
int cmd_is_option(const char *arg, const char *option, int with_value)
{
	int of = 0;
	size_t to_match_len = strlen(option);

	if (arg[of] == '-') {
		of++;
	}
	if (arg[of] == '-') {
		of++;
	}

	if (!with_value) {
		if (strcmp(&arg[of], option) != 0) {
			return 0;
		} else {
			return of + to_match_len;
		}
	}

	while (!(arg[of] == 0 && *option == 0)) {
		if (*option == 0) {
			if ((arg[of] == ':') || (arg[of] == '=')) {
				of++;
				break;
			}
			return 0;
		}
		if (arg[of] != *option) {
			return 0;
		}
		of++;
		option++;
	}

	if (arg[of] == 0) { /* we need a value to follow */
		posix_print_error_and_exit("Incorrect option syntax '%s'. The "
					   "value should follow the options. "
					   "For example --ratio=3\n",
					   arg);
	}
	return of;
}

/**
 * Return 1 if <arg> matches an accepted help option.
 * 0 otherwise
 *
 * Valid help options are [-[-]]{?|h|help}
 * with the h or help in any case combination
 */
int cmd_is_help_option(const char *arg)
{
	if (arg[0] == '-') {
		arg++;
	}
	if (arg[0] == '-') {
		arg++;
	}
	if ((strcasecmp(arg, "?") == 0) ||
	    (strcasecmp(arg, "h") == 0) ||
	    (strcasecmp(arg, "help") == 0)) {
		return 1;
	} else {
		return 0;
	}
}

#define CMD_TYPE_ERROR "Coding error: type %c not understood"
#define CMD_ERR_BOOL_SWI "Programming error: I only know how to "\
	"automatically read boolean switches\n"

/**
 * Read out a the value following an option from str, and store it into
 * <dest>
 * <type> indicates the type of parameter (and type of dest pointer)
 *   'b' : boolean
 *   's' : string (char *)
 *   'u' : 32 bit unsigned integer
 *   'U' : 64 bit unsigned integer
 *   'i' : 32 bit signed integer
 *   'I' : 64 bit signed integer
 *   'd' : *double* float
 *
 * Note: list type ('l') cannot be handled by this function and must always be
 *       manual
 *
 *  <long_d> is the long name of the option
 */
void cmd_read_option_value(const char *str, void *dest, const char type,
			   const char *option)
{
	int error = 0;
	char *endptr = NULL;

	switch (type) {
	case 'b':
		if (strcasecmp(str, "false") == 0) {
			*(bool *)dest = false;
			endptr = (char *)str + 5;
		} else if (strcmp(str, "0") == 0) {
			*(bool *)dest = false;
			endptr = (char *)str + 1;
		} else if (strcasecmp(str, "true") == 0) {
			*(bool *)dest = true;
			endptr = (char *)str + 4;
		} else if (strcmp(str, "1") == 0) {
			*(bool *)dest = true;
			endptr = (char *)str + 1;
		} else {
			error = 1;
		}
		break;
	case 's':
		*(char **)dest = (char *)str;
		endptr = (char *)str + strlen(str);
		break;
	case 'u':
		*(uint32_t *)dest = strtoul(str, &endptr, 0);
		break;
	case 'U':
		*(uint64_t *)dest = strtoull(str, &endptr, 0);
		break;
	case 'i':
		*(int32_t *)dest = strtol(str, &endptr, 0);
		break;
	case 'I':
		*(int64_t *)dest = strtoll(str, &endptr, 0);
		break;
	case 'd':
		*(double *)dest = strtod(str, &endptr);
		break;
	default:
		posix_print_error_and_exit(CMD_TYPE_ERROR, type);
		/* Unreachable */
		break;
	}

	if (!error && endptr && *endptr != 0) {
		error = 1;
	}

	if (error) {
		posix_print_error_and_exit("Error reading value of %s '%s'. Use"
					   " --help for usage information\n",
					   option, str);
	}
}

/**
 * Initialize existing dest* to defaults based on type
 */
void cmd_args_set_defaults(struct args_struct_t args_struct[])
{
	int count = 0;

	while (args_struct[count].option != NULL) {

		if (args_struct[count].dest == NULL) {
			count++;
			continue;
		}

		switch (args_struct[count].type) {
		case 0: /* does not have storage */
			break;
		case 'b':
			*(bool *)args_struct[count].dest = false;
			break;
		case 's':
			*(char **)args_struct[count].dest = NULL;
			break;
		case 'u':
			*(uint32_t *)args_struct[count].dest = UINT32_MAX;
			break;
		case 'U':
			*(uint64_t *)args_struct[count].dest = UINT64_MAX;
			break;
		case 'i':
			*(int32_t *)args_struct[count].dest = INT32_MAX;
			break;
		case 'I':
			*(int64_t *)args_struct[count].dest = INT64_MAX;
			break;
		case 'd':
			*(double *)args_struct[count].dest = (double)NAN;
			break;
		default:
			posix_print_error_and_exit(CMD_TYPE_ERROR,
						   args_struct[count].type);
			break;
		}
		count++;
	}
}

/**
 * For the help messages:
 * Generate a string containing how the option described by <args_s_el>
 * should be used
 *
 * The string is saved in <buf> which has been allocated <size> bytes by the
 * caller
 */
static void cmd_gen_switch_syntax(char *buf, int size,
				  struct args_struct_t *args_s_el)
{
	int ret = 0;

	if (size <= 0) {
		return;
	}

	if (args_s_el->is_mandatory == false) {
		*buf++ = '[';
		size--;
	}

	if (args_s_el->is_switch == true) {
		ret = snprintf(buf, size, "-%s", args_s_el->option);
	} else {
		if (args_s_el->type != 'l') {
			ret = snprintf(buf, size, "-%s=<%s>",
					args_s_el->option, args_s_el->name);
		} else {
			ret = snprintf(buf, size, "-%s <%s>...",
					args_s_el->option, args_s_el->name);
		}
	}

	if (ret < 0) {
		posix_print_error_and_exit("Unexpected error in %s %i\n",
					   __FILE__, __LINE__);
	}
	if (size - ret < 0) {
		/*
		 * If we run out of space we can just stop,
		 * this is not critical
		 */
		return;
	}
	buf += ret;
	size -= ret;

	if (args_s_el->is_mandatory == false) {
		snprintf(buf, size, "] ");
	} else {
		snprintf(buf, size, " ");
	}
}

/**
 * Print short list of available switches
 */
void cmd_print_switches_help(struct args_struct_t args_struct[])
{
	int count = 0;
	int printed_in_line = strlen(_HELP_SWITCH) + 1;

	fprintf(stdout, "%s ", _HELP_SWITCH);

	while (args_struct[count].option != NULL) {
		char stringy[_MAX_STRINGY_LEN];

		cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
				      &args_struct[count]);

		if (printed_in_line + strlen(stringy) > _MAX_LINE_WIDTH) {
			fprintf(stdout, "\n");
			printed_in_line = 0;
		}

		fprintf(stdout, "%s", stringy);
		printed_in_line += strlen(stringy);
		count++;
	}

	fprintf(stdout, "\n");
}

/**
 * Print the long help message of the program
 */
void cmd_print_long_help(struct args_struct_t args_struct[])
{
	int ret;
	int count = 0;
	int printed_in_line = 0;
	char stringy[_MAX_STRINGY_LEN];

	cmd_print_switches_help(args_struct);

	fprintf(stdout, "\n %-*s:%s\n", _LONG_HELP_ALIGN-1,
		_HELP_SWITCH, _HELP_DESCR);

	while (args_struct[count].option != NULL) {
		int printed_right;
		char *toprint;
		int total_to_print;

		cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
				      &args_struct[count]);

		ret = fprintf(stdout, " %-*s:", _LONG_HELP_ALIGN-1, stringy);
		printed_in_line = ret;
		printed_right = 0;
		toprint = args_struct[count].descript;
		total_to_print = strlen(toprint);
		ret = fprintf(stdout, "%.*s\n",
				_MAX_LINE_WIDTH - printed_in_line,
				&toprint[printed_right]);
		printed_right += ret - 1;

		while (printed_right < total_to_print) {
			fprintf(stdout, "%*s", _LONG_HELP_ALIGN, "");
			ret = fprintf(stdout, "%.*s\n",
				      _MAX_LINE_WIDTH - _LONG_HELP_ALIGN,
				      &toprint[printed_right]);
			printed_right += ret - 1;
		}
		count++;
	}
	fprintf(stdout, "\n");
	fprintf(stdout, "Note that which options are available depends on the "
		"enabled features/drivers\n\n");
}

/*
 * <argv> matched the argument described in <arg_element>
 *
 * If arg_element->dest points to a place to store a possible value, read it
 * If there is a callback registered, call it after
 */
static void cmd_handle_this_matched_arg(char *argv, int offset,
					struct args_struct_t *arg_element)
{
	if (arg_element->dest != NULL) {
		if (arg_element->is_switch) {
			if (arg_element->type == 'b') {
				*(bool *)arg_element->dest = true;
			} else {
				posix_print_error_and_exit(CMD_ERR_BOOL_SWI);
			}
		} else { /* if not a switch we need to read its value */
			cmd_read_option_value(&argv[offset],
					      arg_element->dest,
					      arg_element->type,
					      arg_element->option);
		}
	}

	if (arg_element->call_when_found) {
		arg_element->call_when_found(argv, offset);
	}
}

/**
 * Try to find if this argument is in the list (and it is not manual)
 * if it does, try to parse it, set its dest accordingly, and return true
 * if it is not found, return false
 */
bool cmd_parse_one_arg(char *argv, struct args_struct_t args_struct[])
{
	int count = 0;
	int ret;

	if (cmd_is_help_option(argv)) {
		cmd_print_long_help(args_struct);
		posix_exit(0);
	}

	while (args_struct[count].option != NULL) {
		if (args_struct[count].manual) {
			count++;
			continue;
		}
		ret = cmd_is_option(argv, args_struct[count].option,
				    !args_struct[count].is_switch);
		if (ret) {
			cmd_handle_this_matched_arg(argv,
						    ret,
						    &args_struct[count]);
			return true;
		}
		count++;
	}
	return false;
}
