/*
 * Copyright (c) 2024 Astrolight
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/util.h>

#define TXRX_ARGV_BYTES     (1)
#define CONF_ARGV_DEV       (1)
#define CONF_ARGV_FREQUENCY (2)
#define CONF_ARGV_SETTINGS  (3)

/* Maximum bytes we can write and read at once */
#define MAX_SPI_BYTES MIN((CONFIG_SHELL_ARGC_MAX - TXRX_ARGV_BYTES), 32)

static struct device *spi_device;
static struct spi_config config = {.frequency = 1000000,
				   .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)};

static void device_name_get(size_t idx, struct shell_static_entry *entry)
{
	const struct device *dev = shell_device_lookup(idx, "spi");

	entry->syntax = (dev != NULL) ? dev->name : NULL;
	entry->handler = NULL;
	entry->help = NULL;
	entry->subcmd = NULL;
}

SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);

static int cmd_spi_transceive(const struct shell *ctx, size_t argc, char **argv)
{
	uint8_t rx_buffer[MAX_SPI_BYTES] = {0};
	uint8_t tx_buffer[MAX_SPI_BYTES] = {0};

	if (spi_device == NULL) {
		shell_error(ctx, "SPI device isn't configured. Use `spi conf`");
		return -ENODEV;
	}

	int bytes_to_send = argc - TXRX_ARGV_BYTES;

	for (int i = 0; i < bytes_to_send; i++) {
		tx_buffer[i] = strtol(argv[TXRX_ARGV_BYTES + i], NULL, 16);
	}

	const struct spi_buf tx_buffers = {.buf = tx_buffer, .len = bytes_to_send};
	const struct spi_buf rx_buffers = {.buf = rx_buffer, .len = bytes_to_send};

	const struct spi_buf_set tx_buf_set = {.buffers = &tx_buffers, .count = 1};
	const struct spi_buf_set rx_buf_set = {.buffers = &rx_buffers, .count = 1};

	int ret = spi_transceive(spi_device, &config, &tx_buf_set, &rx_buf_set);

	if (ret < 0) {
		shell_error(ctx, "spi_transceive returned %d", ret);
		return ret;
	}

	shell_print(ctx, "TX:");
	shell_hexdump(ctx, tx_buffer, bytes_to_send);

	shell_print(ctx, "RX:");
	shell_hexdump(ctx, rx_buffer, bytes_to_send);

	return ret;
}

static int cmd_spi_conf(const struct shell *ctx, size_t argc, char **argv)
{
	spi_operation_t operation = SPI_WORD_SET(8) | SPI_OP_MODE_MASTER;

	/* warning: initialization discards 'const' qualifier from pointer */
	/* target type */
	struct device *dev = (struct device *)shell_device_get_binding(argv[CONF_ARGV_DEV]);

	if (dev == NULL) {
		shell_error(ctx, "device %s not found.", argv[CONF_ARGV_DEV]);
		return -ENODEV;
	}

	uint32_t frequency = strtol(argv[CONF_ARGV_FREQUENCY], NULL, 10);

	if (!IN_RANGE(frequency, 100 * 1000, 80 * 1000 * 1000)) {
		shell_error(ctx, "frequency must be between 100000  and 80000000");
		return -EINVAL;
	}

	/* no settings */
	if (argc == (CONF_ARGV_FREQUENCY + 1)) {
		goto out;
	}

	char *opts = argv[CONF_ARGV_SETTINGS];
	bool all_opts_is_valid = true;

	while (*opts != '\0') {
		switch (*opts) {
		case 'o':
			operation |= SPI_MODE_CPOL;
			break;
		case 'h':
			operation |= SPI_MODE_CPHA;
			break;
		case 'l':
			operation |= SPI_TRANSFER_LSB;
			break;
		case 'T':
			operation |= SPI_FRAME_FORMAT_TI;
			break;
		default:
			all_opts_is_valid = false;
			shell_error(ctx, "invalid setting %c", *opts);
		}
		opts++;
	}

	if (!all_opts_is_valid) {
		return -EINVAL;
	}

out:
	config.frequency = frequency;
	config.operation = operation;
	spi_device = dev;

	return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_spi_cmds,
			       SHELL_CMD_ARG(conf, &dsub_device_name,
					     "Configure SPI\n"
					     "Usage: spi conf <device> <frequency> [<settings>]\n"
					     "<settings> - any sequence of letters:\n"
					     "o - SPI_MODE_CPOL\n"
					     "h - SPI_MODE_CPHA\n"
					     "l - SPI_TRANSFER_LSB\n"
					     "T - SPI_FRAME_FORMAT_TI\n"
					     "example: spi conf spi1 1000000 ol",
					     cmd_spi_conf, 3, 1),
			       SHELL_CMD_ARG(transceive, NULL,
					     "Transceive data to and from an SPI device\n"
					     "Usage: spi transceive <TX byte 1> [<TX byte 2> ...]",
					     cmd_spi_transceive, 2, MAX_SPI_BYTES - 1),
			       SHELL_SUBCMD_SET_END);

SHELL_CMD_REGISTER(spi, &sub_spi_cmds, "SPI commands", NULL);
