/*
 * QuickSort
 *
 * Author: Jeffrey Stedfast <fejj@novell.com>
 *
 * (C) 2011 Novell, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "config.h"
#include <stdlib.h>
#include <glib.h>

/* Any segment <= this threshold will be sorted using insertion
 * sort. OpenBSD seems to use a value of 7 so we'll go with that for
 * now... */
#define MAX_THRESHOLD 7

#define STACK_SIZE (8 * sizeof (size_t))

typedef struct _QSortStack {
	char *array;
	size_t count;
} QSortStack;

#define QSORT_PUSH(sp, a, c) ((sp)->array = (char*)(a), (sp)->count = (c), (sp)++)
#define QSORT_POP(sp, a, c) ((sp)--, (a) = (sp)->array, (c) = (sp)->count)

#define SWAPTYPE(TYPE, a, b) {              \
	gssize __n = size / sizeof (TYPE);  \
	TYPE *__a = (TYPE *) (a);           \
	TYPE *__b = (TYPE *) (b);           \
	TYPE t;                             \
	                                    \
	do {                                \
		t = *__a;                   \
		*__a++ = *__b;              \
		*__b++ = t;                 \
	} while (--__n > 0);                \
}

#define SWAPBYTE(a, b) SWAPTYPE(char, (a), (b))
#define SWAPLONG(a, b) SWAPTYPE(long, (a), (b))
#define SWAP(a, b) if (swaplong) SWAPLONG((a), (b)) else SWAPBYTE((a), (b))

/* check if we can swap by longs rather than bytes by making sure that
 * memory is properly aligned and that the element size is a multiple
 * of sizeof (long) */
#define SWAP_INIT() swaplong = (((char *) base) - ((char *) 0)) % sizeof (long) == 0 && (size % sizeof (long)) == 0

void
g_qsort_with_data (gpointer base, size_t nmemb, size_t size, GCompareDataFunc compare, gpointer user_data)
{
	QSortStack stack[STACK_SIZE], *sp;
	char *i, *k, *mid;
	size_t n, n1, n2;
	char *lo, *hi;
	int swaplong;
	
	if (nmemb <= 1)
		return;
	
	SWAP_INIT ();
	
	/* initialize our stack */
	sp = stack;
	QSORT_PUSH (sp, base, nmemb);
	
	do {
		QSORT_POP (sp, lo, n);
		
		hi = lo + (n - 1) * size;
		
		if (n < MAX_THRESHOLD) {
			/* switch to insertion sort */
			for (i = lo + size; i <= hi; i += size)
				for (k = i; k > lo && compare (k - size, k, user_data) > 0; k -= size)
					SWAP (k - size, k);
			
			continue;
		}
		
		/* calculate the middle element */
		mid = lo + (n / 2) * size;
		
		/* once we re-order the lo, mid, and hi elements to be in
		 * ascending order, we'll use mid as our pivot. */
		if (compare (mid, lo, user_data) < 0) {
			SWAP (mid, lo);
		}
		
		if (compare (hi, mid, user_data) < 0) {
			SWAP (mid, hi);
			if (compare (mid, lo, user_data) < 0) {
				SWAP (mid, lo);
			}
		}
		
		/* since we've already guaranteed that lo <= mid and mid <= hi,
		 * we can skip comparing them again */
		i = lo + size;
		k = hi - size;
		
		do {
			/* find the first element with a value > pivot value */
			while (i < k && compare (i, mid, user_data) <= 0)
				i += size;
			
			/* find the last element with a value <= pivot value */
			while (k >= i && compare (mid, k, user_data) < 0)
				k -= size;
			
			if (k <= i)
				break;
			
			SWAP (i, k);
			
			/* make sure we keep track of our pivot element */
			if (mid == i) {
				mid = k;
			} else if (mid == k) {
				mid = i;
			}
			
			i += size;
			k -= size;
		} while (1);
		
		if (k != mid) {
			/* swap the pivot with the last element in the first partition */
			SWAP (mid, k);
		}
		
		/* calculate segment sizes */
		n2 = (hi - k) / size;
		n1 = (k - lo) / size;
		
		/* push our partitions onto the stack, largest first
		 * (to make sure we don't run out of stack space) */
		if (n2 > n1) {
			if (n2 > 1) QSORT_PUSH (sp, k + size, n2);
			if (n1 > 1) QSORT_PUSH (sp, lo, n1);
		} else {
			if (n1 > 1) QSORT_PUSH (sp, lo, n1);
			if (n2 > 1) QSORT_PUSH (sp, k + size, n2);
		}
	} while (sp > stack);
}
