
#include "Arduino.h"

#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif
#define BUTTON_COUNT 12	 // SNES has 12, NES only has 8

#include "pins.h"

//shared pins between all controllers
static const int LATCH_PIN = OR_PIN_1;	// brown
static const int CLOCK_PIN = OR_PIN_2;	// white

//individual data pin for each controller
static const int DATA_PIN[GAMEPAD_COUNT] = {
	OR_PIN_3,
#if GAMEPAD_COUNT > 1
	OR_PIN_4,
#endif
#if GAMEPAD_COUNT > 2
	OR_PIN_5,
#endif
#if GAMEPAD_COUNT > 3
	OR_PIN_6,
#endif
};
// power red, ground black

//#define DEBUG

#include "gamepad/Gamepad.h"

static const uint8_t translateToNES[12] = {1, 8, 2, 3, 4, 5, 6, 7, 0, 8, 8, 8};

static const uint8_t nesButtons[4] = {0, 2, 3, 8};
static const uint8_t snesButtons[8] = {0, 1, 2, 3, 8, 9, 10, 11};

class GameControllers {
   public:
	enum Type
	{
		NES = 0,
		SNES = 1,
	};

	enum Button
	{
		B = 0,
		Y = 1,
		SELECT = 2,
		START = 3,
		UP = 4,
		DOWN = 5,
		LEFT = 6,
		RIGHT = 7,
		A = 8,
		X = 9,
		L = 10,
		R = 11,
	};
	Type types[GAMEPAD_COUNT];
	int latchPin;
	int clockPin;
	int dataPins[GAMEPAD_COUNT];
	long buttons[GAMEPAD_COUNT][12];
	int maxButtons = 12;

	int changedControllers[GAMEPAD_COUNT];

	///This has to be initialized once for the shared pins latch and clock
	void init(int latch, int clock) {
		latchPin = latch;
		clockPin = clock;
		pinMode(latchPin, OUTPUT);
		digitalWrite(latchPin, LOW);
		pinMode(clockPin, OUTPUT);
		digitalWrite(clockPin, HIGH);
	}

	///This sets the controller type and initializes its individual data pin
	void setController(int controller, Type type, int dataPin) {
		types[controller] = type;
		dataPins[controller] = dataPin;
		pinMode(dataPins[controller], INPUT_PULLUP);
		for (int i = 0; i < 12; i++) {
			buttons[controller][i] = -1;
		}
	}

	void poll(void (*controllerChanged)(const int controller)) {
		memset(changedControllers, 0, sizeof(changedControllers));

		digitalWrite(latchPin, HIGH);
		delayMicroseconds(12);
		digitalWrite(latchPin, LOW);
		delayMicroseconds(6);
		//Serial.print("snes: ");
		for (int i = 0; i < maxButtons; i++) {
			for (int c = 0; c < GAMEPAD_COUNT; c++) {
				if (digitalRead(dataPins[c])) {
					// up
					//Serial.print("-");
					if (-1 != buttons[c][i]) {
						buttons[c][i] = -1;
						changedControllers[c] = 1;
					}
				} else {
					// down
					//Serial.print(i);
					//++buttons[c][i];
					//changedControllers[c] = 1;
					if (0 != buttons[c][i]) {
						buttons[c][i] = 0;
						changedControllers[c] = 1;
					}
				}
			}
			digitalWrite(clockPin, LOW);
			delayMicroseconds(6);
			digitalWrite(clockPin, HIGH);
			delayMicroseconds(6);
		}
		//Serial.println();

		for (int c = 0; c < GAMEPAD_COUNT; c++) {
			// have any buttons changed state?
			if (1 == changedControllers[c]) {
				controllerChanged(c);
			}
		}
	}

	uint8_t translate(int controller, uint8_t b) {
		if (types[controller] == SNES)
			return b;
		return translateToNES[b];
	}

	///returns if button is currently down
	bool down(int controller, uint8_t b) {
		return buttons[controller][translate(controller, b)] >= 0;
	}

	void pressAll(int controller, void (*pushButton)(const int controller, const uint8_t btn)) {
		if (types[controller] == SNES) {
			for (uint8_t i = 0; i < sizeof(snesButtons); i++) {
				if (buttons[controller][snesButtons[i]] >= 0) {
					pushButton(controller, snesButtons[i]);
				}
			}
		} else {
			for (uint8_t i = 0; i < sizeof(nesButtons); i++) {
				if (buttons[controller][translateToNES[nesButtons[i]]] >= 0) {
					pushButton(controller, nesButtons[i]);
				}
			}
		}
	}
};

static const int translateToHid[12] = {
	BUTTON_B,
	BUTTON_Y,
	BUTTON_SELECT,
	BUTTON_START,
	DPAD_UP,
	DPAD_DOWN,
	DPAD_LEFT,
	DPAD_RIGHT,
	BUTTON_A,
	BUTTON_X,
	BUTTON_L,
	BUTTON_R,
};

GameControllers controllers;

GAMEPAD_CLASS gamepad;

void dummyControllerChanged(const int c) {
}

#ifdef DEBUG
void controllerChangedDebug(const int c) {
	Serial.print("controllerChanged!!!!: ");
	Serial.println(c);
	Serial.print("c: ");
	Serial.print(c);
	for (uint8_t btn = 0; btn < 12; btn++) {
		Serial.print("; ");
		Serial.print(btn);
		Serial.print(", ");
		Serial.print(controllers.buttons[c][controllers.translate(c, btn)]);
		Serial.print(",");
		Serial.print(controllers.buttons[c][btn]);
	}
	Serial.println("");
}
#endif	// DEBUG

void setup() {
	bool allNes = true;
#ifdef DEBUG
	delay(5000);
#endif	// DEBUG
	gamepad.begin();

	//initialize shared pins
	controllers.init(LATCH_PIN, CLOCK_PIN);

	//activate first controller and set the type to SNES
	for (int c = 0; c < GAMEPAD_COUNT; c++) {
		controllers.setController(c, GameControllers::SNES, DATA_PIN[c]);
	}

	// poll controllers once to detect NES vs SNES
	controllers.poll(dummyControllerChanged);

	for (int c = 0; c < GAMEPAD_COUNT; c++) {
		// for NES, A+X+L+R are down always, re-initialize
		if (controllers.down(c, GameControllers::A) && controllers.down(c, GameControllers::X) && controllers.down(c, GameControllers::L) && controllers.down(c, GameControllers::R)) {
#ifdef DEBUG
			Serial.println("detected NES");
#endif	// DEBUG
			controllers.types[c] = GameControllers::NES;
		} else {
#ifdef DEBUG
			Serial.println("detected SNES");
#endif	// DEBUG
			allNes = false;
		}
	}
	if (allNes) {
#ifdef DEBUG
		Serial.println("detected ONLY NES");
#endif	// DEBUG
		controllers.maxButtons = 8;
	}
}

void pushButton(const int c, const uint8_t btn) {
	gamepad.press(c, translateToHid[btn]);
}

void controllerChanged(const int c) {
#ifdef DEBUG
	controllerChangedDebug(c);
#endif	// DEBUG

	gamepad.buttons(c, 0);
	// if start and select are held at the same time, send menu and only menu
	if (controllers.down(c, GameControllers::START) && controllers.down(c, GameControllers::SELECT)) {
		gamepad.press(c, BUTTON_MENU);
	} else {
		// actually send buttons held
		controllers.pressAll(c, pushButton);
	}
	if (controllers.down(c, GameControllers::DOWN)) {
		if (controllers.down(c, GameControllers::RIGHT)) {
			gamepad.setHatSync(c, DPAD_DOWN_RIGHT);
		} else if (controllers.down(c, GameControllers::LEFT)) {
			gamepad.setHatSync(c, DPAD_DOWN_LEFT);
		} else {
			gamepad.setHatSync(c, DPAD_DOWN);
		}
	} else if (controllers.down(c, GameControllers::UP)) {
		if (controllers.down(c, GameControllers::RIGHT)) {
			gamepad.setHatSync(c, DPAD_UP_RIGHT);
		} else if (controllers.down(c, GameControllers::LEFT)) {
			gamepad.setHatSync(c, DPAD_UP_LEFT);
		} else {
			gamepad.setHatSync(c, DPAD_UP);
		}
	} else if (controllers.down(c, GameControllers::RIGHT)) {
		gamepad.setHatSync(c, DPAD_RIGHT);
	} else if (controllers.down(c, GameControllers::LEFT)) {
		gamepad.setHatSync(c, DPAD_LEFT);
	} else {
		gamepad.setHatSync(c, DPAD_CENTERED);
	}
}

void loop() {
	if (gamepad.isConnected()) {
		controllers.poll(controllerChanged);  //read all controllers at once
	}
}
