#include <stdlib.h>
#include <stdio.h>
#include <string.h>

long Random(long range) {
    if ( range > 0)
	return rand() % range;
    else
	return 0;
}

#define BUFF_HEIGHT 480
#define BUFF_WIDTH 640

#define min(a, b)  (((a) < (b)) ? (a) : (b)) 
#define max(a, b)  (((a) > (b)) ? (a) : (b)) 

#define DFLT_X_CENTER  (BUFF_WIDTH / 3)
#define DFLT_Y_CENTER  (BUFF_HEIGHT / 2 - 10)

#define DEFAULT_SPEED 100
#define DEFAULT_RAND   70
#define DEFAULT_BLEND   8

/*
// Square root for longegers.
// Returns floor(sqrt(n)).
*/
long longSqrt (long n) {
	long  root = 0;
	long  bit = 0x00008000;

	while (bit) {
		if ((root | bit) * (root | bit) <= n)
			root |= bit;
		bit >>= 1;
	}
	return root;
}

static long usageError (char *progPath) {
	char  *p = strrchr(progPath, '\\');

	p = p ? p+1 : progPath;

	printf ("Hurricane table generator for CTHUGHA-4-Winamp.\n");
	printf ("The generated table looks like a sattelite view of a hurricane or\n");
	printf ("Jupiter's red spot.\n");
	printf ("\n");
	printf ("Usage:  %s [-s[x][y]] [-c#:#] [-r] outfile.tab [speed [Randomness [Blend]]]\n", p);
	printf ("  Speed should be 30..300 (default %d)\n", DEFAULT_SPEED);
	printf ("  Randomness should be 0..100 (default %d)\n", DEFAULT_RAND);
	printf ("  Blend should be 0..255 (default %d)\n",DEFAULT_BLEND);
	printf ("  -sx slows down horizontal movements\n");
	printf ("  -sy slows down vertical movements\n");
	printf ("  -sxy slows down all movements\n");
	printf ("      Default is -sy.\n");
	printf ("  -c#:# sets the center of the spiral. Should be between 0:0 and\n");
	printf ("      %d:%d (default -c%d:%d).\n", BUFF_WIDTH-1, BUFF_HEIGHT-1,
	                                            DFLT_X_CENTER, DFLT_Y_CENTER);
	printf ("  -r reverses the direction of rotation.\n");
	return 1;
}

/*
// Look for a given command-line switch and remove it from argc/argv if
// found.  *val is set to polong to the string that follows the switch
// letter, or to NULL if not found.
// Decrement argc if a switch was found and removed.
// If the switch appears more than once, then all occurences are removed
// but only the last is taken longo account.
*/
static void eatSwitch (long *argc, char **argv, char switchLetter, char **val) {
	long  i, j;

	*val = NULL;
	i = 1;
	while (i < *argc) {
		if (argv[i][0] == '-'  &&  argv[i][1] == switchLetter) {
			*val = &argv[i][2];
			for (j = i+1; j < *argc; j++)
				argv[j-1] = argv[j];
			(*argc)--;
		} else
			i++;
	}
}

long main (long argc, char **argv) {
	char   *fileName, *p;
	FILE   *fp;
	long    xCenter, yCenter;
	long    x, y, dx, dy, map_x, map_y;
	long    speed, Randomness,Blend;
	bool   slowX = false;
	bool   slowY = true;
	bool   reverse;
	long lOffset, lFull, lBlend;

	eatSwitch (&argc, argv, 's', &p);
	if (p) {
		slowX = (strchr(p,'x') != NULL);
		slowY = (strchr(p,'y') != NULL);
	}

	eatSwitch (&argc, argv, 'c', &p);
	xCenter = p ? min(max(atoi(p),0),BUFF_WIDTH-1) : DFLT_X_CENTER;
	if (p)
		p = strchr (p, ':');
	if (p)
		p++;
	yCenter = p ? min(max(atoi(p),0),BUFF_HEIGHT-1) : DFLT_Y_CENTER;

	eatSwitch (&argc, argv, 'r', &p);
	reverse = (p != NULL);

	if (argc < 2  ||  argc > 5)
		return usageError (argv[0]);

	fileName = argv[1];
	speed = (argc > 2) ? atoi(argv[2]) : DEFAULT_SPEED;
	Randomness = (argc > 3) ? atoi(argv[3]) : DEFAULT_RAND;
	Blend = (argc > 4) ? atoi(argv[4]) : DEFAULT_BLEND;

	speed = min(max(speed,30),300);
	Randomness = min(max(Randomness,0),100);
	Blend = min(max(Blend,0),255);

	if ((fp = fopen(fileName, "wb")) == NULL) {
		printf ("*** Failed to create output file %s\n", fileName);
		return 2;
	}

	printf ("Generating table %s with speed=%d and Randomness=%d around %d:%d\n",
	        fileName, speed, Randomness, xCenter, yCenter);

	for (y = 0; y < BUFF_HEIGHT; y++) {
		printf ("Generating line %d of %d...\r", y + 1, BUFF_HEIGHT);

		for (x = 0; x < BUFF_WIDTH; x++) {
			long  speedFactor;
			long sp;

			if (Randomness == 0)
				sp = speed;
			else {
				speedFactor = Random(Randomness + 1) - Randomness / 3;
				sp = speed * (100L + speedFactor) / 100L;
			}

			dx = x - xCenter;
			dy = y - yCenter;

			if (slowX || slowY) {
				long  dSquared = (long)dx*dx + (long)dy*dy + 1;

				if (slowY)
					dx = (long)(dx * 2500L / dSquared);
				if (slowX)
					dy = (long)(dy * 2500L / dSquared);
			}

			if (reverse)
				sp = (-sp);

			map_x = (long)(x + (dy * sp) / 700);
			map_y = (long)(y - (dx * sp) / 700);

			while (map_y < 0)
				map_y += BUFF_HEIGHT;
			while (map_x < 0)
				map_x += BUFF_WIDTH;
			map_y = map_y % BUFF_HEIGHT;
			map_x = map_x % BUFF_WIDTH;

			lOffset = map_y * BUFF_WIDTH + map_x;
			lBlend = Blend;
			lFull = lOffset + (lBlend << 24 );

			if (fwrite (&lFull, sizeof(long), 1, fp) != 1) {
				printf ("\n*** Error while writing to output file (disk full?)\n");
				fclose (fp);
				return 3;
			}
		}
	}

	printf ("Done.%26s\n", "");

	fclose (fp);

	return 0;
}
