#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define DEV_RANDOM	"/dev/random"
int read_size = 65536;

/* code taken from linux kernel */
#if __GNUC__ == 2 && __GNUC_MINOR__ < 96
#define __builtin_expect(x, expected_value) (x)
#endif
#ifndef __builtin_expect
#define __builtin_expect(x, expected_value) (x)
#endif
#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

void version(void)
{
	fprintf(stderr, "unsort v0.5, (c) 2003-2004 by folkert@vanheusden.com\n");
	fprintf(stderr, "Check www.vanheusden.com for new versions\n");
}

void usage(void)
{
	version();

	fprintf(stderr, "unsort [-options] filename\n");
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "	-v	show version\n");
	fprintf(stderr, "	-h	show usage\n");
	fprintf(stderr, "	-i	number of randomizing iterations\n");
	fprintf(stderr, "	-s	initial seed, default is the current time\n");
	fprintf(stderr, "	-d	delimiter\n");
}

int main(int argc, char *argv[])
{
	int fd = 0; /* initially, read from stdin */
	char *data = NULL, *pnt;
	int nbytesin = 0, nbyteslen = 0;
	int loop;
	char delimiter = '\n'; /* default */
	int npin = 0, nplen = 0;
	char **pstart = NULL;
	int n_iterations = 1;
	long int seed = 1;
	char seedset = 0;

	for(loop=1; loop<argc; loop++)
	{
		if (argv[loop][0] != '-')	/* filename! */
		{
			if (fd != 0)
			{
				fprintf(stderr, "you can select only one file to read from\n");
				return 1;
			}

			fd = open(argv[loop], O_RDONLY);
			if (fd == -1)
			{
				fprintf(stderr, "failed to open file %s: %s\n", argv[loop], strerror(errno));
				return 2;
			}
		}
		else if (strcmp(argv[loop], "-v") == 0)
		{
			version();
		}
		else if (strcmp(argv[loop], "-h") == 0)
		{
			usage();
			return 0;
		}
		else if (strcmp(argv[loop], "-i") == 0)
		{
			n_iterations = atoi(argv[++loop]);
			if (n_iterations < 1)
			{
				fprintf(stderr, "number of itterations must be at least 1\n");
				return 3;
			}
		}
		else if (strcmp(argv[loop], "-s") == 0)
		{
			seed = atoi(argv[++loop]);
			seedset = 1;
		}
		else if (strcmp(argv[loop], "-d") == 0)
		{
			delimiter = argv[++loop][0];
		}
	}

	if (seedset == 0)
	{
		int rfd = open(DEV_RANDOM, O_RDONLY);
		int n_to_read = sizeof(seed);
		char *p = (char *)&seed;

		if (rfd == -1)
		{
			seed = time(NULL);
			fprintf(stderr, "Cannot open %s: %s (%d), using %d as a seed.\n", DEV_RANDOM, strerror(errno), errno, (int)seed);
		}
		else
		{
			while(n_to_read > 0)
			{
				int rc = read(rfd, p, n_to_read);
				if (rc == -1)
				{
					if (errno != EINTR && errno != EAGAIN)
					{
						fprintf(stderr, "Error reading from %s: %s (%d)!\n", DEV_RANDOM, strerror(errno), errno);
						return 1;
					}
				}
				else if (rc == 0)
				{
					fprintf(stderr, "Premature EOF while reading from %s!\n", DEV_RANDOM);
					return 1;
				}
				else
				{
					n_to_read -= rc;
					p += rc;
				}
			}

			close(rfd);
		}
	}

	seed = labs(seed);
	fprintf(stderr, "Using seed: %ld\n", seed);
	srand48(seed);

	/* read until end */
	for(;;)
	{
		int rc;

		if (nbyteslen < (nbytesin + read_size))
		{
			nbyteslen = (nbyteslen * 2) + read_size + 1;

			data = (char *)realloc(data, nbyteslen);
			if (!data)
			{
				fprintf(stderr, "cannot grow buffer (out of memory)\n");
				return 2;
			}
		}

		rc = read(fd, &data[nbytesin], read_size);

		if (rc == -1)
		{
			if (errno != EINTR)
			{
				fprintf(stderr, "error reading: %s\n", strerror(errno));
				return 1;
			}
		}
		else if (rc == 0)
			break;
		else
		{
			nbytesin += rc;
		}
	}
	if (!nbytesin)
		return 0;

	data[nbytesin] = 0x00;

	/* find pointers */
	pnt = data;
	while(pnt)
	{
		if (npin == nplen)
		{
			nplen = (nplen * 2) + 2;
			pstart = (char **)realloc(pstart, sizeof(char *) * nplen);
			if (!pstart)
			{
				fprintf(stderr, "failed to allocate memory\n");
				return 2;
			}
		}

		if (likely(*pnt == delimiter))
		{
			*pnt = 0x00;
			pnt++;
			if (likely(*pnt != 0x00))
				pstart[npin++] = pnt;
		}
		else
		{
			pstart[npin++] = pnt;
		}

		pnt = strchr(pnt, delimiter);
	}

	/* 'sort' */
	for(loop=0; loop<n_iterations; loop++)
	{
		int index1, index2;

		for(index1=0; index1<npin; index1++)
		{
			int loop2;
			char *dummy = pstart[index1];

			for(loop2=0; loop2<10; loop2++)
			{
				index2 = lrand48() % npin;
				if (likely(index2 != index1))
					break;
			}

			pstart[index1] = pstart[index2];
			pstart[index2] = dummy;
		}
	}

	/* output */
	for(loop=0; loop<npin; loop++)
	{
		printf("%s%c", pstart[loop], delimiter);
	}

	/* clean-up */
	free(pstart);
	free(data);

	return 0;
}
