/*
Genesis/Atari/Megadrive:
      LOOKING AT THE PLUG ON FRONT OF CONSOLE (not coming from controller)

      1   2   3   4   5
   -----------------------
   \  o   o   o   o   o  /
    \   o   o   o   o   /
     \-----------------/
        6   7   8   9

PIN # USAGE

  5: 5V VCC
  8: GND
*/
#include "Arduino.h"

#ifndef GAMEPAD_COUNT
#define GAMEPAD_COUNT 2
#endif

#define BUTTON_COUNT 9
#define PIN_COUNT 6

#include "gamepad/Gamepad.h"

enum
{
	// this is the button mapping, change as you wish
	SC_BTN_A = BUTTON_Y,
	SC_BTN_B = BUTTON_B,
	SC_BTN_C = BUTTON_A,
	SC_BTN_X = BUTTON_L,
	SC_BTN_Y = BUTTON_X,
	SC_BTN_Z = BUTTON_R,
	SC_BTN_START = BUTTON_START,
	SC_BTN_MODE = BUTTON_SELECT,
	SC_BTN_HOME = BUTTON_MENU,
	SC_BTN_UP = 1,
	SC_BTN_DOWN = 2,
	SC_BTN_LEFT = 4,
	SC_BTN_RIGHT = 8,
};

#include "pins.h"

static const int DATA_PIN_SELECT[GAMEPAD_COUNT] = {
	OR_PIN_5,
#if GAMEPAD_COUNT > 1
	OR_PIN_7,
#endif
};

#if CODE_PLATFORM == 2

#define OPT_PIN_READ1(X) (bitRead(reg1, DATA_PIN[c][X]))
#define OPT_PIN_READ2(X) (bitRead(reg2, DATA_PIN[c][X]))

//individual data pin BIT for each controller, they are read in bulk
static const int DATA_PIN[GAMEPAD_COUNT][PIN_COUNT] = {
	{3, 2, 1, 0, 4, 7},
#if GAMEPAD_COUNT > 1
	{7, 6, 5, 4, 3, 1},
#endif
};

#else

#define OPT_PIN_READ1(X) (digitalRead(DATA_PIN[c][X]))
#define OPT_PIN_READ2(X) (digitalRead(DATA_PIN[c][X]))

//individual data pin for each controller
static const int DATA_PIN[GAMEPAD_COUNT][PIN_COUNT] = {
	{OR_PIN_1, OR_PIN_11, OR_PIN_2, OR_PIN_3, OR_PIN_4, OR_PIN_6},
#if GAMEPAD_COUNT > 1
	{OR_PIN_18, OR_PIN_19, OR_PIN_20, OR_PIN_21, OR_PIN_14, OR_PIN_15},
#endif
};

#endif	// CODE_PLATFORM

const byte SC_CYCLE_DELAY = 10;	 // Delay (µs) between setting the select pin and reading the button pins

class SegaControllers32U4 {
   public:
	SegaControllers32U4(void);
	void readState();
	byte currentDpadState[GAMEPAD_COUNT];
	word currentState[GAMEPAD_COUNT];
	// Controller previous states
	word lastState[GAMEPAD_COUNT];
	byte lastDpadState[GAMEPAD_COUNT];

	void poll(void (*controllerChanged)(const int c)) {
		readState();
		for (int c = 0; c < GAMEPAD_COUNT; c++) {
			if (currentState[c] != lastState[c] || currentDpadState[c] != lastDpadState[c]) {
				controllerChanged(c);
				lastState[c] = currentState[c];
				lastDpadState[c] = currentDpadState[c];
			}
		}
	}

	bool down(int controller, int button) const {
		return currentDpadState[controller] & button;
	}

	bool dpad(int controller, int button) const {
		return currentDpadState[controller] & button;
	}

#ifdef DEBUG
	void printState(byte gp) {
		auto cs = currentState[gp];
		Serial.print(_connected[gp] ? "+" : "-");
		Serial.print(_sixButtonMode[gp] ? "6" : "3");
		Serial.print((cs & SC_BTN_UP) ? "U" : "0");
		Serial.print((cs & SC_BTN_DOWN) ? "D" : "0");
		Serial.print((cs & SC_BTN_LEFT) ? "L" : "0");
		Serial.print((cs & SC_BTN_RIGHT) ? "R" : "0");
		Serial.print((cs & SC_BTN_START) ? "S" : "0");
		Serial.print((cs & SC_BTN_A) ? "A" : "0");
		Serial.print((cs & SC_BTN_B) ? "B" : "0");
		Serial.print((cs & SC_BTN_C) ? "C" : "0");
		Serial.print((cs & SC_BTN_X) ? "X" : "0");
		Serial.print((cs & SC_BTN_Y) ? "Y" : "0");
		Serial.print((cs & SC_BTN_Z) ? "Z" : "0");
		Serial.print((cs & SC_BTN_MODE) ? "M" : "0");
		Serial.print((cs & SC_BTN_HOME) ? "H" : "0");
		Serial.println(" sending");
	}
#endif

   private:
	boolean _pinSelect;

	byte _ignoreCycles[GAMEPAD_COUNT];

	boolean _connected[GAMEPAD_COUNT];
	boolean _sixButtonMode[GAMEPAD_COUNT];

#if CODE_PLATFORM == 2
	void readPort(byte c, byte reg1, byte reg2);
	byte _inputReg3;
#if GAMEPAD_COUNT > 1
	byte _inputReg1;
	byte _inputReg2;
#endif	// GAMEPAD_COUNT
#else
	void readPort(byte c);
#endif	// CODE_PLATFORM
};

SegaControllers32U4::SegaControllers32U4(void) {
	for (int c = 0; c < GAMEPAD_COUNT; c++) {
		// Setup output pin
		pinMode(DATA_PIN_SELECT[c], OUTPUT);
		digitalWrite(DATA_PIN_SELECT[c], HIGH);

		// Setup input pins
		for (byte i = 0; i < PIN_COUNT; i++) {
			pinMode(DATA_PIN[c][i], INPUT_PULLUP);
		}
	}

	_pinSelect = true;
	for (int c = 0; c < GAMEPAD_COUNT; c++) {
		currentState[c] = 0;
		lastState[c] = 0;
		currentDpadState[c] = 0;
		lastDpadState[c] = 0;
		_connected[c] = 0;
		_sixButtonMode[c] = false;
		_ignoreCycles[c] = 0;
	}
}

#if CODE_PLATFORM == 2

void SegaControllers32U4::readState() {
	// Set the select pins low/high
	_pinSelect = !_pinSelect;
	if (!_pinSelect) {
		PORTE &= ~B01000000;
		PORTC &= ~B01000000;
	} else {
		PORTE |= B01000000;
		PORTC |= B01000000;
	}

	// Short delay to stabilise outputs in controller
	delayMicroseconds(SC_CYCLE_DELAY);

	// Read all input registers
	_inputReg3 = PIND;
#if GAMEPAD_COUNT > 1
	_inputReg1 = PINF;
	_inputReg2 = PINB;
#endif

	readPort(0, _inputReg3, _inputReg3);
#if GAMEPAD_COUNT > 1
	readPort(1, _inputReg1, _inputReg2);
#endif
}

#else

void SegaControllers32U4::readState() {
	// Set the select pins low/high
	_pinSelect = !_pinSelect;
	if (!_pinSelect) {
		for (int c = 0; c < GAMEPAD_COUNT; c++) {
			digitalWrite(DATA_PIN_SELECT[c], LOW);
		}
	} else {
		for (int c = 0; c < GAMEPAD_COUNT; c++) {
			digitalWrite(DATA_PIN_SELECT[c], HIGH);
		}
	}

	// Short delay to stabilise outputs in controller
	delayMicroseconds(SC_CYCLE_DELAY);

	for (int c = 0; c < GAMEPAD_COUNT; c++) {
		readPort(c);
	}
}

#endif

// "Normal" Six button controller reading routine, done a bit differently in this project
// Cycle  TH out  TR in  TL in  D3 in  D2 in  D1 in  D0 in
// 0      LO      Start  A      0      0      Down   Up
// 1      HI      C      B      Right  Left   Down   Up
// 2      LO      Start  A      0      0      Down   Up      (Check connected and read Start and A in this cycle)
// 3      HI      C      B      Right  Left   Down   Up      (Read B, C and directions in this cycle)
// 4      LO      Start  A      0      0      0      0       (Check for six button controller in this cycle)
// 5      HI      C      B      Mode   X      Y      Z       (Read X,Y,Z and Mode in this cycle)
// 6      LO      ---    ---    ---    ---    ---    Home    (Home only for 8bitdo wireless gamepads)
// 7      HI      ---    ---    ---    ---    ---    ---
void SegaControllers32U4::readPort(byte c
#if CODE_PLATFORM == 2
								   ,
								   byte reg1,
								   byte reg2
#endif
) {
	if (_ignoreCycles[c] <= 0) {
		if (_pinSelect)	 // Select pin is HIGH
		{
			if (_connected[c]) {
				// Check if six button mode is active
				if (_sixButtonMode[c]) {
					// Read input pins for X, Y, Z, Mode
					(OPT_PIN_READ1(0) == LOW) ? currentState[c] |= SC_BTN_Z : currentState[c] &= ~SC_BTN_Z;
					(OPT_PIN_READ1(1) == LOW) ? currentState[c] |= SC_BTN_Y : currentState[c] &= ~SC_BTN_Y;
					(OPT_PIN_READ1(2) == LOW) ? currentState[c] |= SC_BTN_X : currentState[c] &= ~SC_BTN_X;
					(OPT_PIN_READ1(3) == LOW) ? currentState[c] |= SC_BTN_MODE : currentState[c] &= ~SC_BTN_MODE;
					_sixButtonMode[c] = false;
					_ignoreCycles[c] = 2;  // Ignore the two next cycles (cycles 6 and 7 in table above)
				} else {
					// Read input pins for Up, Down, Left, Right, B, C
					(OPT_PIN_READ1(0) == LOW) ? currentDpadState[c] |= SC_BTN_UP : currentDpadState[c] &= ~SC_BTN_UP;
					(OPT_PIN_READ1(1) == LOW) ? currentDpadState[c] |= SC_BTN_DOWN : currentDpadState[c] &= ~SC_BTN_DOWN;
					(OPT_PIN_READ1(2) == LOW) ? currentDpadState[c] |= SC_BTN_LEFT : currentDpadState[c] &= ~SC_BTN_LEFT;
					(OPT_PIN_READ1(3) == LOW) ? currentDpadState[c] |= SC_BTN_RIGHT : currentDpadState[c] &= ~SC_BTN_RIGHT;
					(OPT_PIN_READ2(4) == LOW) ? currentState[c] |= SC_BTN_B : currentState[c] &= ~SC_BTN_B;
					(OPT_PIN_READ2(5) == LOW) ? currentState[c] |= SC_BTN_C : currentState[c] &= ~SC_BTN_C;
				}
			} else	// No Mega Drive controller is connected, use SMS/Atari mode
			{
				// Clear current state
				currentState[c] = currentDpadState[c] = 0;

				// Read input pins for Up, Down, Left, Right, Fire1, Fire2
				if (OPT_PIN_READ1(0) == LOW) {
					currentDpadState[c] |= SC_BTN_UP;
				}
				if (OPT_PIN_READ1(1) == LOW) {
					currentDpadState[c] |= SC_BTN_DOWN;
				}
				if (OPT_PIN_READ1(2) == LOW) {
					currentDpadState[c] |= SC_BTN_LEFT;
				}
				if (OPT_PIN_READ1(3) == LOW) {
					currentDpadState[c] |= SC_BTN_RIGHT;
				}
				if (OPT_PIN_READ2(4) == LOW) {
					currentState[c] |= SC_BTN_A;
				}
				if (OPT_PIN_READ2(5) == LOW) {
					currentState[c] |= SC_BTN_B;
				}
			}
		} else	// Select pin is LOW
		{
			// Check if a controller is connected
			_connected[c] = OPT_PIN_READ1(2) == LOW && OPT_PIN_READ1(3) == LOW;

			// Check for six button mode
			_sixButtonMode[c] = OPT_PIN_READ1(0) == LOW && OPT_PIN_READ1(1) == LOW;

			// Read input pins for A and Start
			if (_connected[c]) {
				if (!_sixButtonMode[c]) {
					(OPT_PIN_READ2(4) == LOW) ? currentState[c] |= SC_BTN_A : currentState[c] &= ~SC_BTN_A;
					(OPT_PIN_READ2(5) == LOW) ? currentState[c] |= SC_BTN_START : currentState[c] &= ~SC_BTN_START;
				}
			}
		}
	} else {
		if (_ignoreCycles[c]-- == 2)  // Decrease the ignore cycles counter and read 8bitdo home in first "ignored" cycle, this cycle is unused on normal 6-button controllers
		{
			(OPT_PIN_READ1(0) == LOW) ? currentState[c] |= SC_BTN_HOME : currentState[c] &= ~SC_BTN_HOME;
		}
	}
}

SegaControllers32U4 controllers;

GAMEPAD_CLASS gamepad;

void controllerChanged(const int c) {
#ifdef DEBUG
	controllers.printState(c);
#endif

	// if start and down are held at the same time, send menu and only menu
	gamepad.buttons(c, controllers.currentState[c]);
	if (controllers.down(c, SC_BTN_START) && controllers.dpad(c, SC_BTN_DOWN)) {
		gamepad.buttons(c, 0);
		gamepad.press(c, BUTTON_MENU);
		gamepad.setHatSync(c, DPAD_CENTERED);
		return;
	}
	if (controllers.dpad(c, SC_BTN_DOWN)) {
		if (controllers.dpad(c, SC_BTN_RIGHT)) {
			gamepad.setHatSync(c, DPAD_DOWN_RIGHT);
		} else if (controllers.dpad(c, SC_BTN_LEFT)) {
			gamepad.setHatSync(c, DPAD_DOWN_LEFT);
		} else {
			gamepad.setHatSync(c, DPAD_DOWN);
		}
	} else if (controllers.dpad(c, SC_BTN_UP)) {
		if (controllers.dpad(c, SC_BTN_RIGHT)) {
			gamepad.setHatSync(c, DPAD_UP_RIGHT);
		} else if (controllers.dpad(c, SC_BTN_LEFT)) {
			gamepad.setHatSync(c, DPAD_UP_LEFT);
		} else {
			gamepad.setHatSync(c, DPAD_UP);
		}
	} else if (controllers.dpad(c, SC_BTN_RIGHT)) {
		gamepad.setHatSync(c, DPAD_RIGHT);
	} else if (controllers.dpad(c, SC_BTN_LEFT)) {
		gamepad.setHatSync(c, DPAD_LEFT);
	} else {
		gamepad.setHatSync(c, DPAD_CENTERED);
	}
}

void setup() {
#ifdef DEBUG
	Serial.begin(115200);
#endif
	gamepad.begin();
}

void loop() {
	if (gamepad.isConnected()) {
		controllers.poll(controllerChanged);
	}
}
