// SPDX-License-Identifier: LGPL-2.1-or-later
/**
 * This file is part of libnvme.
 */

/**
 * mi-mctp-csi-test: open a MI connection over MCTP, and send two commands
 * in parallel with different CSI buffers
 */

#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <pthread.h>

#include <libnvme-mi.h>

#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>
#include <bits/pthreadtypes.h>

void fhexdump(FILE *fp, const unsigned char *buf, int len)
{
	const int row_len = 16;
	int i, j;

	for (i = 0; i < len; i += row_len) {
		char hbuf[row_len * strlen("00 ") + 1];
		char cbuf[row_len + strlen("|") + 1];

		for (j = 0; (j < row_len) && ((i+j) < len); j++) {
			unsigned char c = buf[i + j];

			sprintf(hbuf + j * 3, "%02x ", c);

			if (!isprint(c))
				c = '.';

			sprintf(cbuf + j, "%c", c);
		}

		strcat(cbuf, "|");

		fprintf(fp, "%08x  %*s |%s\n", i,
				0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
	}
}

void hexdump(const unsigned char *buf, int len)
{
	fhexdump(stdout, buf, len);
}

int do_get_log_page(nvme_mi_ep_t ep, int argc, char **argv)
{
	struct nvme_get_log_args args = { 0 };
	struct nvme_mi_ctrl *ctrl;
	uint8_t buf[4096];
	uint16_t ctrl_id;
	int rc, tmp;

	if (argc < 2) {
		fprintf(stderr, "no controller ID specified\n");
		return -1;
	}

	tmp = atoi(argv[1]);
	if (tmp < 0 || tmp > 0xffff) {
		fprintf(stderr, "invalid controller ID\n");
		return -1;
	}

	ctrl_id = tmp & 0xffff;

	args.args_size = sizeof(args);
	args.log = buf;
	args.len = sizeof(buf);

	if (argc > 2) {
		tmp = atoi(argv[2]);
		args.lid = tmp & 0xff;
	} else {
		args.lid = 0x1;
	}

	ctrl = nvme_mi_init_ctrl(ep, ctrl_id);
	if (!ctrl) {
		warn("can't create controller");
		return -1;
	}

	rc = nvme_mi_admin_get_log(ctrl, &args);
	if (rc) {
		warn("can't perform Get Log page command");
		return -1;
	}

	printf("Get log page (log id = 0x%02x) data:\n", args.lid);
	hexdump(buf, args.len);

	return 0;
}

struct thread_struct {
	nvme_mi_ep_t ep;
	int argc;
	char **argv;
	int rc;
};

void *csi_thread_helper(void *context)
{
	struct thread_struct *s = (struct thread_struct *) context;

	s->rc = do_get_log_page(s->ep, s->argc, s->argv);
	return NULL;
}

enum action {
	ACTION_CSI_TEST,
};

int do_csi_test(nvme_root_t root, int net, __u8 eid,
		int argc, char **argv)
{
	int rc = 0;
	nvme_mi_ep_t ep1, ep2;

	ep1 = nvme_mi_open_mctp(root, net, eid);
	if (!ep1)
		errx(EXIT_FAILURE, "can't open MCTP endpoint %d:%d", net, eid);
	ep2 = nvme_mi_open_mctp(root, net, eid);
	if (!ep2)
		errx(EXIT_FAILURE, "can't open MCTP endpoint %d:%d", net, eid);

	pthread_t thread;

	nvme_mi_set_csi(ep1, 0);//Not necessary, but to be explicit
	nvme_mi_set_csi(ep2, 1);
	struct thread_struct s;

	s.ep = ep2;
	s.argc = argc;
	s.argv = argv;

	// Create a new thread to run my_function
	if (pthread_create(&thread, NULL, csi_thread_helper, &s)) {
		fprintf(stderr, "Error creating thread\n");
		return 1;
	}

	rc = do_get_log_page(ep1, argc, argv);

	// Main thread continues to do other work
	printf("Main thread finished with rc=%d\n", rc);

	// Wait for the created thread to finish
	if (pthread_join(thread, NULL)) {
		fprintf(stderr, "Error joining thread\n");
		return 2;
	}

	printf("Second thread finished with rc=%d\n", s.rc);

	nvme_mi_close(ep1);
	nvme_mi_close(ep2);

	if (rc)
		return rc;
	if (s.rc)
		return s.rc;
	return 0;
}

static int do_action_endpoint(enum action action,
				nvme_root_t root,
				int net,
				uint8_t eid,
				int argc,
				char **argv)
{
	int rc;

	switch (action) {
	case ACTION_CSI_TEST:
		rc = do_csi_test(root, net, eid, argc, argv);
		break;
	default:
		/* This shouldn't be possible, as we should be covering all
		 * of the enum action options above. Hoever, keep the compilers
		 * happy and fail gracefully.
		 */
		fprintf(stderr, "invalid action %d?\n", action);
		rc = -1;
	}
	return rc;
}

int main(int argc, char **argv)
{
	enum action action;
	nvme_root_t root;
	bool usage = true;
	uint8_t eid = 0;
	int rc = 0, net = 0;

	if (argc >= 5) {
		usage = false;
		net = atoi(argv[1]);
		eid = atoi(argv[2]) & 0xff;
		argv += 2;
		argc -= 2;
	}

	if (usage) {
		fprintf(stderr,
			"usage: %s <net> <eid> [action] [action args]\n",
			argv[0]);
		fprintf(stderr, "where action is:\n"
			"  csi-test <controller-id> [<log-id>]\n"
			"\n"
			);
		return EXIT_FAILURE;
	}

	char *action_str = argv[1];

	argc--;
	argv++;

	if (!strcmp(action_str, "csi-test")) {
		action = ACTION_CSI_TEST;
	} else {
		fprintf(stderr, "invalid action '%s'\n", action_str);
		return EXIT_FAILURE;
	}

	root = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
	if (!root)
		err(EXIT_FAILURE, "can't create NVMe root");

	rc = do_action_endpoint(action, root, net, eid, argc, argv);
	nvme_mi_free_root(root);

	return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}


