/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 2017 Oracle and/or its affiliates.  All rights reserved.
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/btree.h"

static int __db_quicksort __P((DB *, DBT *, DBT *, u_int32_t *, u_int32_t *,
		u_int32_t *, u_int32_t *, u_int32_t));

/*
 * __db_compare_both --
 *	Use the comparison functions from db to compare akey and bkey, and if
 *	DB_DUPSORT adata and bdata.
 *
 * PUBLIC: int __db_compare_both __P((DB *, const DBT *, const DBT *,
 * PUBLIC:   const DBT *, const DBT *));
 */
int
__db_compare_both(db, akey, adata, bkey, bdata)
	DB *db;
	const DBT *akey;
	const DBT *adata;
	const DBT *bkey;
	const DBT *bdata;
{
	BTREE *t;
	int cmp;

	t = (BTREE *)db->bt_internal;

	cmp = t->bt_compare(db, akey, bkey, NULL);
	if (cmp != 0) return cmp;
	if (!F_ISSET(db, DB_AM_DUPSORT))
	    return (0);

	if (adata == 0) return bdata == 0 ? 0 : -1;
	if (bdata == 0) return 1;

#ifdef HAVE_COMPRESSION
	if (DB_IS_COMPRESSED(db))
		return t->compress_dup_compare(db, adata, bdata, NULL);
#endif
	return db->dup_compare(db, adata, bdata, NULL);
}

#define	DB_SORT_SWAP(a, ad, b, bd)					\
do {									\
	tmp = (a)[0]; (a)[0] = (b)[0]; (b)[0] = tmp;			\
	tmp = (a)[-1]; (a)[-1] = (b)[-1]; (b)[-1] = tmp;		\
	if (data != NULL) {						\
		tmp = (ad)[0]; (ad)[0] = (bd)[0]; (bd)[0] = tmp;	\
		tmp = (ad)[-1]; (ad)[-1] = (bd)[-1]; (bd)[-1] = tmp;	\
	}								\
} while (0)

#define	DB_SORT_LOAD_DBT(a, ad, aptr, adptr)				\
do {									\
	(a).data = (u_int8_t*)key->data + (aptr)[0];			\
	(a).size = (aptr)[-1];						\
	if (data != NULL) {						\
		(ad).data = (u_int8_t*)data->data + (adptr)[0];		\
		(ad).size = (adptr)[-1];				\
	}								\
} while (0)

#define	DB_SORT_COMPARE(a, ad, b, bd) (data != NULL ?			\
	__db_compare_both(db, &(a), &(ad), &(b), &(bd)) :		\
	__db_compare_both(db, &(a), 0, &(b), 0))

#define	DB_SORT_STACKSIZE 32

/*
 * __db_quicksort --
 *	The quicksort implementation for __db_sort_multiple() and
 *	__db_sort_multiple_key().
 */
static int
__db_quicksort(db, key, data, kstart, kend, dstart, dend, size)
	DB *db;
	DBT *key, *data;
	u_int32_t *kstart, *kend, *dstart, *dend;
	u_int32_t size;
{
	int ret, cmp;
	u_int32_t tmp, len;
	u_int32_t *kptr, *dptr, *kl, *dl, *kr, *dr;
	DBT a, ad, b, bd, m, md;
	ENV *env;

	struct DB_SORT_quicksort_stack {
		u_int32_t *kstart;
		u_int32_t *kend;
		u_int32_t *dstart;
		u_int32_t *dend;
	} stackbuf[DB_SORT_STACKSIZE], *stack;
	u_int32_t soff, slen;

	ret = 0;
	env = db->env;

	memset(&a, 0, sizeof(DBT));
	memset(&ad, 0, sizeof(DBT));
	memset(&b, 0, sizeof(DBT));
	memset(&bd, 0, sizeof(DBT));
	memset(&m, 0, sizeof(DBT));
	memset(&md, 0, sizeof(DBT));

	/* NB end is smaller than start */

	stack = stackbuf;
	soff = 0;
	slen = DB_SORT_STACKSIZE;

 start:
	if (kend >= kstart) goto pop;

	/* If there's only one value, it's already sorted */
	len = (u_int32_t)(kstart - kend) / size;
	if (len == 1) goto pop;

	DB_SORT_LOAD_DBT(a, ad, kstart, dstart);
	DB_SORT_LOAD_DBT(b, bd, kend + size, dend + size);

	if (len == 2) {
		/* Special case the sorting of two value sequences */
		if (DB_SORT_COMPARE(a, ad, b, bd) > 0) {
			DB_SORT_SWAP(kstart, dstart, kend + size,
				dend + size);
		}
		goto pop;
	}

	kptr = kstart - (len / 2) * size;
	dptr = dstart - (len / 2) * size;
	DB_SORT_LOAD_DBT(m, md, kptr, dptr);

	/* Find the median of three */
	if (DB_SORT_COMPARE(a, ad, b, bd) < 0) {
		if (DB_SORT_COMPARE(m, md, a, ad) < 0) {
			/* m < a < b */
			if (len == 3) {
				DB_SORT_SWAP(kstart, dstart, kptr, dptr);
				goto pop;
			}
			DB_SORT_SWAP(kstart, dstart, kend + size, dend + size);
		} else if (DB_SORT_COMPARE(m, md, b, bd) < 0) {
			/* a <= m < b */
			if (len == 3) {
				goto pop;
			}
			DB_SORT_SWAP(kptr, dptr, kend + size, dend + size);
		} else {
			/* a < b <= m */
			if (len == 3) {
				DB_SORT_SWAP(kptr, dptr, kend + size,
					dend + size);
				goto pop;
			}
			/* Do nothing */
		}
	} else {
		if (DB_SORT_COMPARE(a, ad, m, md) < 0) {
			/* b <= a < m */
			DB_SORT_SWAP(kstart, dstart, kend + size,
			    dend + size);
			if (len == 3) {
				DB_SORT_SWAP(kptr, dptr, kend + size,
				    dend + size);
				goto pop;
			}
		} else if (DB_SORT_COMPARE(b, bd, m, md) < 0) {
			/* b < m <= a */
			if (len == 3) {
				DB_SORT_SWAP(kstart, dstart, kend + size,
					dend + size);
				goto pop;
			}
			DB_SORT_SWAP(kptr, dptr, kend + size, dend + size);
		} else {
			/* m <= b <= a */
			if (len == 3) {
				DB_SORT_SWAP(kstart, dstart, kptr, dptr);
				DB_SORT_SWAP(kptr, dptr, kend + size,
					dend + size);
				goto pop;
			}
			/* Do nothing */
		}
	}

	/* partition */
	DB_SORT_LOAD_DBT(b, bd, kend + size, dend + size);
	kl = kstart;
	dl = dstart;
	kr = kend + size;
	dr = dend + size;
	kptr = kstart;
	dptr = dstart;
	while (kptr >= kr) {
		DB_SORT_LOAD_DBT(a, ad, kptr, dptr);
		cmp = DB_SORT_COMPARE(a, ad, b, bd);
		if (cmp < 0) {
			DB_SORT_SWAP(kl, dl, kptr, dptr);
			kl -= size;
			dl -= size;
			kptr -= size;
			dptr -= size;
		} else if (cmp > 0) {
			DB_SORT_SWAP(kr, dr, kptr, dptr);
			kr += size;
			dr += size;
		} else {
			kptr -= size;
			dptr -= size;
		}
	}

	if (soff == slen) {
		/* Grow the stack */
		slen = slen * 2;
		if (stack == stackbuf) {
			ret = __os_malloc(env, slen *
				sizeof(struct DB_SORT_quicksort_stack), &stack);
			if (ret != 0) goto error;
			memcpy(stack, stackbuf, soff *
				sizeof(struct DB_SORT_quicksort_stack));
		} else {
			ret = __os_realloc(env, slen *
				sizeof(struct DB_SORT_quicksort_stack), &stack);
			if (ret != 0) goto error;
		}
	}

	/* divide and conquer */
	stack[soff].kstart = kr - size;
	stack[soff].kend = kend;
	stack[soff].dstart = dr - size;
	stack[soff].dend = dend;
	++soff;

	kend = kl;
	dend = dl;

	goto start;

 pop:
	if (soff != 0) {
		--soff;
		kstart = stack[soff].kstart;
		kend = stack[soff].kend;
		dstart = stack[soff].dstart;
		dend = stack[soff].dend;
		goto start;
	}

 error:
	if (stack != stackbuf)
		__os_free(env, stack);

	return (ret);
}

#undef DB_SORT_SWAP
#undef DB_SORT_LOAD_DBT

/*
 * __db_sort_multiple --
 *	If flags == DB_MULTIPLE_KEY, sorts a DB_MULTIPLE_KEY format DBT using
 *	the BTree comparison function and duplicate comparison function.
 *
 *	If flags == DB_MULTIPLE, sorts one or two DB_MULTIPLE format DBTs using
 *	the BTree comparison function and duplicate comparison function. Will
 *	assume key and data specifies pairs of key/data to sort together. If
 *	data is NULL, will just sort key according to the btree comparison
 *	function.
 *
 *	Uses an in-place quicksort algorithm, with median of three for the pivot
 *	point.
 *
 * PUBLIC: int __db_sort_multiple __P((DB *, DBT *, DBT *, u_int32_t));
 */
int
__db_sort_multiple(db, key, data, flags)
	DB *db;
	DBT *key, *data;
	u_int32_t flags;
{
	u_int32_t *kstart, *kend, *dstart, *dend;

	/* TODO: sanity checks on the DBTs */
	/* DB_ILLEGAL_METHOD(db, DB_OK_BTREE); */

	kstart = (u_int32_t*)((u_int8_t *)key->data + key->ulen) - 1;

	switch (flags) {
	case DB_MULTIPLE:
		if (data != NULL)
			dstart = (u_int32_t*)((u_int8_t *)data->data +
				data->ulen) - 1;
		else
			dstart = kstart;

		/* Find the end */
		for (kend = kstart, dend = dstart;
		    *kend != (u_int32_t)-1 && *dend != (u_int32_t)-1;
		    kend -= 2, dend -= 2)
			;

		return (__db_quicksort(db, key, data, kstart, kend, dstart,
			dend, 2));
	case DB_MULTIPLE_KEY:
		/* Find the end */
		for (kend = kstart; *kend != (u_int32_t)-1; kend -= 4)
			;

		return (__db_quicksort(db, key, key, kstart, kend, kstart - 2,
			kend - 2, 4));
	default:
		return (__db_ferr(db->env, "DB->sort_multiple", 0));
	}
}
