// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Robin Jarry

#include <gr_errno.h>
#include <gr_macro.h>
#include <gr_string.h>

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *astrcat(char *buf, const char *fmt, ...) {
	char *ret = NULL;
	va_list ap;
	int n;

	if (fmt == NULL) {
		errno = EINVAL;
		goto err;
	}

	va_start(ap, fmt);
	n = vasprintf(&ret, fmt, ap);
	va_end(ap);
	if (n < 0)
		goto err;

	if (buf != NULL) {
		int buf_len = strlen(buf);
		char *tmp = malloc(buf_len + n + 1);
		if (tmp == NULL) {
			errno = ENOMEM;
			goto err;
		}
		memcpy(tmp, buf, buf_len);
		memcpy(tmp + buf_len, ret, n + 1);
		free(buf);
		free(ret);
		ret = tmp;
	}

	return ret;
err:
	free(buf);
	free(ret);
	return errno_set_null(errno);
}

char *strjoin(char **array, size_t len, const char *sep) {
	char *out = NULL;

	for (size_t i = 0; i < len; i++) {
		if (out == NULL)
			out = strdup(array[i]);
		else
			out = astrcat(out, "%s%s", sep, array[i]);

		if (out == NULL)
			break;
	}

	return out;
}

int cpuset_format(char *buf, size_t len, const cpu_set_t *set) {
	unsigned i, j;
	size_t n = 0;

	if (buf == NULL || set == NULL || len <= 1)
		return errno_set(EINVAL);

	buf[0] = '\0';

	for (i = 0; i < CPU_SETSIZE; i++) {
		if (CPU_ISSET(i, set)) {
			for (j = i + 1; j < CPU_SETSIZE; j++)
				if (!CPU_ISSET(j, set))
					break;
			j -= 1;

			if (i == j)
				SAFE_BUF(snprintf, len, "%u,", i);
			else if (j - i == 1)
				SAFE_BUF(snprintf, len, "%u,%u,", i, j);
			else
				SAFE_BUF(snprintf, len, "%u-%u,", i, j);

			i = j + 1;
		}
	}

	if (n > 0) {
		// strip trailing comma
		buf[n - 1] = '\0';
	}

	return 0;
err:
	return errno_set(errno);
}

static const char *parse_number(const char *buf, unsigned *num) {
	char *next = NULL;
	errno = 0;
	*num = strtoul(buf, &next, 10);
	if (errno != 0)
		return errno_set_null(errno);
	if (buf == next)
		return errno_set_null(EINVAL);
	return next;
}

int cpuset_parse(cpu_set_t *set, const char *buf) {
	if (set == NULL || buf == NULL || *buf == '\0')
		return errno_set(EINVAL);

	CPU_ZERO(set);

	while (*buf) {
		unsigned start, end;

		while (*buf == ',')
			buf++;

		buf = parse_number(buf, &start);
		if (buf == NULL)
			return errno_set(errno);

		if (*buf == '-') {
			buf = parse_number(++buf, &end);
			if (buf == NULL)
				return errno_set(errno);
		} else {
			end = start;
		}

		if (start > end)
			return errno_set(ERANGE);
		for (; start <= end; start++) {
			if (start >= CPU_SETSIZE)
				return errno_set(EOVERFLOW);
			CPU_SET(start, set);
		}
	}

	return 0;
}

// Matches RTE_LOG_EMERG(1) through RTE_LOG_DEBUG(8).
static const char *log_level_names[] = {
	[0] = "disabled",
	[1] = "emerg",
	[2] = "alert",
	[3] = "crit",
	[4] = "error",
	[5] = "warning",
	[6] = "notice",
	[7] = "info",
	[8] = "debug",
};

const char *gr_log_level_name(uint32_t level) {
	if (level < ARRAY_DIM(log_level_names))
		return log_level_names[level];
	return "unknown";
}

int gr_log_level_parse(const char *name) {
	if (name == NULL)
		return errno_set(EINVAL);

	for (unsigned i = 0; i < ARRAY_DIM(log_level_names); i++) {
		if (strcmp(name, log_level_names[i]) == 0)
			return i;
	}

	return errno_set(EINVAL);
}

int parse_uint(unsigned *u, const char *s, unsigned base, unsigned min, unsigned max) {
	unsigned long val;
	char *endptr;

	if (s == NULL)
		return errno_set(EINVAL);

	errno = 0;
	val = strtoul(s, &endptr, base);
	if (errno != 0)
		return errno_set(errno);
	if (*endptr != '\0')
		return errno_set(EINVAL);
	if (val < min || val > max)
		return errno_set(ERANGE);

	*u = val;

	return 0;
}
