/*
N64:
      LOOKING AT THE PLUG FROM CONTROLLER
           . ~~~ .
       .-~         ~-.
      /   o   o   o   \
     /    1   2   3    \
     |_________________|


PIN # USAGE (colors from my extension cable, check your own)

  1:  GND            - red
  2:  DATA           - white
  3:  VCC +3.3V ONLY - black

GAMECUBE:
      LOOKING AT THE PLUG ON FRONT OF CONSOLE (not coming from controller)

               7
        .-------------.
       /       o       \
      /                 \
     |     1   2   3     |
     |     o   o   o     |
     |                   |
      \    o   o   o    /
       \   4   5   6   /
        `-.         .-'
            ~-._.-~

   PIN # USAGE (colors from my extension cable, check your own)

  1:  VCC 5V rumble  - grey
  2:  DATA           - yellow
  3:  GND            - black
  4:  GND            - black
  5:  UNKNOWN
  6:  VCC 3.3V ONLY  - red
  7:  GND            - black

  I tie all 3 grounds together
*/

#include "pins.h"

#define DATA_PIN OR_PIN_2

// how often to poll, 100? 14? polling must not occur faster than every 20 ms
#define POLL_DELAY 14

#include "Arduino.h"

#include "Nintendo.h"

//#define DEBUG

#ifdef GAMECUBE

/*
// these are my average values from my apparantly very bad wavebird controller
#define AXIS_CENTER_IN 135
#define AXIS_MAX_IN 230
#define AXIS_MIN_IN 30

#define TRIGGER_MAX_IN 236
#define TRIGGER_MIN_IN 36
*/
// these look like much more proper values from a pelican gc controller
#define AXIS_CENTER_IN 128
#define AXIS_MAX_IN 255
#define AXIS_MIN_IN 0

#define TRIGGER_MAX_IN 255
#define TRIGGER_MIN_IN 0

#else  // N64
#define AXIS_CENTER_IN 0
#define AXIS_MAX_IN 80
#define AXIS_MIN_IN -80
#endif

#include "gamepad/Gamepad.h"
#include "util.cpp"

#ifdef GAMECUBE
typedef CGamecubeController NintendoController;
typedef Gamecube_Report_t ControllerReport;
#define NINTENDO_REPORT_SIZE 8
#else  // N64
typedef CN64Controller NintendoController;
typedef N64_Report_t ControllerReport;
#define NINTENDO_REPORT_SIZE 4
#endif

// Define a Controller
NintendoController controller(DATA_PIN);

uint8_t oldReport[NINTENDO_REPORT_SIZE];

GAMEPAD_CLASS gamepad;

#ifdef DEBUG
void print_report(ControllerReport &controller) {
	Serial.print("buttons: ");
	Serial.print(controller.a ? "A" : "-");
	Serial.print(controller.b ? "B" : "-");
	Serial.print(controller.z ? "Z" : "-");
	Serial.print(controller.l ? "L" : "-");
	Serial.print(controller.r ? "R" : "-");
	Serial.print(controller.start ? "S" : "-");
#ifdef GAMECUBE
	Serial.print(controller.y ? "Y" : "-");
	Serial.print(controller.x ? "X" : "-");
#endif
	Serial.print(" DPAD: ");
	Serial.print(controller.dup ? "U" : "-");
	Serial.print(controller.ddown ? "D" : "-");
	Serial.print(controller.dleft ? "L" : "-");
	Serial.print(controller.dright ? "R" : "-");
	Serial.print(" Y: ");
	Serial.print(controller.yAxis);
	Serial.print(" YT: ");
	Serial.print(-translateAxis(controller.yAxis));
	Serial.print(" X: ");
	Serial.print(controller.xAxis);
	Serial.print(" XT: ");
	Serial.print(translateAxis(controller.xAxis));
#ifdef GAMECUBE
	Serial.print(" CY: ");
	Serial.print(controller.cyAxis);
	Serial.print(" CYT: ");
	Serial.print(-translateAxis(controller.cyAxis));
	Serial.print(" CX: ");
	Serial.print(controller.cxAxis);
	Serial.print(" CXT: ");
	Serial.print(translateAxis(controller.cxAxis));
	Serial.print(" LEFT: ");
	Serial.print(controller.left);
	Serial.print(" TLEFT: ");
	Serial.print(translateTrigger(controller.left));
	Serial.print(" RIGHT: ");
	Serial.print(controller.right);
	Serial.print(" TRIGHT: ");
	Serial.print(translateTrigger(controller.right));
#else  // N64
	Serial.print(" C: ");
	Serial.print(controller.cup ? "U" : "-");
	Serial.print(controller.cdown ? "D" : "-");
	Serial.print(controller.cleft ? "L" : "-");
	Serial.print(controller.cright ? "R" : "-");
#endif
	Serial.println();
}
#endif

void setup() {
	// n64 low because it *should* be 3.3V
	digitalWrite(DATA_PIN, LOW);
#ifdef DEBUG
	Serial.begin(115200);
	if (controller.begin()) {
		Serial.println(F("controller.begin() success."));
	} else {
		Serial.println(F("controller.begin() fail."));
	}
#else
	controller.begin();
#endif

	gamepad.begin();
}

void loop() {
	delay(POLL_DELAY);
	// Try to read the controller data
	if (controller.read()) {
		// Print Controller information
		auto report = controller.getReport();

		if (memcmp(oldReport, report.raw8, NINTENDO_REPORT_SIZE)) {
			memcpy(oldReport, report.raw8, NINTENDO_REPORT_SIZE);
		} else {
			// nothing changed
			return;
		}

#ifdef DEBUG
		print_report(report);
#endif
		uint8_t c = 0;	// for now just do 1 pad
		gamepad.buttons(c, 0);
		if (report.start) {
			if (report.ddown) {
				// then only send menu, nothing else
				gamepad.press(c, BUTTON_MENU);
				gamepad.setHatSync(c, DPAD_CENTER);
				return;
			}
			gamepad.press(c, BUTTON_START);
		}
		if (report.a) {
			gamepad.press(c, BUTTON_A);
		}
		if (report.b) {
			gamepad.press(c, BUTTON_B);
		}
		if (report.z) {
			gamepad.press(c, BUTTON_TL2);
		}
		if (report.l) {
			gamepad.press(c, BUTTON_L);
		}
		if (report.r) {
			gamepad.press(c, BUTTON_R);
		}
		auto hat = calculateDpadDirection(report.dup, report.ddown, report.dleft, report.dright);
#ifdef GAMECUBE
		if (report.y) {
			gamepad.press(c, BUTTON_Y);
		}
		if (report.x) {
			gamepad.press(c, BUTTON_X);
		}
		gamepad.setAxis(c,
						translateAxis(report.xAxis),
						-translateAxis(report.yAxis),
						translateAxis(report.cxAxis),
						-translateAxis(report.cyAxis),
						translateTrigger(report.left),
						translateTrigger(report.right),
						hat);
#else  // N64
		auto cHat = dpadToAxis(calculateDpadDirection(report.cup, report.cdown, report.cleft, report.cright));
		gamepad.setAxis(c, translateAxis(report.xAxis), -translateAxis(report.yAxis), cHat.x, cHat.y, 0, 0, hat);
#endif
	} else {
		// Add debounce if reading failed
		delay(100);
#ifdef DEBUG
		Serial.println(F("Error reading controller."));
		if (controller.begin()) {
			Serial.println(F("controller.begin() success."));
		} else {
			Serial.println(F("controller.begin() fail."));
		}
#else
		controller.begin();
#endif
	}
}
