/* 
 * Create a device in vJoy with 11 buttons, (X, Y, Rx, Ry, Slider, Slider2) axes, 1 continuous POV hat, and no force feedback.
 * Install RNDIS mod on mini.
 * Start this command on the mini via ssh or telnet:
 * nc -l -p 9854 < /dev/input/event24
 * Start this program: clovercon_portal.exe <virtual joystick #: 1> <ip address: 10.234.137.10> <port: 9854>
 */

/* 
 * 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

#include <WinSock2.h>
#include <ws2tcpip.h>
#include "public.h"
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include "vjoyinterface.h"

#define CLAMP(value, low, high) \
	(((value) < (low)) ? (low) : (((value) > (high)) ? (high) : (value)))

// from linux input.h
struct device_input_event {
	uint32_t sec;
	uint32_t usec;
	uint16_t type;
	uint16_t code;
	int32_t value;
};
// from linux input-event-codes.h
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_ABS 0x03
#define BTN_GAMEPAD 0x130
#define BTN_C 0x132
#define BTN_Z 0x135
#define BTN_SELECT 0x13a
#define BTN_START 0x13b
#define BTN_MODE 0x13c
#define BTN_TRIGGER_HAPPY 0x2c0

const unsigned axismap[] = {HID_USAGE_X, HID_USAGE_Y, HID_USAGE_SL0, HID_USAGE_RX, HID_USAGE_RY, HID_USAGE_SL1};

#pragma warning(suppress : 4838) // you can't actually store negative numbers in a DWORD, but the API requires it
const DWORD dpadmap[16] = {-1, 27000, 9000, -1, 0, 31500, 4500, -1, 18000, 22500, 13500};

// https://stackoverflow.com/a/16723307
void print_wsa_error(const char *userstr, int wsaerror)
{
	wchar_t *s = NULL;
	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, wsaerror,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPWSTR)&s, 0, NULL);
	fprintf(stderr, "%s: %S\n", userstr, s);
	LocalFree(s);
}

static const char *usage = "Usage: <virtual joystick #: 1> <ip address: 10.234.137.10> <port: 9854>\n";

int main(int argc, char *argv[])
{
	struct device_input_event event;
	struct sockaddr_in sin = {0};
	unsigned joyid;
	WSADATA wsadata;
	uint8_t dpad = 0; // bitfield used as index into dpadmap[]
	int wsaret;
	SOCKET sock;

	if (!vJoyEnabled()) {
		fputs("You must have vjoy installed, and at least one device enabled.", stderr);
		return 1;
	}

	joyid = argc >= 2 ? atoi(argv[1]) : 1;
	if (!AcquireVJD(joyid)) {
		fputs("Could not acquire virtual joystick.", stderr);
		fputs(usage, stderr);
		return 1;
	}

	if (wsaret = WSAStartup(MAKEWORD(2, 2), &wsadata)) {
		print_wsa_error("WSAStartup", wsaret);
		return 1;
	}

	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {
		print_wsa_error("socket", WSAGetLastError());
		return 1;
	}

	sin.sin_family = AF_INET;
	sin.sin_port = htons(argc >= 4 ? atoi(argv[3]) : 9854);
	inet_pton(AF_INET, argc >= 3 ? argv[2] : "10.234.137.10", &sin.sin_addr);

	if (connect(sock, (sockaddr *) &sin, sizeof sin)) {
		print_wsa_error("connect", WSAGetLastError());
		fputs(usage, stderr);
		return 1;
	}

	while (recv(sock, (char *) &event, sizeof event, MSG_WAITALL))
		switch (event.type) {
		case EV_KEY:
#ifdef NUNCHUK_FIXES
			if (0) {
#else
			if (event.code >= BTN_TRIGGER_HAPPY && event.code <= BTN_TRIGGER_HAPPY+3) {
#endif
				if (event.value)
					dpad |= 1 << (event.code-BTN_TRIGGER_HAPPY);
				else
					dpad &= ~(1 << (event.code-BTN_TRIGGER_HAPPY));
				SetContPov(dpadmap[dpad], joyid, 1);
			} else // the ninty gamepads don't have C and Z, so skip those buttons
#ifdef NUNCHUK_FIXES
			if (event.code == BTN_SELECT)
				SetBtn(event.value, joyid, 5);
			else if (event.code == BTN_START)
				SetBtn(event.value, joyid, 6);
			else if (event.code == BTN_MODE)
				SetBtn(event.value, joyid, 3);
			else
#endif
				SetBtn(event.value, joyid, event.code - BTN_GAMEPAD-(event.code > BTN_C ? (event.code > BTN_Z ? 1 : 0) : -1));
			break;
		case EV_ABS:
			if (event.code <= 5) // stick values are [-72, 72] and trigger values are [0, 0xff], but Windows expects them between [0, 0x8000]
				SetAxis(event.code == 2 || event.code == 5 ? event.value * (0x8000 / 0xff) : (CLAMP(event.value, -72, 72) + 72) * (0x8000 / 144), joyid, axismap[event.code]);
		case EV_SYN: // the Set* APIs already syn for us, no need to handle this event
			break;
		default:
			printf("Unknown event at %u.%u, type %hu, code %hu, value %hd\n", event.sec, event.usec, event.type, event.code, event.value);
		}

	return 0;
}