// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2003-2005 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include "platform_defs.h"
#include "command.h"
#include "input.h"

cmdinfo_t	*cmdtab;
int		ncmds;

static iterfunc_t	iter_func;
static checkfunc_t	check_func;

struct cmdline {
	char	*cmdline;
	bool	iterate;
};

static int		ncmdline;
static struct cmdline	*cmdline;

static int
compare(const void *a, const void *b)
{
	return strcmp(((const cmdinfo_t *)a)->name,
		      ((const cmdinfo_t *)b)->name);
}

void
add_command(
	const cmdinfo_t	*ci)
{
	cmdtab = realloc((void *)cmdtab, ++ncmds * sizeof(*cmdtab));
	if (!cmdtab) {
		perror(_("adding libxcmd command"));
		exit(1);
	}
	cmdtab[ncmds - 1] = *ci;
	qsort(cmdtab, ncmds, sizeof(*cmdtab), compare);
}

static int
check_command(
	const cmdinfo_t	*ci)
{
	/* always run internal library supplied commands */
	if (ci->flags & CMD_FLAG_LIBRARY)
		return 1;

	if (check_func)
		return check_func(ci);
	return 1;
}

void
add_check_command(
	checkfunc_t	cf)
{
	check_func = cf;
}

int
command_usage(
	const cmdinfo_t *ci)
{
	printf("%s %s -- %s\n", ci->name, ci->args, ci->oneline);
	return 0;
}

int
command(
	const cmdinfo_t	*ct,
	int		argc,
	char		**argv)
{
	char		*cmd = argv[0];

	if (!check_command(ct))
		return 0;

	if (argc-1 < ct->argmin || (ct->argmax != -1 && argc-1 > ct->argmax)) {
		if (ct->argmax == -1)
			fprintf(stderr,
	_("bad argument count %d to %s, expected at least %d arguments\n"),
				argc-1, cmd, ct->argmin);
		else if (ct->argmin == ct->argmax)
			fprintf(stderr,
	_("bad argument count %d to %s, expected %d arguments\n"),
				argc-1, cmd, ct->argmin);
		else
			fprintf(stderr,
	_("bad argument count %d to %s, expected between %d and %d arguments\n"),
			argc-1, cmd, ct->argmin, ct->argmax);
		return 0;
	}
	platform_getoptreset();
	return ct->cfunc(argc, argv);
}

const cmdinfo_t *
find_command(
	const char	*cmd)
{
	cmdinfo_t	*ct;

	for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) {
		if (strcmp(ct->name, cmd) == 0 ||
		    (ct->altname && strcmp(ct->altname, cmd) == 0))
			return (const cmdinfo_t *)ct;
	}
	return NULL;
}

void
add_user_command(char *optarg)
{
	ncmdline++;
	cmdline = realloc(cmdline, sizeof(struct cmdline) * (ncmdline));
	if (!cmdline) {
		perror("realloc");
		exit(1);
	}
	cmdline[ncmdline-1].cmdline = optarg;
	cmdline[ncmdline-1].iterate = true;

}

void
add_oneshot_user_command(char *optarg)
{
	ncmdline++;
	cmdline = realloc(cmdline, sizeof(struct cmdline) * (ncmdline));
	if (!cmdline) {
		perror("realloc");
		exit(1);
	}
	cmdline[ncmdline-1].cmdline = optarg;
	cmdline[ncmdline-1].iterate = false;
}

/*
 * Run a command, iterating as necessary. Return 0 for success, non-zero
 * if an error occurred. Errors terminate loop iteration immediately.
 */
static int
iterate_command(
	const cmdinfo_t	*ct,
	int		argc,
	char		**argv)
{
	int		error = 0;
	int		j;

	/* if there's nothing to iterate, we're done! */
	if (!iter_func)
		return 0;

	for (j = iter_func(0); j; j = iter_func(j)) {
		error = command(ct, argc, argv);
		if (error)
			break;

	}

	return error;
}

void
add_command_iterator(
	iterfunc_t	func)
{
	iter_func = func;
}

static int
process_input(
	char		*input,
	bool		iterate)
{
	char		**v;
	const cmdinfo_t	*ct;
	int		c = 0;
	int		error = 0;

	v = breakline(input, &c);
	if (!c)
		goto out;

	ct = find_command(v[0]);
	if (!ct) {
		fprintf(stderr, _("command \"%s\" not found\n"), v[0]);
		goto out;
	}

	/* oneshot commands don't iterate */
	if (!iterate || (ct->flags & CMD_FLAG_ONESHOT))
		error = command(ct, c, v);
	else
		error = iterate_command(ct, c, v);
out:
	doneline(input, v);
	return error;
}

void
command_loop(void)
{
	char	*input;
	int	done = 0;
	int	i;

	if (!cmdline) {
		/* interactive mode */
		while (!done) {
			input = fetchline();
			if (!input)
				break;
			done = process_input(input, false);
		}
		return;
	}

	/* command line mode */
	for (i = 0; !done && i < ncmdline; i++) {
		input = strdup(cmdline[i].cmdline);
		if (!input) {
			fprintf(stderr,
				_("cannot strdup command '%s': %s\n"),
				cmdline[i].cmdline, strerror(errno));
			exit(1);
		}
		done = process_input(input, cmdline[i].iterate);
	}
	free(cmdline);
	return;
}

void
report_io_times(
	const char		*verb,
	struct timeval		*t2,
	long long		offset,
	long long		count,
	long long		total,
	int			ops,
	int			compact)
{
	char			s1[64], s2[64], ts[64];

	timestr(t2, ts, sizeof(ts), compact ? VERBOSE_FIXED_TIME : 0);
	if (!compact) {
		cvtstr((double)total, s1, sizeof(s1));
		cvtstr(tdiv((double)total, *t2), s2, sizeof(s2));
		printf(_("%s %lld/%lld bytes at offset %lld\n"),
			verb, total, count, (long long)offset);
		printf(_("%s, %d ops; %s (%s/sec and %.4f ops/sec)\n"),
			s1, ops, ts, s2, tdiv((double)ops, *t2));
	} else {/* bytes,ops,time,bytes/sec,ops/sec */
		printf("%lld,%d,%s,%.3f,%.3f\n",
			total, ops, ts,
			tdiv((double)total, *t2), tdiv((double)ops, *t2));
	}
}
