/*
 * Supercharged multi-baton twirling action.
 * By Ted Percival, 2006-06-24
 * I hereby release this code into the public domain.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>

#define BATON_COUNT 30
#define DEFAULT_ITERATION_TIME 2000 /* number of microseconds between updates */
#define DEFAULT_GLYPHS "_.-`-."

typedef unsigned char state_t;

enum { MODE_CYCLIC, MODE_SLIDE_LEFT, MODE_SLIDE_RIGHT } g_mode;
int g_baton_count;

void fill(char* glyphs, char* batons, state_t* states);
state_t next(const state_t* states, int baton, int nglyphs);
void usage(const char* invoked_as);

int main(int argc, char* argv[]) {
	int i;
	state_t states[BATON_COUNT];
	char batons[BATON_COUNT+1];
	struct timeval tvtracker, tvnow;
	unsigned long timetaken, iteration_time;
	char* glyphs = DEFAULT_GLYPHS;

	g_mode = MODE_SLIDE_RIGHT;
	iteration_time = DEFAULT_ITERATION_TIME;

	while (-1 != (i = getopt(argc, argv, "clrhd:n:"))) {
		switch(i) {
		case 'c':
			g_mode = MODE_CYCLIC; break;
		case 'l':
			g_mode = MODE_SLIDE_LEFT; break;
		case 'r':
			g_mode = MODE_SLIDE_RIGHT; break;
		case 'd':
			iteration_time = strtoul(optarg, NULL, 10);
			iteration_time = iteration_time ? iteration_time * 1000 : DEFAULT_ITERATION_TIME;
			break;
		case '?':
		default:
		case 'h':
			usage(argv[0]);
			exit(0);
		}
	}

	if (optind < argc && strlen(argv[optind]))
		glyphs = argv[optind];
	else
		glyphs = DEFAULT_GLYPHS;

	batons[BATON_COUNT] = '\0'; /* null-terminate */
	fill(glyphs, batons, states);
	for (i = 0; i < BATON_COUNT; ++i)
		printf("%c ", batons[i]);

	if (0 != gettimeofday(&tvtracker, NULL))
		exit(1);

	while (1) {
		for (i = 0; i < BATON_COUNT; ++i) {
			if (i == 0)
				printf("\r");
			states[i] = next(states, i, strlen(glyphs));
			batons[i] = glyphs[states[i]];
			printf("%c ", batons[i]);
			fflush(stdout);

			if (0 != gettimeofday(&tvnow, NULL))
				exit(1);
			timetaken = (tvnow.tv_sec - tvtracker.tv_sec) * 1000000;
			timetaken += tvnow.tv_usec - tvtracker.tv_usec;
			tvtracker = tvnow;
			if (iteration_time > timetaken)
				usleep(iteration_time - timetaken);
		}
	}
}

void fill(char* glyphs, char* batons, state_t* states) {
	int i;
	for (i = 0; i < BATON_COUNT; ++i) {
		states[i] = 0;
		batons[i] = glyphs[0];
	}
}

state_t next(const state_t* states, int baton, int nglyphs) {
	state_t newstate;
	switch(g_mode) {
	case MODE_CYCLIC:
		newstate = states[baton] + 1;
		break;

	case MODE_SLIDE_LEFT:
		if (baton == 0)
			newstate = states[baton];
		else
			newstate = states[baton - 1];
		++newstate;
		break;

	case MODE_SLIDE_RIGHT:
		/* Because there could be any number of batons and they're drawn
		 * left-to-right, this case gets a bit hairy. */
		if (baton == BATON_COUNT - 1)
			newstate = states[baton] + 1;
		else
			newstate = states[BATON_COUNT - 1] + BATON_COUNT - baton;
		break;
	
	default:
		newstate = 0;
	}

	newstate %= nglyphs;

	return newstate;
}

void usage(const char* invoked_as) {
	fprintf(stderr,
		"Usage: %s [-c|-l|-r] [-d delay] [sequence]\n"
		"\n"
		"-c\tCyclic\n"
		"-l\tSlide left\n"
		"-r\tSlide right\n"
		"-d\tThe delay (in milliseconds) between each item being drawn. Default 2.\n"
		"-h\tThis help (usage) message\n"
		"\n"
		"sequence is the sequence of characters to draw for the animation.\n"
		"The default sequence is \"" DEFAULT_GLYPHS "\".\n\n",
		invoked_as);
}
