#include <bluefruit.h>

// Pin definitions for the 9 buttons
const int buttonPins[] = {1, 2, 3, 4, 5, 6, 8, 9, 10};
const int numButtons = sizeof(buttonPins) / sizeof(buttonPins[0]);

// Button state tracking
struct Button {
  int pin;
  bool lastState;
  unsigned long lastDebounceTime;
  unsigned long pressTime;
  int clickCount;
  bool isPressed;
  bool longPressHandled;
};

Button buttons[numButtons];

// Debounce and timing constants
const unsigned long debounceDelay = 50;
const unsigned long doubleClickDelay = 400;
const unsigned long longPressDelay = 1000;

// BLE Keyboard object
BLEDis bledis;
BLEHidAdafruit blehid;

// Character mapping for each event
// 9 buttons * 3 events/button = 27 characters
const char eventChars[numButtons][3] = {
  {'a', 'b', 'c'}, // Button D1: single, double, long
  {'d', 'e', 'f'}, // Button D2: single, double, long
  {'g', 'h', 'i'}, // Button D3: single, double, long
  {'j', 'k', 'l'}, // Button D4: single, double, long
  {'m', 'n', 'o'}, // Button D5: single, double, long
  {'p', 'q', 'r'}, // Button D6: single, double, long
  {'s', 't', 'u'}, // Button D8: single, double, long
  {'v', 'w', 'x'}, // Button D9: single, double, long
  {'y', 'z', '1'}  // Button D10: single, double, long
};

void setup() {
  Serial.begin(115200);
  //while ( !Serial ) delay(10); // Wait for serial port to connect.

  Serial.println("Nexus Game Controller Starting...");

  // Initialize buttons
  for (int i = 0; i < numButtons; i++) {
    buttons[i].pin = buttonPins[i];
    pinMode(buttons[i].pin, INPUT_PULLUP);
    buttons[i].lastState = HIGH;
    buttons[i].lastDebounceTime = 0;
    buttons[i].pressTime = 0;
    buttons[i].clickCount = 0;
    buttons[i].isPressed = false;
    buttons[i].longPressHandled = false;
  }

  // Setup Bluetooth
  Bluefruit.begin();
  Bluefruit.setName("Nexus Game Controller");
  Bluefruit.setTxPower(4);

  // Configure and start BLE services
  bledis.setManufacturer("Seeed Studio");
  bledis.setModel("XIAO nRF52840 Sense");
  bledis.begin();

  blehid.begin();

  // Start advertising
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
  Bluefruit.Advertising.addService(blehid);
  Bluefruit.Advertising.addName();
  
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast advertising mode
  Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds

  Serial.println("Advertising...");

  // Set up connection/disconnection callbacks
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
}

void connect_callback(uint16_t conn_handle) {
  (void) conn_handle;
  Serial.println("Bluetooth connected");
}

void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
  (void) conn_handle;
  (void) reason;
  Serial.println("Bluetooth disconnected");
}


void loop() {
  for (int i = 0; i < numButtons; i++) {
    handleButton(&buttons[i], i);
  }
}

void handleButton(Button* b, int buttonIndex) {
  bool reading = digitalRead(b->pin);

  // Debounce logic
  if (reading != b->lastState) {
    b->lastDebounceTime = millis();
  }

  if ((millis() - b->lastDebounceTime) > debounceDelay) {
    // If the button state has been stable
    if (reading == LOW && !b->isPressed) { // Button just pressed
      b->isPressed = true;
      b->pressTime = millis();
      b->clickCount++;
      b->longPressHandled = false;
    } else if (reading == HIGH && b->isPressed) { // Button just released
      b->isPressed = false;
      if (!b->longPressHandled) {
        // It's a click, but we need to wait for a potential double click
      } else {
        // This was a long press, so we do nothing on release
        b->longPressHandled = false; // Reset for next time
      }
    }
  }

  // Check for long press
  if (b->isPressed && !b->longPressHandled && (millis() - b->pressTime > longPressDelay)) {
    triggerEvent(buttonIndex, 2); // Long Press
    b->longPressHandled = true;
    b->clickCount = 0; // Reset click count after a long press
  }

  // Check for single/double click timeout
  if (b->clickCount > 0 && !b->isPressed && (millis() - b->pressTime > doubleClickDelay)) {
    if (!b->longPressHandled) {
      if (b->clickCount == 1) {
        triggerEvent(buttonIndex, 0); // Single Click
      } else if (b->clickCount == 2) {
        triggerEvent(buttonIndex, 1); // Double Click
      }
    }
    b->clickCount = 0;
  }

  b->lastState = reading;
}

void triggerEvent(int buttonIndex, int eventType) {
  // eventType: 0 = single, 1 = double, 2 = long
  char key = eventChars[buttonIndex][eventType];
  
  const char* eventStr[] = {"single click", "double click", "long press"};

  Serial.print("Button D");
  Serial.print(buttonPins[buttonIndex]);
  Serial.print(" triggered ");
  Serial.print(eventStr[eventType]);
  Serial.print(" event. Character '");
  Serial.print(key);
  Serial.println("' has been sent.");

  if (Bluefruit.connected()) {
    blehid.keyPress(key);
    delay(10); // a small delay to prevent flooding
    blehid.keyRelease();
  }
}
