/*
 * Copyright (c) 2022 Meta
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef CONFIG_ARCH_POSIX
#include <unistd.h>
#else
#include <zephyr/posix/unistd.h>
#endif

#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/crc.h>

static const char *const crc_types[] = {
	[CRC7_BE] = "7_be",
	[CRC8] = "8",
	[CRC8_CCITT] "8_ccitt",
	[CRC16] = "16",
	[CRC16_ANSI] = "16_ansi",
	[CRC16_CCITT] = "16_ccitt",
	[CRC16_ITU_T] = "16_itu_t",
	[CRC32_C] = "32_c",
	[CRC32_IEEE] = "32_ieee",
};

static int string_to_crc_type(const char *s)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(crc_types); ++i) {
		if (strcmp(s, crc_types[i]) == 0) {
			return i;
		}
	}

	return -1;
}

static void usage(const struct shell *sh)
{
	size_t i;

	shell_print(sh, "crc [options..] <address> <size>");
	shell_print(sh, "options:");
	shell_print(sh, "-f         This is the first packet");
	shell_print(sh, "-l         This is the last packet");
	shell_print(sh, "-p <poly>  Use polynomial 'poly'");
	shell_print(sh, "-r         Reflect");
	shell_print(sh, "-s <seed>  Use 'seed' as the initial value");
	shell_print(sh, "-t <type>  Compute the CRC described by 'type'");
	shell_print(sh, "Note: some options are only useful for certain CRCs");
	shell_print(sh, "CRC Types:");
	for (i = 0; i < ARRAY_SIZE(crc_types); ++i) {
		shell_print(sh, "%s", crc_types[i]);
	}
}

static int cmd_crc(const struct shell *sh, size_t argc, char **argv)
{
	int rv;
	size_t size = -1;
	bool last = false;
	uint32_t poly = 0;
	bool first = false;
	uint32_t seed = 0;
	bool reflect = false;
	void *addr = (void *)-1;
	enum crc_type type = CRC32_IEEE;

	optind = 1;

	while ((rv = getopt(argc, argv, "fhlp:rs:t:")) != -1) {
		switch (rv) {
		case 'f':
			first = true;
			break;
		case 'h':
			usage(sh);
			return 0;
		case 'l':
			last = true;
			break;
		case 'p':
			poly = (size_t)strtoul(optarg, NULL, 16);
			if (poly == 0 && errno == EINVAL) {
				shell_error(sh, "invalid seed '%s'", optarg);
				return -EINVAL;
			}
			break;
		case 'r':
			reflect = true;
			break;
		case 's':
			seed = (size_t)strtoul(optarg, NULL, 16);
			if (seed == 0 && errno == EINVAL) {
				shell_error(sh, "invalid seed '%s'", optarg);
				return -EINVAL;
			}
			break;
		case 't':
			type = string_to_crc_type(optarg);
			if (type == -1) {
				shell_error(sh, "invalid type '%s'", optarg);
				return -EINVAL;
			}
			break;
		case '?':
		default:
			usage(sh);
			return -EINVAL;
		}
	}

	if (optind + 2 > argc) {
		shell_error(sh, "'address' and 'size' arguments are mandatory");
		usage(sh);
		return -EINVAL;
	}

	addr = (void *)strtoul(argv[optind], NULL, 16);
	if (addr == 0 && errno == EINVAL) {
		shell_error(sh, "invalid address '%s'", argv[optind]);
		return -EINVAL;
	}

	size = (size_t)strtoul(argv[optind + 1], NULL, 0);
	if (size == 0 && errno == EINVAL) {
		shell_error(sh, "invalid size '%s'", argv[optind + 1]);
		return -EINVAL;
	}

	shell_print(sh, "0x%x", crc_by_type(type, addr, size, seed, poly, reflect, first, last));

	return 0;
}

SHELL_CMD_ARG_REGISTER(crc, NULL, NULL, cmd_crc, 0, 12);
