#define USE_THE_REPOSITORY_VARIABLE

#include "git-compat-util.h"
#include "alias.h"
#include "config.h"
#include "gettext.h"
#include "strbuf.h"
#include "string-list.h"

struct config_alias_data {
	const char *alias;
	char *v;
	struct string_list *list;
};

static int config_alias_cb(const char *var, const char *value,
			   const struct config_context *ctx UNUSED, void *d)
{
	struct config_alias_data *data = d;
	const char *subsection, *key;
	size_t subsection_len;

	if (parse_config_key(var, "alias", &subsection, &subsection_len,
			     &key) < 0)
		return 0;

	/*
	 * Two config syntaxes:
	 * - alias.name = value   (without subsection, case-insensitive)
	 * - [alias "name"]
	 *       command = value  (with subsection, case-sensitive)
	 */
	/* Treat [alias ""] (empty subsection) the same as plain [alias]. */
	if (subsection && !subsection_len)
		subsection = NULL;

	if (subsection && strcmp(key, "command")) {
		/*
		 * We have historically supported the "alias.name" form when
		 * "name" happens to contain dots (e.g., alias.foo.bar to allow
		 * "git foo.bar". But our parsing above would split that into
		 * subsection "foo".
		 *
		 * If we do not understand the final key in a subsection-style
		 * variable, fall back to treating it as a two-level alias.
		 */
		key = var + strlen("alias.");
		subsection = NULL;
		subsection_len = 0;
	}

	if (data->alias) {
		int match;

		if (subsection)
			match = (strlen(data->alias) == subsection_len &&
				 !strncmp(data->alias, subsection,
					  subsection_len));
		else
			match = !strcasecmp(data->alias, key);

		if (match) {
			FREE_AND_NULL(data->v);
			return git_config_string(&data->v,
						 var, value);
		}
	} else if (data->list) {
		struct string_list_item *item;

		if (!value)
			return config_error_nonbool(var);

		if (subsection)
			item = string_list_append_nodup(data->list,
				xmemdupz(subsection, subsection_len));
		else
			item = string_list_append(data->list, key);
		item->util = xstrdup(value);
	}

	return 0;
}

char *alias_lookup(const char *alias)
{
	struct config_alias_data data = { alias, NULL };

	read_early_config(the_repository, config_alias_cb, &data);

	return data.v;
}

void list_aliases(struct string_list *list)
{
	struct config_alias_data data = { NULL, NULL, list };

	read_early_config(the_repository, config_alias_cb, &data);
}

void quote_cmdline(struct strbuf *buf, const char **argv)
{
	for (const char **argp = argv; *argp; argp++) {
		if (argp != argv)
			strbuf_addch(buf, ' ');
		strbuf_addch(buf, '"');
		for (const char *p = *argp; *p; p++) {
			const char c = *p;

			if (c == '"' || c =='\\')
				strbuf_addch(buf, '\\');
			strbuf_addch(buf, c);
		}
		strbuf_addch(buf, '"');
	}
}

#define SPLIT_CMDLINE_BAD_ENDING 1
#define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
#define SPLIT_CMDLINE_ARGC_OVERFLOW 3
static const char *split_cmdline_errors[] = {
	N_("cmdline ends with \\"),
	N_("unclosed quote"),
	N_("too many arguments"),
};

int split_cmdline(char *cmdline, const char ***argv)
{
	size_t src, dst, count = 0, size = 16;
	char quoted = 0;

	ALLOC_ARRAY(*argv, size);

	/* split alias_string */
	(*argv)[count++] = cmdline;
	for (src = dst = 0; cmdline[src];) {
		char c = cmdline[src];
		if (!quoted && isspace(c)) {
			cmdline[dst++] = 0;
			while (cmdline[++src]
					&& isspace(cmdline[src]))
				; /* skip */
			ALLOC_GROW(*argv, count + 1, size);
			(*argv)[count++] = cmdline + dst;
		} else if (!quoted && (c == '\'' || c == '"')) {
			quoted = c;
			src++;
		} else if (c == quoted) {
			quoted = 0;
			src++;
		} else {
			if (c == '\\' && quoted != '\'') {
				src++;
				c = cmdline[src];
				if (!c) {
					FREE_AND_NULL(*argv);
					return -SPLIT_CMDLINE_BAD_ENDING;
				}
			}
			cmdline[dst++] = c;
			src++;
		}
	}

	cmdline[dst] = 0;

	if (quoted) {
		FREE_AND_NULL(*argv);
		return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
	}

	if (count >= INT_MAX) {
		FREE_AND_NULL(*argv);
		return -SPLIT_CMDLINE_ARGC_OVERFLOW;
	}

	ALLOC_GROW(*argv, count + 1, size);
	(*argv)[count] = NULL;

	return count;
}

const char *split_cmdline_strerror(int split_cmdline_errno)
{
	return split_cmdline_errors[-split_cmdline_errno - 1];
}
