#include <Keyboard.h>
#define __ 0x00000000 // No Chord
#define C_ 0x00000001 // Ctrl
#define _A 0x00000010 // Alt
#define CA 0x00000011 // Ctrl+Alt
#define DEBOUNCE_MILLIS 100

// *****************************
// **** START CHANGING HERE ****
// *****************************

#define ROWS 5
#define COLS 13

// Define Row/column pin order
const int rowPins[ROWS] = {A3,A2,A1,A0,15};
const int colPins[COLS] = {8,7,6,5,4,3,2,9,0,1,11,16,10};

// What to press

// Ctrl / Alt Chord keys
const uint8_t keyMapChord[ROWS][COLS] = {
  {__,__,_A, C_,__, __,__,__, __,__,__,__, __},
  {__,CA,CA, C_,C_, __,__,__, __,__,__,__, C_},
  {__,__,__, C_,C_, __,_A,_A, __,__,__,__, __},
  {C_,__,__, _A,CA, __,_A,_A, __,__,__,__, __},
  {C_,CA,C_, C_,_A, __,__,__, __,__,__,__, __}
};

// Command key map
// see this for special keys: https://www.arduino.cc/reference/en/language/functions/usb/keyboard/keyboardmodifiers/
const uint8_t keyMap[ROWS][COLS] = { 
    {KEY_LEFT_SHIFT,'h','f',  'o','o',  'q',KEY_LEFT_ARROW,KEY_RIGHT_ARROW,  '7','8','9','+',KEY_BACKSPACE},
    {KEY_PAGE_UP,   's','p',  'm','c',  'f',      'c',           'g',        '4','5','6','t','f'},
    {KEY_PAGE_DOWN, 'l','b',  'd','a',  'p',      's',           'c',        '1','2','3','-',KEY_ESC},
    {     'l',      '[',']',  't','h',  'e',      'a',           't',        '0','.','i','a',KEY_DELETE},
    {     'p',      'g','g',  's','g',  'u',      's',           's',        KEY_LEFT_SHIFT,'/',KEY_RETURN,KEY_RETURN,KEY_DELETE}
};

// ****************************
// **** STOP CHANGING HERE ****
// ****************************

// helper vars
bool someKeyIsAlreadyPressed = false;
bool pressedKey[ROWS][COLS] = {false};
uint8_t shiftPressesCount = 0;

// hardware helper vars
unsigned long currentMillis = 0;
unsigned long hwKeyLastMillis[ROWS][COLS] = {0};
bool hwKeyFlags[ROWS][COLS] = {false};

// ----------------------------
void setup() {
  
  for(int i = 0; i < ROWS; i++){
    pinMode(rowPins[i], OUTPUT);
  }

  for(int i = 0; i < COLS; i++){
    pinMode(colPins[i], INPUT_PULLUP);
  }

  Keyboard.begin();
}

// ----------------------------
void loop() {

  // cache current loop millis, not as acurrate (which doesn't matter), but waaaay faster
  currentMillis = millis();

  // Keyboard keys scan/polling
  for (uint8_t row = 0; row < ROWS; row++) {
    
    // set the row pin low, this begin the scan for that row.
    pinMode(rowPins[row], OUTPUT);
    digitalWrite(rowPins[row], LOW);
      
    for (uint8_t col = 0; col < COLS; col++) {

      // scan column value
      bool keyIsPressed = !digitalRead(colPins[col]);

      // key changed state and debounce
      if (hwKeyFlags[row][col] != keyIsPressed && (currentMillis - hwKeyLastMillis[row][col]) > DEBOUNCE_MILLIS){
        hwKeyFlags[row][col] = keyIsPressed;
        hwKeyLastMillis[row][col] = currentMillis;

        // Shift key?
        if (keyMap[row][col] == KEY_LEFT_SHIFT)
        {
          // Pressing shift
          if (keyIsPressed){
            shiftPressesCount++;
            Keyboard.press(keyMap[row][col]);
          }

          // Releasing Shift
          if(!keyIsPressed){
            shiftPressesCount--;
            Keyboard.release(keyMap[row][col]);
          }

          // continue the loop
          continue;
        }

        // Pressing a command key:
        // only one key is allowed at once to prevent unwanted key presses and ghosting
        // if more than 1 shift keys are pressed, also skip as this WILL cause ghosting
        if (keyIsPressed && !someKeyIsAlreadyPressed && shiftPressesCount < 2){
          pressedKey[row][col] = true;
          someKeyIsAlreadyPressed = true;

          // press ctrl/alt chord keys if necessary
          if (keyMapChord[row][col] & C_){
            Keyboard.press(KEY_LEFT_CTRL);
          }
          if (keyMapChord[row][col] & _A){
            Keyboard.press(KEY_LEFT_ALT);
          }

          // press the command key
          Keyboard.write(keyMap[row][col]);

          // release chord keys
          if (keyMapChord[row][col] & C_){
            Keyboard.release(KEY_LEFT_CTRL);
          }
          if (keyMapChord[row][col] & _A){
            Keyboard.release(KEY_LEFT_ALT);
          }
        }

        // Releasing key, should be the same as the currently pressed one
        if(!keyIsPressed && pressedKey[row][col])
        {
          pressedKey[row][col] = false;
          someKeyIsAlreadyPressed = false;
        }
      }
    }

    // Set row pin to high impedance input. Effectively ends row pulse.
    digitalWrite(rowPins[row],HIGH);
    pinMode(rowPins[row],INPUT);
  }
}