/**
 * \file
 * Quicksort.
 *
 * Copyright (C) 2013 Xamarin Inc
 *
 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
 */

#include "config.h"

#ifdef HAVE_SGEN_GC

#include "sgen/sgen-gc.h"

#define ELEM(i) \
	(((unsigned char*)array) + ((i) * element_size))
#define SET(i,j) \
	do memmove ((i), (j), element_size); while (0)
#define SWAP(i,j) \
	do { \
		size_t __i = (i), __j = (j); \
		if (__i != __j) { \
			SET (swap_tmp, ELEM (__i)); \
			SET (ELEM (__i), ELEM (__j)); \
			SET (ELEM (__j), swap_tmp); \
		} \
	} while (0)

static void
sgen_qsort_rec (
	void *const array,
	const size_t element_size,
	int (*compare) (const void *, const void *),
	ssize_t begin,
	ssize_t end,
	unsigned char *const pivot_tmp,
	unsigned char *const swap_tmp)
{
	ssize_t left, right, mid, pivot;
	while (begin < end) {
		left = begin;
		right = end;
		mid = begin + (end - begin) / 2;

		/* Choose median of 3 as pivot and pre-sort to avoid O(n^2) case.
		 *
		 * L --o--o----->
		 *     |  |
		 * M --o--|--o-->
		 *        |  |
		 * R -----o--o-->
		 */
		if (compare (ELEM (mid), ELEM (left)) < 0)
			SWAP (mid, left);
		if (compare (ELEM (right), ELEM (left)) < 0)
			SWAP (right, left);
		if (compare (ELEM (right), ELEM (mid)) < 0)
			SWAP (right, mid);
		pivot = mid;
		SET (pivot_tmp, ELEM (pivot));

		/* Partition. */
		for (;;) {
			while (left <= right && compare (ELEM (left), pivot_tmp) <= 0)
				++left;
			while (left <= right && compare (ELEM (right), pivot_tmp) > 0)
				--right;
			if (left > right)
				break;
			SWAP (left, right);
			if (pivot == right)
				pivot = left;
			++left;
			--right;
		}
		SET (ELEM (pivot), ELEM (right));
		SET (ELEM (right), pivot_tmp);
		--right;

		/* Recursively sort shorter partition, loop on longer partition. */
		if (right - begin < end - left) {
			sgen_qsort_rec (
				array,
				element_size,
				compare,
				begin,
				right,
				pivot_tmp,
				swap_tmp);
			begin = left;
		} else {
			sgen_qsort_rec (
				array,
				element_size,
				compare,
				left,
				end,
				pivot_tmp,
				swap_tmp);
			end = right;
		}
	}
}

void sgen_qsort (
	void *const array,
	const size_t count,
	const size_t element_size,
	int (*compare) (const void *, const void *))
{
	unsigned char *pivot_tmp = g_newa (unsigned char, element_size);
	unsigned char *swap_tmp = g_newa (unsigned char, element_size);
	sgen_qsort_rec (
		array,
		element_size,
		compare,
		0,
		(ssize_t)count - 1,
		pivot_tmp,
		swap_tmp);
}

#endif
