
#ifndef GAMEPAD_COMMON_H
#define GAMEPAD_COMMON_H

#include <Arduino.h>

#define ARDUINO_ARCH_ESP32 1
#include "HIDTypes.h"

#if GAMEPAD_OUTPUT == 5	 // nintendo switch

#define BUTTON_A NSButton_A
#define BUTTON_B NSButton_B
#define BUTTON_MENU NSButton_Home
#define BUTTON_X NSButton_X
#define BUTTON_Y NSButton_Y
#define BUTTON_TL NSButton_LeftTrigger
#define BUTTON_TR NSButton_RightTrigger
#define BUTTON_TL2 NSButton_LeftThrottle
#define BUTTON_TR2 NSButton_RightThrottle
#define BUTTON_SELECT NSButton_Minus
#define BUTTON_START NSButton_Plus
#define BUTTON_THUMBL NSButton_LeftStick
#define BUTTON_THUMBR NSButton_RightStick

#define DPAD_CENTER NSGAMEPAD_DPAD_CENTERED
#define DPAD_UP NSGAMEPAD_DPAD_UP
#define DPAD_UP_RIGHT NSGAMEPAD_DPAD_UP_RIGHT
#define DPAD_RIGHT NSGAMEPAD_DPAD_RIGHT
#define DPAD_DOWN_RIGHT NSGAMEPAD_DPAD_DOWN_RIGHT
#define DPAD_DOWN NSGAMEPAD_DPAD_DOWN
#define DPAD_DOWN_LEFT NSGAMEPAD_DPAD_DOWN_LEFT
#define DPAD_LEFT NSGAMEPAD_DPAD_LEFT
#define DPAD_UP_LEFT NSGAMEPAD_DPAD_UP_LEFT

#define AXIS_CENTER 128
#define AXIS_MAX 255
#define AXIS_MIN 0

// not supported by horipad yet...
#define TRIGGER_MAX 255
#define TRIGGER_MIN 0

#else

#define BUTTON_A 1
#define BUTTON_B 2
#define BUTTON_MENU 4
#define BUTTON_X 8
#define BUTTON_Y 16
#define BUTTON_TL 64
#define BUTTON_TR 128
#define BUTTON_TL2 256
#define BUTTON_TR2 512
#define BUTTON_SELECT 1024
#define BUTTON_START 2048
#define BUTTON_THUMBL 8192
#define BUTTON_THUMBR 16384

#define DPAD_CENTER 0
#define DPAD_UP 1
#define DPAD_UP_RIGHT 2
#define DPAD_RIGHT 3
#define DPAD_DOWN_RIGHT 4
#define DPAD_DOWN 5
#define DPAD_DOWN_LEFT 6
#define DPAD_LEFT 7
#define DPAD_UP_LEFT 8

#define AXIS_CENTER 0
#define AXIS_MAX 32767
#define AXIS_MIN -32767

#define TRIGGER_MAX 255
#define TRIGGER_MIN 0

#endif	// nintendo switch

// aliases
#define BUTTON_HOME BUTTON_MENU
#define BUTTON_L BUTTON_TL
#define BUTTON_L1 BUTTON_TL
#define BUTTON_R BUTTON_TR
#define BUTTON_R1 BUTTON_TR
#define BUTTON_L2 BUTTON_TL2
#define BUTTON_R2 BUTTON_TR2
#define BUTTON_L3 BUTTON_THUMBL
#define BUTTON_R3 BUTTON_THUMBR

#define DPAD_CENTERED DPAD_CENTER
// end aliases

#ifndef AXIS_CENTER_IN
#define AXIS_CENTER_IN 0
#endif
#ifndef AXIS_MAX_IN
#define AXIS_MAX_IN 32767
#endif
#ifndef AXIS_MIN_IN
#define AXIS_MIN_IN -32767
#endif
#ifndef TRIGGER_MAX_IN
#define TRIGGER_MAX_IN 127
#endif
#ifndef TRIGGER_MIN_IN
#define TRIGGER_MIN_IN -127
#endif

#if 0
#define BUTTON_1 1
#define BUTTON_2 2
#define BUTTON_3 4
#define BUTTON_4 8
#define BUTTON_5 16
#define BUTTON_6 32
#define BUTTON_7 64
#define BUTTON_8 128
#define BUTTON_9 256
#define BUTTON_10 512
#define BUTTON_11 1024
#define BUTTON_12 2048
#define BUTTON_13 4096
#define BUTTON_14 8192
#define BUTTON_15 16384
#define BUTTON_16 32768
#define BUTTON_17 65536
#define BUTTON_18 131072
#define BUTTON_19 262144
#define BUTTON_20 524288
#define BUTTON_21 1048576
#define BUTTON_22 2097152
#define BUTTON_23 4194304
#define BUTTON_24 8388608
#define BUTTON_25 16777216
#define BUTTON_26 33554432
#define BUTTON_27 67108864
#define BUTTON_28 134217728
#define BUTTON_29 268435456
#define BUTTON_30 536870912
#define BUTTON_31 1073741824
#define BUTTON_32 2147483648
#endif

#ifndef GAMEPAD_REPORT_ARRAY_ADD
// this is used by radio gamepad to send additional info
#define GAMEPAD_REPORT_ARRAY_ADD 0
#endif

// this must match _hidReportDescriptor, length to actually send
#define GAMEPAD_REPORT_LEN 15

static const uint8_t _hidReportDescriptor[] PROGMEM = {
	USAGE_PAGE(1),
	0x01,  // USAGE_PAGE (Generic Desktop)
	USAGE(1),
	0x05,  // USAGE (Gamepad)
	COLLECTION(1),
	0x01,  // COLLECTION (Application)
	USAGE(1),
	0x01,  //   USAGE (Pointer)
	COLLECTION(1),
	0x00,  //   COLLECTION (Physical)

	// ------------------------------------------------- Buttons (1 to 32)
	USAGE_PAGE(1),
	0x09,  //     USAGE_PAGE (Button)
	USAGE_MINIMUM(1),
	0x01,  //     USAGE_MINIMUM (Button 1)
	USAGE_MAXIMUM(1),
	0x20,  //     USAGE_MAXIMUM (Button 32)
	LOGICAL_MINIMUM(1),
	0x00,  //     LOGICAL_MINIMUM (0)
	LOGICAL_MAXIMUM(1),
	0x01,  //     LOGICAL_MAXIMUM (1)
	REPORT_SIZE(1),
	0x01,  //     REPORT_SIZE (1)
	REPORT_COUNT(1),
	0x20,  //     REPORT_COUNT (32)
	HIDINPUT(1),
	0x02,  //     INPUT (Data, Variable, Absolute) ;32 button bits
	// ------------------------------------------------- X/Y position, Z/rZ position
	USAGE_PAGE(1),
	0x01,  //		USAGE_PAGE (Generic Desktop)
	COLLECTION(1),
	0x00,  //		COLLECTION (Physical)
	USAGE(1),
	0x30,  //     USAGE (X)
	USAGE(1),
	0x31,  //     USAGE (Y)
	USAGE(1),
	0x32,  //     USAGE (Z)
	USAGE(1),
	0x35,  //     USAGE (rZ)
	0x16,
	0x01,
	0x80,  //LOGICAL_MINIMUM (-32767)
	0x26,
	0xFF,
	0x7F,  //LOGICAL_MAXIMUM (32767)
	REPORT_SIZE(1),
	0x10,  //		REPORT_SIZE (16)
	REPORT_COUNT(1),
	0x04,  //		REPORT_COUNT (4)
	HIDINPUT(1),
	0x02,  //     INPUT (Data,Var,Abs)
	// ------------------------------------------------- Triggers
	USAGE(1),
	0x33,  //     USAGE (rX) Left Trigger
	USAGE(1),
	0x34,  //     USAGE (rY) Right Trigger
	LOGICAL_MINIMUM(1),
	0x81,  //     LOGICAL_MINIMUM (-127)
	LOGICAL_MAXIMUM(1),
	0x7f,  //     LOGICAL_MAXIMUM (127)
	REPORT_SIZE(1),
	0x08,  //     REPORT_SIZE (8)
	REPORT_COUNT(1),
	0x02,  //     REPORT_COUNT (2)
	HIDINPUT(1),
	0x02,				//     INPUT (Data, Variable, Absolute) ;4 bytes (X,Y,Z,rZ)
	END_COLLECTION(0),	//     END_COLLECTION

	USAGE_PAGE(1),
	0x01,  //     USAGE_PAGE (Generic Desktop)
	USAGE(1),
	0x39,  //     USAGE (Hat switch)
	USAGE(1),
	0x39,  //     USAGE (Hat switch)
	LOGICAL_MINIMUM(1),
	0x01,  //     LOGICAL_MINIMUM (1)
	LOGICAL_MAXIMUM(1),
	0x08,  //     LOGICAL_MAXIMUM (8)
	REPORT_SIZE(1),
	0x04,  //     REPORT_SIZE (4)
	REPORT_COUNT(1),
	0x02,  //     REPORT_COUNT (2)
	HIDINPUT(1),
	0x02,  //     INPUT (Data, Variable, Absolute) ;1 byte Hat1, Hat2

	END_COLLECTION(0),	//     END_COLLECTION
	END_COLLECTION(0)	//     END_COLLECTION
};

class AbstractGamepad {
   public:
	uint32_t _buttons[GAMEPAD_COUNT];
	uint8_t gamepadReport[GAMEPAD_REPORT_LEN + GAMEPAD_REPORT_ARRAY_ADD];

	AbstractGamepad() {
	}

	virtual void begin(void) {
	}

	virtual void end(void) {
	}

	virtual bool isConnected(void) {
		return true;
	}

	virtual void setAxis(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
		if (x == -32768) {
			x = -32767;
		}
		if (y == -32768) {
			y = -32767;
		}
		if (z == -32768) {
			z = -32767;
		}
		if (rZ == -32768) {
			rZ = -32767;
		}

		gamepadReport[0] = _buttons[cIdx];
		gamepadReport[1] = (_buttons[cIdx] >> 8);
		gamepadReport[2] = (_buttons[cIdx] >> 16);
		gamepadReport[3] = (_buttons[cIdx] >> 24);
		gamepadReport[4] = x;
		gamepadReport[5] = (x >> 8);
		gamepadReport[6] = y;
		gamepadReport[7] = (y >> 8);
		gamepadReport[8] = z;
		gamepadReport[9] = (z >> 8);
		gamepadReport[10] = rZ;
		gamepadReport[11] = (rZ >> 8);
		gamepadReport[12] = (signed char)(rX - 128);
		gamepadReport[13] = (signed char)(rY - 128);
		gamepadReport[14] = hat;
		if (gamepadReport[12] == -128) {
			gamepadReport[12] = -127;
		}
		if (gamepadReport[13] == -128) {
			gamepadReport[13] = -127;
		}

		this->sync(cIdx);
	}

	virtual void setHatSync(const uint8_t cIdx, signed char hat) {
		setAxis(cIdx, AXIS_CENTER, AXIS_CENTER, AXIS_CENTER, AXIS_CENTER, 0, 0, hat);
	}

	virtual void buttons(const uint8_t cIdx, uint32_t b) {
		_buttons[cIdx] = b;
	}

	virtual void press(const uint8_t cIdx, uint32_t b) {
		buttons(cIdx, _buttons[cIdx] | b);
	}

	virtual void release(const uint8_t cIdx, uint32_t b) {
		buttons(cIdx, _buttons[cIdx] & ~b);
	}

	virtual bool isPressed(const uint8_t cIdx, uint32_t b) {
		return ((b & _buttons[cIdx]) > 0);
	}

	virtual void sync(const uint8_t cIdx) {
		sendHidReport(cIdx, &gamepadReport, GAMEPAD_REPORT_LEN);
	}

	virtual void sendHidReport(const uint8_t cIdx, const void* d, int len);	 // actually sends report

	virtual ~AbstractGamepad() {
	}
};

#endif	// GAMEPAD_COMMON_H
