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

	unsort - reorder files semi-randomly
	Copyright (C) 2007, 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 <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include "mt19937ar.h"
#include "msort.h"
#include "shuffle.h"

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 void u32swap(uint32_t *a, uint32_t *b) {
	uint32_t u;
	u = *a;
	*a = *b;
	*b = u;
}

static uint32_t sqrt32(uint32_t u) {
	uint32_t r, p;

	if(u < 2)
		return u;

	r = p = u / 2;

	do {
		p = r;
		r = (p + u / p) / 2;
	} while(r < p);

	return p;
}

static uint32_t seed = 0;
static uint32_t *tmp = NULL;

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

void shuffle_tmp(uint32_t *t) {
	tmp = t;
}

void shuffle_init(uint32_t *a, uint32_t count) {
	uint32_t u;
	for(u = 0; u < count; u++)
		a[u] = u;
}

void shuffle_none(uint32_t *src, uint32_t *dst, uint32_t count) {
	if(!count)
		return;

	if(src) {
		if(src != dst)
			memcpy(dst, src, count * sizeof *src);
	} else {
		shuffle_init(dst, count);
	}
}

void shuffle_random(uint32_t *src, uint32_t *dst, uint32_t count) {
	uint32_t u;

	if(!count)
		return;

	if(src != dst) {
		if(src)
			memcpy(dst, src, count * sizeof *src);
		else
			shuffle_init(dst, count);
	}
	for(u = count - 1; u > 0; u--)
		u32swap(dst + mt_genrand32_bounded(0, u + 1), dst + u);
}

void shuffle_bitreverse(uint32_t *src, uint32_t *dst, uint32_t count) {
	uint32_t u;

	if(!count)
		return;

	if(src == dst) {
		for(u = 0; u < count; u++)
			dst[u] = u32reverse(dst[u] ^ seed);
		src = NULL;
	} else {
		for(u = 0; u < count; u++)
			dst[u] = u32reverse(u ^ seed);
	}
	msort32(dst, count);
	if(src)
		for(u = 0; u < count; u++)
			dst[u] = src[u32reverse(dst[u]) ^ seed];
	else
		for(u = 0; u < count; u++)
			dst[u] = u32reverse(dst[u]) ^ seed;
}

static uint32_t rev(uint32_t r, uint32_t *a, uint32_t u) {
	return (u % r) * r + a[u / r];
}

static uint32_t fwd(uint32_t r, uint32_t *a, uint32_t u) {
	return a[u % r] * r + u / r;
}

static void shuffle_invert(uint32_t *src, uint32_t *dst, uint32_t count) {
	uint32_t u;
	for(u = 0; u < count; u++)
		dst[src[u]] = u;
}

void shuffle_sqrtbase(uint32_t *src, uint32_t *dst, uint32_t count) {
	uint32_t u, r;
	uint32_t *a, *b;

	if(!count)
		return;

	if(src == dst || !tmp) {
		shuffle_bitreverse(src, dst, count);
		return;
	}

	r = sqrt32(count);
	if(r * r < count)
		r++;

	a = tmp;
	b = a + r;

	shuffle_random(NULL, a, r);

	for(u = 0; u < count; u++)
			dst[u] = fwd(r, a, u);

	msort32(dst, count);
	shuffle_invert(a, b, r);
	if(src)
		for(u = 0; u < count; u++)
			dst[u] = src[rev(r, b, dst[u])];
	else
		for(u = 0; u < count; u++)
			dst[u] = rev(r, b, dst[u]);
}

const shuffle_algo_t shuffle_heuristic = shuffle_sqrtbase;
