/* HOWTO
 * install the RNDIS mod using hakchi: https://github.com/DanTheMan827-Hakchi/rndis.hmod
 * give yourself an ip address on the RNDIS interface: ip addr add 10.234.137.11/8 dev usb
 * call this program: ssh root@10.234.137.10 'cat /dev/input/event24' | ./a.out
 * hint: killall ReedPlayer-Clover, to shut down the UI so you won't accidentally launch a game
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <linux/joystick.h>

/* 
 * uncomment this if you want to use a nunchuk
 * ignores D-PAD and maps tilt left to L, tilt right to R, and tilt back to X
 */
/*#define NUNCHUK_FIXES*/

uint16_t usedkeys[] = {
	BTN_A,
	BTN_B,
	BTN_X,
	BTN_Y,
	BTN_TL,
	BTN_TR,
	BTN_TL2,
	BTN_TR2,
	BTN_SELECT,
	BTN_START,
	BTN_MODE,
	BTN_DPAD_LEFT,
	BTN_DPAD_RIGHT,
	BTN_DPAD_UP,
	BTN_DPAD_DOWN
};

uint16_t happymap[] = {
	BTN_DPAD_LEFT,
	BTN_DPAD_RIGHT,
	BTN_DPAD_UP,
	BTN_DPAD_DOWN
};

/* the input_event struct varies across devices, we hardcode it here to be the one on the classic */
struct device_input_event {
	uint32_t sec;
	uint32_t usec;
	uint16_t type;
	uint16_t code;
	int32_t value;
};


/* from https://www.kernel.org/doc/html/v5.6/input/uinput.html */
void emit(int fd, int type, int code, int val)
{
	struct input_event ie;
	
	ie.type = type;
	ie.code = code;
	ie.value = val;
	
	ie.time.tv_sec = 0;
	ie.time.tv_usec = 0;

	write(fd, &ie, sizeof(ie));
}

int main(void)
{
	struct uinput_setup usetup = {{BUS_USB, 0x1209, 0x0001, 1}, "Clovercon Portal", 0};
	struct device_input_event event;
	struct uinput_abs_setup asetup = {0, {0, -72, 72}}, asetup2 = {0, {0, 0, 0xff}};
	int uinput;
	unsigned i;

	if ((uinput = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) == -1) {
		perror("open uinput");
		return 2;
	}
	
	ioctl(uinput, UI_SET_EVBIT, EV_KEY);
	for (i = 0; i < sizeof usedkeys / sizeof(uint16_t); ++i)
		ioctl(uinput, UI_SET_KEYBIT, usedkeys[i]);
	ioctl(uinput, UI_SET_EVBIT, EV_ABS);
	for (i = 0; i <= 5; ++i) {
		ioctl(uinput, UI_SET_ABSBIT, i);
		if (i == 2 || i == 5) {
			asetup2.code = i;
			ioctl(uinput, UI_ABS_SETUP, &asetup2);
		} else {
			asetup.code = i;
			ioctl(uinput, UI_ABS_SETUP, &asetup);
		}
	}

	ioctl(uinput, UI_DEV_SETUP, &usetup);
	ioctl(uinput, UI_DEV_CREATE);
	
	while (fread(&event, sizeof event, 1, stdin))
		/* most of the key events the mini generates make sense, except the dpad
		 * it uses the TRIGGER_HAPPY events for them, so we'll map them to the DPAD events,
		 * which didn't exist back then.
		 * BTN_TRIGGER_HAPPY3 -> BTN_DPAD_UP
		 * BTN_TRIGGER_HAPPY4 -> BTN_DPAD_DOWN
		 * BTN_TRIGGER_HAPPY1 -> BTN_DPAD_LEFT
		 * BTN_TRIGGER_HAPPY2 -> BTN_DPAD_RIGHT
		 */
		if (event.type == EV_SYN || event.type == EV_ABS)
			emit(uinput, event.type, event.code, event.value);
		else if (event.type == EV_KEY) {
#ifdef NUNCHUK_FIXES
			if (event.code == BTN_SELECT)
				emit(uinput, EV_KEY, BTN_TL, event.value);
			else if (event.code == BTN_START)
				emit(uinput, EV_KEY, BTN_TR, event.value);
			else if (event.code == BTN_MODE)
				emit(uinput, EV_KEY, BTN_X, event.value);
			else if (event.code < BTN_TRIGGER_HAPPY)
#endif
			emit(uinput, EV_KEY, event.code >= BTN_TRIGGER_HAPPY ? happymap[event.code-BTN_TRIGGER_HAPPY] : event.code, event.value);
		} else
			printf("Unknown event: %"PRIu32".%"PRIu32", type %"PRIu16", code %"PRIu16", value %"PRId16"\n", event.sec, event.usec, event.type, event.code, event.value);
	
	perror("fread");
	return 1;
}
