/*
 * mania-hero.c
 * Requires libxtst
 * Tested using the `Guitar Hero: Warriors of Rock' controller for Nintendo Wii
 * (you may have to modify the button and axis enums if you use a different one)
 * Compile with: cc mania-hero.c -o mania-hero -Ofast -s -lX11 -lXtst
 */

#include <stdlib.h>
#include <unistd.h>
#include <linux/joystick.h> /* https://www.kernel.org/doc/Documentation/input/joystick-api.txt */
#include <stdio.h>
#include <fcntl.h>
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>

#define DEADZONE 10000

enum button {
	SELECT,
	START,
	STRUM_UP,
	STRUM_DOWN,
	FRET_GREEN,
	FRET_RED,
	FRET_YELLOW,
	FRET_BLUE,
	FRET_ORANGE
};

enum axis {
	STICK_X,
	STICK_Y,
	UNKNOWN, /* couldn't get this to move, might be WHAMMY_X? */
	WHAMMY
};

unsigned int fretmap[] = {
	XK_d,
	XK_f,
	XK_space,
	XK_j,
	XK_k
};

unsigned int stickmap[] = {
	XK_Left,
	XK_Right,
	XK_Down,
	XK_Up
};

int main(int argc, char *argv[])
{
	int file, heldfrets[5] = {0};
	Display *display;
	struct js_event event;
	
	if (argc < 2) {
		fprintf(stderr, "Usage: %s <joystick path>\n", argv[0]);
		return 1;
	}
	
	if ((display = XOpenDisplay(NULL)) == NULL) {
		fprintf(stderr, "Failed to open display `%s', is $DISPLAY set?\n", XDisplayName(NULL));
		return 2;
	}
	
	if (!XTestQueryExtension(display, &file, &file, &file, &file)) { /* uses file as a dummy variable as it's overwritten in the next `if' anyway */
		fprintf(stderr, "Your display does not support XTEST\n");
		return 2;
	}
	
	if ((file = open(argv[1], O_RDONLY)) == -1) {
		perror("Couldn't open joystick");
		return 2;
	}
	
	setbuf(stdout, NULL);
	
	while (read(file, &event, sizeof(event)) == sizeof(event)) {
		static const int colors[5] = {92, 91, 93, 94, 33};
		int i, isstrum = 0;
		
		if (event.type == JS_EVENT_BUTTON) {
			if (event.number >= FRET_GREEN && event.number <= FRET_ORANGE) {
				heldfrets[event.number - FRET_GREEN] = event.value;
				if (!event.value)
					XTestFakeKeyEvent(display, XKeysymToKeycode(display, fretmap[event.number - FRET_GREEN]), False, CurrentTime);
			} else if (event.value && (event.number == STRUM_UP || event.number == STRUM_DOWN)) {
				isstrum = 1;
				for (i = 0; i < 5; ++i) {
					XTestFakeKeyEvent(display, XKeysymToKeycode(display, fretmap[i]), False, CurrentTime);
					if (heldfrets[i])
						XTestFakeKeyEvent(display, XKeysymToKeycode(display, fretmap[i]), True, CurrentTime);
				}
			} else if (event.number == SELECT)
				XTestFakeKeyEvent(display, XKeysymToKeycode(display, XK_Return), event.value, CurrentTime);
			else if (event.number == START)
				XTestFakeKeyEvent(display, XKeysymToKeycode(display, XK_Escape), event.value, CurrentTime);
		} else if (event.type == JS_EVENT_AXIS && event.number <= 1)
			XTestFakeKeyEvent(display, XKeysymToKeycode(display, stickmap[event.number*2 + (event.value >= 0)]), abs(event.value) > DEADZONE, CurrentTime);
		
		XFlush(display);
		
		putchar('\r');
		for (i = 0; i < 5; ++i)
			printf("\x1b[%dm[%c] ", colors[i], heldfrets[i] ? '#' : ' ');
		printf("\x1b[0m%c", isstrum ? '!' : ' ');
	}
	
	XCloseDisplay(display);
	return 0;
}
