#include "git-compat-util.h"
#include "string-list.h"

void string_list_init_nodup(struct string_list *list)
{
	struct string_list blank = STRING_LIST_INIT_NODUP;
	memcpy(list, &blank, sizeof(*list));
}

void string_list_init_dup(struct string_list *list)
{
	struct string_list blank = STRING_LIST_INIT_DUP;
	memcpy(list, &blank, sizeof(*list));
}

/* if there is no exact match, point to the index where the entry could be
 * inserted */
static size_t get_entry_index(const struct string_list *list, const char *string,
			      bool *exact_match)
{
	size_t left = 0, right = list->nr;
	compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;

	while (left < right) {
		size_t middle = left + (right - left) / 2;
		int compare = cmp(string, list->items[middle].string);
		if (compare < 0)
			right = middle;
		else if (compare > 0)
			left = middle + 1;
		else {
			if (exact_match)
				*exact_match = true;
			return middle;
		}
	}

	if (exact_match)
		*exact_match = false;
	return right;
}

static size_t add_entry(struct string_list *list, const char *string)
{
	bool exact_match;
	size_t index = get_entry_index(list, string, &exact_match);

	if (exact_match)
		return index;

	ALLOC_GROW(list->items, list->nr+1, list->alloc);
	if (index < list->nr)
		MOVE_ARRAY(list->items + index + 1, list->items + index,
			   list->nr - index);
	list->items[index].string = list->strdup_strings ?
		xstrdup(string) : (char *)string;
	list->items[index].util = NULL;
	list->nr++;

	return index;
}

struct string_list_item *string_list_insert(struct string_list *list, const char *string)
{
	size_t index = add_entry(list, string);

	return list->items + index;
}

void string_list_remove(struct string_list *list, const char *string,
			int free_util)
{
	bool exact_match;
	int i = get_entry_index(list, string, &exact_match);

	if (exact_match) {
		if (list->strdup_strings)
			free(list->items[i].string);
		if (free_util)
			free(list->items[i].util);

		list->nr--;
		MOVE_ARRAY(list->items + i, list->items + i + 1, list->nr - i);
	}
}

bool string_list_has_string(const struct string_list *list, const char *string)
{
	bool exact_match;
	get_entry_index(list, string, &exact_match);
	return exact_match;
}

size_t string_list_find_insert_index(const struct string_list *list, const char *string,
				     bool *exact_match)
{
	return get_entry_index(list, string, exact_match);
}

struct string_list_item *string_list_lookup(struct string_list *list, const char *string)
{
	bool exact_match;
	size_t i = get_entry_index(list, string, &exact_match);
	if (!exact_match)
		return NULL;
	return list->items + i;
}

void string_list_remove_duplicates(struct string_list *list, int free_util)
{
	if (list->nr > 1) {
		size_t dst = 1;
		compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
		for (size_t src = 1; src < list->nr; src++) {
			if (!cmp(list->items[dst - 1].string, list->items[src].string)) {
				if (list->strdup_strings)
					free(list->items[src].string);
				if (free_util)
					free(list->items[src].util);
			} else
				list->items[dst++] = list->items[src];
		}
		list->nr = dst;
	}
}

int for_each_string_list(struct string_list *list,
			 string_list_each_func_t fn, void *cb_data)
{
	int ret = 0;
	for (size_t i = 0; i < list->nr; i++)
		if ((ret = fn(&list->items[i], cb_data)))
			break;
	return ret;
}

void filter_string_list(struct string_list *list, int free_util,
			string_list_each_func_t want, void *cb_data)
{
	size_t dst = 0;
	for (size_t src = 0; src < list->nr; src++) {
		if (want(&list->items[src], cb_data)) {
			list->items[dst++] = list->items[src];
		} else {
			if (list->strdup_strings)
				free(list->items[src].string);
			if (free_util)
				free(list->items[src].util);
		}
	}
	list->nr = dst;
}

static int item_is_not_empty(struct string_list_item *item, void *data UNUSED)
{
	return *item->string != '\0';
}

void string_list_remove_empty_items(struct string_list *list, int free_util)
{
	filter_string_list(list, free_util, item_is_not_empty, NULL);
}

void string_list_clear(struct string_list *list, int free_util)
{
	if (list->items) {
		if (list->strdup_strings) {
			for (size_t i = 0; i < list->nr; i++)
				free(list->items[i].string);
		}
		if (free_util) {
			for (size_t i = 0; i < list->nr; i++)
				free(list->items[i].util);
		}
		free(list->items);
	}
	list->items = NULL;
	list->nr = list->alloc = 0;
}

void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)
{
	if (list->items) {
		if (clearfunc) {
			for (size_t i = 0; i < list->nr; i++)
				clearfunc(list->items[i].util, list->items[i].string);
		}
		if (list->strdup_strings) {
			for (size_t i = 0; i < list->nr; i++)
				free(list->items[i].string);
		}
		free(list->items);
	}
	list->items = NULL;
	list->nr = list->alloc = 0;
}

void string_list_setlen(struct string_list *list, size_t nr)
{
	if (list->strdup_strings)
		BUG("cannot setlen a string_list which owns its entries");
	if (nr > list->nr)
		BUG("cannot grow a string_list with setlen");
	list->nr = nr;
}

struct string_list_item *string_list_append_nodup(struct string_list *list,
						  char *string)
{
	struct string_list_item *retval;
	ALLOC_GROW(list->items, list->nr + 1, list->alloc);
	retval = &list->items[list->nr++];
	retval->string = string;
	retval->util = NULL;
	return retval;
}

struct string_list_item *string_list_append(struct string_list *list,
					    const char *string)
{
	return string_list_append_nodup(
			list,
			list->strdup_strings ? xstrdup(string) : (char *)string);
}

/*
 * Encapsulate the compare function pointer because ISO C99 forbids
 * casting from void * to a function pointer and vice versa.
 */
struct string_list_sort_ctx
{
	compare_strings_fn cmp;
};

static int cmp_items(const void *a, const void *b, void *ctx)
{
	struct string_list_sort_ctx *sort_ctx = ctx;
	const struct string_list_item *one = a;
	const struct string_list_item *two = b;
	return sort_ctx->cmp(one->string, two->string);
}

void string_list_sort(struct string_list *list)
{
	struct string_list_sort_ctx sort_ctx = {list->cmp ? list->cmp : strcmp};

	QSORT_S(list->items, list->nr, cmp_items, &sort_ctx);
}

struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
						     const char *string)
{
	struct string_list_item *item;
	compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;

	for_each_string_list_item(item, list)
		if (!cmp(string, item->string))
			return item;
	return NULL;
}

int unsorted_string_list_has_string(struct string_list *list,
				    const char *string)
{
	return unsorted_string_list_lookup(list, string) != NULL;
}

void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)
{
	if (list->strdup_strings)
		free(list->items[i].string);
	if (free_util)
		free(list->items[i].util);
	list->items[i] = list->items[list->nr-1];
	list->nr--;
}

/*
 * append a substring [p..end] to list; return number of things it
 * appended to the list.
 */
static int append_one(struct string_list *list,
		      const char *p, const char *end,
		      int in_place, unsigned flags)
{
	if (!end)
		end = p + strlen(p);

	if ((flags & STRING_LIST_SPLIT_TRIM)) {
		/* rtrim */
		for (; p < end; end--)
			if (!isspace(end[-1]))
				break;
	}

	if ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p))
		return 0;

	if (in_place) {
		*((char *)end) = '\0';
		string_list_append(list, p);
	} else {
		string_list_append_nodup(list, xmemdupz(p, end - p));
	}
	return 1;
}

/*
 * Unfortunately this cannot become a public interface, as _in_place()
 * wants to have "const char *string" while the other variant wants to
 * have "char *string" for type safety.
 *
 * This accepts "const char *string" to allow both wrappers to use it;
 * it internally casts away the constness when in_place is true by
 * taking advantage of strpbrk() that takes a "const char *" arg and
 * returns "char *" pointer into that const string.  Yucky but works ;-).
 */
static int split_string(struct string_list *list, const char *string, const char *delim,
			int maxsplit, int in_place, unsigned flags)
{
	int count = 0;
	const char *p = string;

	if (in_place && list->strdup_strings)
		BUG("string_list_split_in_place() called with strdup_strings");
	else if (!in_place && !list->strdup_strings)
		BUG("string_list_split() called without strdup_strings");

	for (;;) {
		const char *end;

		if (flags & STRING_LIST_SPLIT_TRIM) {
			/* ltrim */
			while (*p && isspace(*p))
				p++;
		}

		if (0 <= maxsplit && maxsplit <= count)
			end = NULL;
		else
			end = strpbrk(p, delim);

		count += append_one(list, p, end, in_place, flags);

		if (!end)
			return count;
		p = end + 1;
	}
}

int string_list_split(struct string_list *list, const char *string,
		      const char *delim, int maxsplit)
{
	return split_string(list, string, delim, maxsplit, 0, 0);
}

int string_list_split_in_place(struct string_list *list, char *string,
			       const char *delim, int maxsplit)
{
	return split_string(list, string, delim, maxsplit, 1, 0);
}

int string_list_split_f(struct string_list *list, const char *string,
			const char *delim, int maxsplit, unsigned flags)
{
	return split_string(list, string, delim, maxsplit, 0, flags);
}

int string_list_split_in_place_f(struct string_list *list, char *string,
			       const char *delim, int maxsplit, unsigned flags)
{
	return split_string(list, string, delim, maxsplit, 1, flags);
}
