#define DISABLE_SIGN_COMPARE_WARNINGS

#include "test-tool.h"
#include "mem-pool.h"
#include "mergesort.h"
#include "strbuf.h"

static uint32_t minstd_rand(uint32_t *state)
{
	*state = (uint64_t)*state * 48271 % 2147483647;
	return *state;
}

struct line {
	char *text;
	struct line *next;
};

DEFINE_LIST_SORT(static, sort_lines, struct line, next);

static int compare_strings(const struct line *x, const struct line *y)
{
	return strcmp(x->text, y->text);
}

static int sort_stdin(void)
{
	struct line *lines;
	struct line **tail = &lines;
	struct strbuf sb = STRBUF_INIT;
	struct mem_pool lines_pool;
	char *p;

	strbuf_read(&sb, 0, 0);

	/*
	 * Split by newline, but don't create an item
	 * for the empty string after the last separator.
	 */
	if (sb.len && sb.buf[sb.len - 1] == '\n')
		strbuf_setlen(&sb, sb.len - 1);

	mem_pool_init(&lines_pool, 0);
	p = sb.buf;
	for (;;) {
		char *eol = strchr(p, '\n');
		struct line *line = mem_pool_alloc(&lines_pool, sizeof(*line));
		line->text = p;
		*tail = line;
		tail = &line->next;
		if (!eol)
			break;
		*eol = '\0';
		p = eol + 1;
	}
	*tail = NULL;

	sort_lines(&lines, compare_strings);

	while (lines) {
		puts(lines->text);
		lines = lines->next;
	}
	return 0;
}

static void dist_sawtooth(int *arr, int n, int m)
{
	int i;
	for (i = 0; i < n; i++)
		arr[i] = i % m;
}

static void dist_rand(int *arr, int n, int m)
{
	int i;
	uint32_t seed = 1;
	for (i = 0; i < n; i++)
		arr[i] = minstd_rand(&seed) % m;
}

static void dist_stagger(int *arr, int n, int m)
{
	int i;
	for (i = 0; i < n; i++)
		arr[i] = (i * m + i) % n;
}

static void dist_plateau(int *arr, int n, int m)
{
	int i;
	for (i = 0; i < n; i++)
		arr[i] = (i < m) ? i : m;
}

static void dist_shuffle(int *arr, int n, int m)
{
	int i, j, k;
	uint32_t seed = 1;
	for (i = j = 0, k = 1; i < n; i++)
		arr[i] = minstd_rand(&seed) % m ? (j += 2) : (k += 2);
}

#define DIST(name) { #name, dist_##name }

static struct dist {
	const char *name;
	void (*fn)(int *arr, int n, int m);
} dist[] = {
	DIST(sawtooth),
	DIST(rand),
	DIST(stagger),
	DIST(plateau),
	DIST(shuffle),
};

static const struct dist *get_dist_by_name(const char *name)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(dist); i++) {
	       if (!strcmp(dist[i].name, name))
		       return &dist[i];
	}
	return NULL;
}

static void mode_copy(int *arr UNUSED, int n UNUSED)
{
	/* nothing */
}

static void mode_reverse(int *arr, int n)
{
	int i, j;
	for (i = 0, j = n - 1; i < j; i++, j--)
		SWAP(arr[i], arr[j]);
}

static void mode_reverse_1st_half(int *arr, int n)
{
	mode_reverse(arr, n / 2);
}

static void mode_reverse_2nd_half(int *arr, int n)
{
	int half = n / 2;
	mode_reverse(arr + half, n - half);
}

static int compare_ints(const void *av, const void *bv)
{
	const int *ap = av, *bp = bv;
	int a = *ap, b = *bp;
	return (a > b) - (a < b);
}

static void mode_sort(int *arr, int n)
{
	QSORT(arr, n, compare_ints);
}

static void mode_dither(int *arr, int n)
{
	int i;
	for (i = 0; i < n; i++)
		arr[i] += i % 5;
}

static void unriffle(int *arr, int n, int *tmp)
{
	int i, j;
	COPY_ARRAY(tmp, arr, n);
	for (i = j = 0; i < n; i += 2)
		arr[j++] = tmp[i];
	for (i = 1; i < n; i += 2)
		arr[j++] = tmp[i];
}

static void unriffle_recursively(int *arr, int n, int *tmp)
{
	if (n > 1) {
		int half = n / 2;
		unriffle(arr, n, tmp);
		unriffle_recursively(arr, half, tmp);
		unriffle_recursively(arr + half, n - half, tmp);
	}
}

static void mode_unriffle(int *arr, int n)
{
	int *tmp;
	ALLOC_ARRAY(tmp, n);
	unriffle_recursively(arr, n, tmp);
	free(tmp);
}

static unsigned int prev_pow2(unsigned int n)
{
	unsigned int pow2 = 1;
	while (pow2 * 2 < n)
		pow2 *= 2;
	return pow2;
}

static void unriffle_recursively_skewed(int *arr, int n, int *tmp)
{
	if (n > 1) {
		int pow2 = prev_pow2(n);
		int rest = n - pow2;
		unriffle(arr + pow2 - rest, rest * 2, tmp);
		unriffle_recursively_skewed(arr, pow2, tmp);
		unriffle_recursively_skewed(arr + pow2, rest, tmp);
	}
}

static void mode_unriffle_skewed(int *arr, int n)
{
	int *tmp;
	ALLOC_ARRAY(tmp, n);
	unriffle_recursively_skewed(arr, n, tmp);
	free(tmp);
}

#define MODE(name) { #name, mode_##name }

static struct mode {
	const char *name;
	void (*fn)(int *arr, int n);
} mode[] = {
	MODE(copy),
	MODE(reverse),
	MODE(reverse_1st_half),
	MODE(reverse_2nd_half),
	MODE(sort),
	MODE(dither),
	MODE(unriffle),
	MODE(unriffle_skewed),
};

static const struct mode *get_mode_by_name(const char *name)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(mode); i++) {
	       if (!strcmp(mode[i].name, name))
		       return &mode[i];
	}
	return NULL;
}

static int generate(int argc, const char **argv)
{
	const struct dist *dist = NULL;
	const struct mode *mode = NULL;
	int i, n, m, *arr;

	if (argc != 4)
		return 1;

	dist = get_dist_by_name(argv[0]);
	mode = get_mode_by_name(argv[1]);
	n = strtol(argv[2], NULL, 10);
	m = strtol(argv[3], NULL, 10);
	if (!dist || !mode)
		return 1;

	ALLOC_ARRAY(arr, n);
	dist->fn(arr, n, m);
	mode->fn(arr, n);
	for (i = 0; i < n; i++)
		printf("%08x\n", arr[i]);
	free(arr);
	return 0;
}

static struct stats {
	int get_next, set_next, compare;
} stats;

struct number {
	int value, rank;
	struct number *next;
};

DEFINE_LIST_SORT_DEBUG(static, sort_numbers, struct number, next,
		       stats.get_next++, stats.set_next++);

static int compare_numbers(const struct number *an, const struct number *bn)
{
	int a = an->value, b = bn->value;
	stats.compare++;
	return (a > b) - (a < b);
}

static void clear_numbers(struct number *list)
{
	while (list) {
		struct number *next = list->next;
		free(list);
		list = next;
	}
}

static int test(const struct dist *dist, const struct mode *mode, int n, int m)
{
	int *arr;
	size_t i;
	struct number *curr, *list, **tail;
	int is_sorted = 1;
	int is_stable = 1;
	const char *verdict;
	int result = -1;

	ALLOC_ARRAY(arr, n);
	dist->fn(arr, n, m);
	mode->fn(arr, n);
	for (i = 0, tail = &list; i < n; i++) {
		curr = xmalloc(sizeof(*curr));
		curr->value = arr[i];
		curr->rank = i;
		*tail = curr;
		tail = &curr->next;
	}
	*tail = NULL;

	stats.get_next = stats.set_next = stats.compare = 0;
	sort_numbers(&list, compare_numbers);

	QSORT(arr, n, compare_ints);
	for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) {
		if (arr[i] != curr->value)
			is_sorted = 0;
		if (curr->next && curr->value == curr->next->value &&
		    curr->rank >= curr->next->rank)
			is_stable = 0;
	}
	if (i < n) {
		verdict = "too short";
	} else if (curr) {
		verdict = "too long";
	} else if (!is_sorted) {
		verdict = "not sorted";
	} else if (!is_stable) {
		verdict = "unstable";
	} else {
		verdict = "OK";
		result = 0;
	}

	printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n",
	       dist->name, mode->name, n, m, stats.get_next, stats.set_next,
	       stats.compare, verdict);

	clear_numbers(list);
	free(arr);

	return result;
}

/*
 * A version of the qsort certification program from "Engineering a Sort
 * Function" by Bentley and McIlroy, Software—Practice and Experience,
 * Volume 23, Issue 11, 1249–1265 (November 1993).
 */
static int run_tests(int argc, const char **argv)
{
	const char *argv_default[] = { "100", "1023", "1024", "1025" };
	if (!argc)
		return run_tests(ARRAY_SIZE(argv_default), argv_default);
	printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n",
	       "distribut", "mode", "n", "m", "get_next", "set_next",
	       "compare", "verdict");
	while (argc--) {
		int i, j, m, n = strtol(*argv++, NULL, 10);
		for (i = 0; i < ARRAY_SIZE(dist); i++) {
			for (j = 0; j < ARRAY_SIZE(mode); j++) {
				for (m = 1; m < 2 * n; m *= 2) {
					if (test(&dist[i], &mode[j], n, m))
						return 1;
				}
			}
		}
	}
	return 0;
}

int cmd__mergesort(int argc, const char **argv)
{
	int i;
	const char *sep;

	if (argc == 6 && !strcmp(argv[1], "generate"))
		return generate(argc - 2, argv + 2);
	if (argc == 2 && !strcmp(argv[1], "sort"))
		return sort_stdin();
	if (argc > 1 && !strcmp(argv[1], "test"))
		return run_tests(argc - 2, argv + 2);
	fprintf(stderr, "usage: test-tool mergesort generate <distribution> <mode> <n> <m>\n");
	fprintf(stderr, "   or: test-tool mergesort sort\n");
	fprintf(stderr, "   or: test-tool mergesort test [<n>...]\n");
	fprintf(stderr, "\n");
	for (i = 0, sep = "distributions: "; i < ARRAY_SIZE(dist); i++, sep = ", ")
		fprintf(stderr, "%s%s", sep, dist[i].name);
	fprintf(stderr, "\n");
	for (i = 0, sep = "modes: "; i < ARRAY_SIZE(mode); i++, sep = ", ")
		fprintf(stderr, "%s%s", sep, mode[i].name);
	fprintf(stderr, "\n");
	return 129;
}
