// SPDX-License-Identifier: GPL-2.0+

#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <linux/dcbnl.h>

#include "dcb.h"
#include "utils.h"

static void dcb_dcbx_help_set(void)
{
	fprintf(stderr,
		"Usage: dcb dcbx set dev STRING\n"
		"           [ host | lld-managed ]\n"
		"           [ cee | ieee ] [ static ]\n"
		"\n"
	);
}

static void dcb_dcbx_help_show(void)
{
	fprintf(stderr,
		"Usage: dcb dcbx show dev STRING\n"
		"\n"
	);
}

static void dcb_dcbx_help(void)
{
	fprintf(stderr,
		"Usage: dcb dcbx help\n"
		"\n"
	);
	dcb_dcbx_help_show();
	dcb_dcbx_help_set();
}

struct dcb_dcbx_flag {
	__u8 value;
	const char *key_fp;
	const char *key_json;
};

static const struct dcb_dcbx_flag dcb_dcbx_flags[] = {
	{DCB_CAP_DCBX_HOST, "host", NULL },
	{DCB_CAP_DCBX_LLD_MANAGED, "lld-managed", "lld_managed" },
	{DCB_CAP_DCBX_VER_CEE, "cee", NULL },
	{DCB_CAP_DCBX_VER_IEEE, "ieee", NULL },
	{DCB_CAP_DCBX_STATIC, "static", NULL },
};

static void dcb_dcbx_print(__u8 dcbx)
{
	int bit;
	int i;

	while ((bit = ffs(dcbx))) {
		bool found = false;

		bit--;
		for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
			const struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];

			if (flag->value == 1 << bit) {
				print_bool(PRINT_JSON, flag->key_json ?: flag->key_fp,
					   NULL, true);
				print_string(PRINT_FP, NULL, "%s ", flag->key_fp);
				found = true;
				break;
			}
		}

		if (!found)
			fprintf(stderr, "Unknown DCBX bit %#x.\n", 1 << bit);

		dcbx &= ~(1 << bit);
	}

	print_nl();
}

static int dcb_dcbx_get(struct dcb *dcb, const char *dev, __u8 *dcbx)
{
	__u16 payload_len;
	void *payload;
	int err;

	err = dcb_get_attribute_bare(dcb, DCB_CMD_IEEE_GET, dev, DCB_ATTR_DCBX,
				     &payload, &payload_len);
	if (err != 0)
		return err;

	if (payload_len != 1) {
		fprintf(stderr, "DCB_ATTR_DCBX payload has size %d, expected 1.\n",
			payload_len);
		return -EINVAL;
	}
	*dcbx = *(__u8 *) payload;
	return 0;
}

static int dcb_dcbx_set(struct dcb *dcb, const char *dev, __u8 dcbx)
{
	return dcb_set_attribute_bare(dcb, DCB_CMD_SDCBX, dev, DCB_ATTR_DCBX,
				      &dcbx, 1, DCB_ATTR_DCBX);
}

static int dcb_cmd_dcbx_set(struct dcb *dcb, const char *dev, int argc, char **argv)
{
	__u8 dcbx = 0;
	__u8 i;

	if (!argc) {
		dcb_dcbx_help_set();
		return 0;
	}

	do {
		if (matches(*argv, "help") == 0) {
			dcb_dcbx_help_set();
			return 0;
		}

		for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
			const struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];

			if (matches(*argv, flag->key_fp) == 0) {
				dcbx |= flag->value;
				NEXT_ARG_FWD();
				goto next;
			}
		}

		fprintf(stderr, "What is \"%s\"?\n", *argv);
		dcb_dcbx_help_set();
		return -EINVAL;

next:
		;
	} while (argc > 0);

	return dcb_dcbx_set(dcb, dev, dcbx);
}

static int dcb_cmd_dcbx_show(struct dcb *dcb, const char *dev, int argc, char **argv)
{
	__u8 dcbx;
	int ret;

	ret = dcb_dcbx_get(dcb, dev, &dcbx);
	if (ret != 0)
		return ret;

	while (argc > 0) {
		if (matches(*argv, "help") == 0) {
			dcb_dcbx_help_show();
			return 0;
		} else {
			fprintf(stderr, "What is \"%s\"?\n", *argv);
			dcb_dcbx_help_show();
			return -EINVAL;
		}

		NEXT_ARG_FWD();
	}

	open_json_object(NULL);
	dcb_dcbx_print(dcbx);
	close_json_object();
	return 0;
}

int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv)
{
	if (!argc || matches(*argv, "help") == 0) {
		dcb_dcbx_help();
		return 0;
	} else if (matches(*argv, "show") == 0) {
		NEXT_ARG_FWD();
		return dcb_cmd_parse_dev(dcb, argc, argv,
					 dcb_cmd_dcbx_show, dcb_dcbx_help_show);
	} else if (matches(*argv, "set") == 0) {
		NEXT_ARG_FWD();
		return dcb_cmd_parse_dev(dcb, argc, argv,
					 dcb_cmd_dcbx_set, dcb_dcbx_help_set);
	} else {
		fprintf(stderr, "What is \"%s\"?\n", *argv);
		dcb_dcbx_help();
		return -EINVAL;
	}
}
