/*****************************************************************************

	unsort - reorder files semi-randomly
	Copyright (C) 2008  Wessel Dankers <wsl@fruit.je>

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.

*****************************************************************************/

#include <stdlib.h>
#include <math.h>

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#include "merge.h"

const merge_t merge_0 = {0};

static uint32_t seed = 0;

void merge_seed(uint32_t s) {
	seed = s;
}

static uint32_t u32reverse(uint32_t i) {
	i = (i & 0xAAAAAAAA) >> 1 | (i & 0x55555555) << 1;
	i = (i & 0xCCCCCCCC) >> 2 | (i & 0x33333333) << 2;
	i = (i & 0xF0F0F0F0) >> 4 | (i & 0x0F0F0F0F) << 4;
	i = (i & 0xFF00FF00) >> 8 | (i & 0x00FF00FF) << 8;
	return (i << 16) | (i >> 16);
}

static int merge_cmp(merge_t *a, merge_t *b, int shuffle) {
	uint64_t av, bv;
	uint32_t at, bt, ar, br, ac, bc;
	ac = a->cursor;
	bc = b->cursor;
	at = a->count;
	bt = b->count;
	if(ac == at)
		return bc == bt ? 0 : 1;
	if(bc == bt)
		return -1;
	ar = a->ratio;
	br = b->ratio;
	av = (UINT64_C(1) + UINT64_C(2) * (uint64_t)ac) * (uint64_t)br;
	bv = (UINT64_C(1) + UINT64_C(2) * (uint64_t)bc) * (uint64_t)ar;
	if(av < bv)
		return -1;
	if(av > bv)
		return 1;
	at = a->start;
	bt = b->start;
	if(shuffle) {
		at = u32reverse(at ^ seed);
		bt = u32reverse(bt ^ seed);
	}
	if(at < bt)
		return -1;
	if(at > bt)
		return 1;
	if(ac < bc)
		return -1;
	if(ac > bc)
		return 1;
	if(ar > br)
		return -1;
	if(ar < br)
		return 1;
	return a < b ? -1 : a > b;
}

static void merge_update(merge_t *mg, uint32_t count, uint32_t o, int shuffle) {
	merge_t m, *m1, *m2;
	uint32_t o1, o2;

	if(!count)
		return;

	m = mg[o];

	for(;;) {
		o1 = o * 2 + 1;
		if(o1 >= count)
			break;
		m1 = mg + o1;

		o2 = o * 2 + 2;
		if(o2 < count) {
			m2 = mg + o2;
			if(merge_cmp(m2, m1, shuffle) < 0) {
				o1 = o2;
				m1 = m2;
			}
		}

		if(merge_cmp(&m, m1, shuffle) < 0)
			break;

		mg[o] = *m1;
		*m1 = m;
		o = o1;
	}
}

void merge(merge_t *mg, uint32_t count, uint32_t *src, uint32_t *dst, int shuffle) {
	uint32_t cur, u = count / 2;

	if(!mg || !u)
		return;

	do merge_update(mg, count, u, shuffle);
		while(u--);

	u = 0;
	for(;;) {
		cur = mg->cursor;
		if(cur == mg->count)
			break;
		dst[mg->start + cur] = src ? *src++ : u++;
		mg->cursor = cur + 1;
		merge_update(mg, count, 0, shuffle);
	}
}

static int merge_sort_cmp(const merge_t *a, const merge_t *b) {
	uint32_t at, bt;
	at = a->start;
	bt = b->start;
	return at < bt ? -1 : at > bt;
}

static void merge_sort_update(merge_t *mg, uint32_t count, uint32_t o) {
	merge_t mm, *m, *m1, *m2;
	uint32_t o1, o2;

	if(!count)
		return;

	m = mg + o;
	mm = *m;

	for(;;) {
		o1 = o * 2 + 1;
		if(o1 >= count)
			break;
		m1 = mg + o1;

		o2 = o * 2 + 2;
		if(o2 < count) {
			m2 = mg + o2;
			if(merge_sort_cmp(m1, m2) < 0) {
				o1 = o2;
				m1 = m2;
			}
		}

		if(merge_sort_cmp(m1, &mm) <= 0)
			break;

		*m = *m1;
		o = o1;
		m = m1;
	}

	*m = mm;
}

void merge_sort(merge_t *mg, uint32_t count) {
	uint32_t u = count / 2;
	merge_t mm;

	if(!mg || !u)
		return;

	do merge_sort_update(mg, count, u);
		while(u--);

	while(count-- > 1) {
		mm = mg[0];
		mg[0] = mg[count];
		mg[count] = mm;
		merge_sort_update(mg, count, 0);
	}
}
