/*
 * leetbuzz.c - buzzes your scr/lck led in a leet fashion
 * derived from heartbeat.c by alessandro rubini (your book's just best :)
 *
 * this little program will attract some geek eyes at the next hack event
 * for sure ;-)
 *
 * by scut <scut@nb.in-berlin.de>
 *
 * must be executed as suid root, fortunatly
 *
 * compile with: gcc -o leetbuzz leetbuzz.c -lm
 *
 * tested with 2.[02].x on alpha, sparc and x86
 */

#define	LB_SHUTTER	32
// #define	LB_MODE_ALT

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/kd.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int	consolefd;
char	flasher[LB_SHUTTER];

void	led_runthru(char *, int, unsigned long);
void	led_doshutter(char *, int);
int	led_sinewave(int);
int	led_init(void);
void	led_uninit(void);
void	led_set(void);
void	led_unset(void);
int	led_change(void);

int
main(int argc, char **argv)
{
	if (led_init() == 0) {
		fprintf(stderr, "cannot open tty, lammah\n");
		exit(1);
	}

	for (;;) {
		led_sinewave(5);
		led_runthru(flasher, LB_SHUTTER, 5000);
	}
	exit(0); /* never happen */
}

/* runs through our neat array
 */
void
led_runthru(char *p_array, int max, unsigned long waitdigit)
{
	struct timeval	st;
	struct timeval	ct;
	int	n;

	for (n = 0; n < max; n++) {
		if (gettimeofday(&st, NULL) == -1) return;

		if (p_array[n] == '\x00') {
			led_unset();
		} else if (p_array[n] == '\x01') {
			led_set();
		}
		if (gettimeofday(&ct, NULL) == -1) return;
		while ((((ct.tv_sec * 1000000) + ct.tv_usec) -
			((st.tv_sec * 1000000) + st.tv_usec)) < waitdigit)
			gettimeofday(&ct, NULL);
	}
	return;
}

/* little bresenham hack to stretch our intensity
 */
void
led_doshutter(char *p_array, int intensity)
{
	int	n = 0;
	float	e;
	int	x, y;

	if (intensity > LB_SHUTTER)
		return;

	for (y = x = 0; x < LB_SHUTTER; x++) {
		e = y - ((x * intensity) / LB_SHUTTER);
		if (e < 0) {
			e *= -1;
		}
		if (e <= 0.5) {
			p_array[x] = '\x00';
		} else {
			p_array[x] = '\x01';
			y++;
		}
	}

#ifdef DEBUG
	for (x = 0; x < LB_SHUTTER; x++)
		printf("%c", (p_array[x]) ? 'X' : ' ');
	printf("\n");
#endif

	return;
}

/* tells wether the led should be active (1) or not (0) for sinewave
 * with period (in seconds)
 * first call -> init
 * period = 0 -> init
 */
int
led_sinewave(int period)
{
	static struct timeval	*st = NULL;
	static struct timeval	*ct = NULL;

	double			t_f;
	unsigned long long	st_usec;
	unsigned long long	ct_usec;
	unsigned long long	td;

	/* new init ? */
	if (period == 0) {
		free(st);
		st = NULL;
	}
	if (st == NULL) {
		st = calloc(1, sizeof(struct timeval));
		if (gettimeofday(st, NULL) == -1) {
			fprintf(stderr, "cannot get time of day for st :)\n");
			exit(1);
		}
	}
	if (period == 0)
		return (0);

	if (ct == NULL) {
		ct = calloc(1, sizeof(struct timeval));
	}

	/* get current time and then compare */
	if (gettimeofday(ct, NULL) == -1) {
		fprintf(stderr, "cannot get time of day for ct :)\n");
		exit(1);
	}

	st_usec = (st->tv_sec * 1000000) + st->tv_usec;
	ct_usec = (ct->tv_sec * 1000000) + ct->tv_usec;
	td = ct_usec - st_usec;  /* difference */

	/* compute relative period, then compute sine value */
	td = (td % (period * 1000000));
	t_f = (double)(td / (double)(period * 1000000));
	t_f *= 2 * M_PI; /* yeah, i like math.h */
#ifdef	LB_MODE_ALT
	t_f = ((sin(t_f) + 1) / 3) + 0.3;
#else
	t_f = (sin(t_f) + 1) / 2; /* we don't need negative LEDs */
#endif

#ifdef DEBUG
	printf("%3.5f : ", t_f);
#endif
	led_doshutter(flasher, (int)(t_f * LB_SHUTTER));
	return(1);
}

int
led_init(void)
{
	consolefd = open("/dev/tty0", O_RDONLY);
	if (consolefd == -1)
		return(0);
	return(1);
}

void
led_uninit(void)
{
	close(consolefd);
	return;
}

void
led_set(void)
{
	char	led;

	ioctl(consolefd, KDSETLED, 1);
	return;
}

void
led_unset(void)
{
	char	led;

	ioctl(consolefd, KDSETLED, 0);
	return;
}

int
led_change(void)
{
	char	led;

	if (ioctl(consolefd, KDGETLED, &led) != -1) {
		ioctl(consolefd, KDSETLED, (led == 1) ? 0 : 1);
	}
	return(led);
}


