//=============================================================================
// iR Hackpad Demo Firmware
// Compile for Arduino Leonardo
//=============================================================================

// The Arduino Keyboard library creates a HID keyboard interface to send keyboard scancodes to the host.
// For convenience, the class takes ASCII characters via Keyboard::write(), which are automatically
// converted to the corresponding keboard scan-codes according to the keyboard layout passed to
// Keyboard::begin(). On the host side, the scan-codes are again converted to characters by the OS.
#include "Keyboard.h"

//=============================================================================
// Our key matrix (see schematics)
const int N_rows = 5;
const int N_cols = 4;
const int gpio_rows[N_rows] = { 6, 7, 8, 9, 5 };
const int gpio_cols[N_cols] = { 15, 14, 16, 10 };

// Key function assignment: ASCII characters or special function codes (see Keyboard.h)
const int key_matrix[N_rows][N_cols] = {
  {'a', 'b', 'c', 'd'},  // 4x4 keypad
  {'e', 'f', 'g', 'h'},
  {'i', 'j', 'k', 'l'},
  {'m', 'n', 'o', 'p'},
  {'_', '_', '_', 'x'}   // rotery encoder button
};

// Key state
struct {
  bool last;          // Last debounced state
  bool inhibit;       // State change inhibited during debounce period
  uint32_t since_ms;  // Inhibited since timestamp
} key_state[N_rows][N_cols];

const uint32_t KEY_DEBOUNCE_MS = 50;     // Key Debounce period
const uint32_t KEY_SCAN_DELAY_US = 200;  // Delay between row scans

//=============================================================================

void setup()
{
  for (int col=0; col<N_cols; col++) {
    pinMode(gpio_cols[col], INPUT_PULLUP);  // Key matrix columns as inputs
  }
  for (int row=0; row<N_rows; row++) {
    digitalWrite(gpio_rows[row], HIGH);
    pinMode(gpio_rows[row], OUTPUT);  // Key matrix rows as outputs
  }

  memset( &key_state, 0, sizeof(key_state) );  // Clear key state

  Keyboard.begin(KeyboardLayout_de_DE);
  Serial.begin(9600);  // Open serial port to host, so the IDE can reset the device for reprogramming
}

//=============================================================================
// Key debouncing:
// React immediately to rising edge (pressed == 1 and last_pressed == 0)
// After that, inhibit further state changes for KEY_DEBOUNCE_MS milliseconds.

void eval_key( int row, int col, bool pressed )
{
  // Remove inhibit after debounce period
  if (key_state[row][col].inhibit &&
          ((millis() - key_state[row][col].since_ms) > KEY_DEBOUNCE_MS))  {
      key_state[row][col].inhibit = 0;
  }

  if (pressed == true) {                       // Key is pressed?
    if (key_state[row][col].last == false) {    // Rising edge: Key has just been pressed?
      key_state[row][col].last = true;
      key_state[row][col].inhibit = true;
      key_state[row][col].since_ms = millis();
      Keyboard.press(key_matrix[row][col]);
    }

    // TODO: Repeat delay, repeat rate

  }
  else  {  // Pressed == false
    if (!key_state[row][col].inhibit)
      key_state[row][col].last = false;
      Keyboard.release(key_matrix[row][col]);
  }
}

//=============================================================================

void loop()
{
  for (int row=0; row<N_rows; row++) {
    digitalWrite(gpio_rows[row], LOW);
    for (int col=0; col<N_cols; col++) {
      const bool pressed = (digitalRead(gpio_cols[col]) == LOW);  // Input level low: Key is pressed
      eval_key( row, col, pressed );
    }
    digitalWrite(gpio_rows[row], HIGH);
    delayMicroseconds(KEY_SCAN_DELAY_US);  // Give the column inputs time to reach HIGH state (slow recharge via weak pullups)
  }
}

//=============================================================================
