/*
 * Project: CMtec CMDR Keyboard 42
 * Date:    2023-09-02
 * Author:  Christoffer Martinsson
 * Email:   cm@cmtec.se
 * License: Please refer to LICENSE in root directory
 */

#include <Arduino.h>
#include <IndicatorLed.h>
#include <Keypad.h>

const uint8_t USB_LED_CAPS_LOCK = 1; // Caps lock LED definition from HID spec

const uint16_t KEY_OFFSET = 0xAA00;            // Offset to apply for not interfere with already defined keyboard keys
const uint16_t KEY_FN = 1 + KEY_OFFSET;        // Function layer button reference
const uint16_t KEY_GUI_LATCH = 2 + KEY_OFFSET; // GUI latching (ex: Win) button reference

const uint8_t NBR_OF_BUTTONS = 42; // Number of buttons used (42 in this case)

IndicatorLed status_led = IndicatorLed(13);

uint64_t current_timestamp = 0;
uint64_t button_timestamp = 0;
uint64_t indicator_timestamp = 0;

bool key_pressed = false;
bool win_latched = false;

const byte KEYBOARD_MATRIX_ROWS = 4;
const byte KEYBOARD_MATRIX_COLS = 12;

byte keyboard_martix_row_pins[KEYBOARD_MATRIX_ROWS] = {0, 1, 2, 3};
byte keyboard_matrix_col_pins[KEYBOARD_MATRIX_COLS] = {9, 8, 7, 6, 5, 4, 19, 18, 17, 16, 15, 14};

const uint8_t MAX_SIMULTANIOUS_KEYS = LIST_MAX;

const char keyboard_matrix_id[KEYBOARD_MATRIX_ROWS][KEYBOARD_MATRIX_COLS] = {
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
    {13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24},
    {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36},
    {0, 0, 0, 37, 38, 39, 40, 41, 42, 0, 0, 0},
};

Keypad keyboard_matrix =
    Keypad(makeKeymap(keyboard_matrix_id), keyboard_martix_row_pins, keyboard_matrix_col_pins, KEYBOARD_MATRIX_ROWS, KEYBOARD_MATRIX_COLS);

struct Button {
    uint16_t keycode = NO_KEY;
    uint16_t fn1_keycode = NO_KEY;
    uint16_t fn2_keycode = NO_KEY;
    uint8_t kstate = IDLE;
    uint16_t last_keycode = NO_KEY;
    bool run_keycode = false;
};

/*
   * "KeyId" (keyboard matrix id) corresponding with the physical design of the actual keyboard.
   -------------------------------------      -------------------------------------
   |  1  |  2  |  3  |  4  |  5  |  6  |      |  7  |  8  |  9  | 1O  | 11  | 12  |
   | 13  | 14  | 15  | 16  | 17  | 18  |      | 19  | 20  | 21  | 22  | 23  | 24  |
   | 25  | 26  | 27  | 28  | 29  | 30  |      | 31  | 32  | 33  | 34  | 35  | 36  |
   ------------------| 37  | 38  | 39  |      | 40  | 41  | 42  |------------------
                     -------------------      -------------------
   * "Fn0 key" is the layer 0 key to use.
   * "Fn1 key" is the layer 1 key to use. Don NOT add KEY_FN1 or KEY_FN2 to this layer.
   * "Fn2 key" is the layer 2 key to use. Don NOT add KEY_FN1 or KEY_FN2 to this layer.

   Swedish keymap (ISO) special characters (not matching the key definition):

   * KEY_TILDE = §
   * KEY_SEMICOLON = ö
   * KEY_QUOTE = ä
   * KEY_LEFT_BRACE = å
   * KEY_SLASH = -
   * KEY_NON_US_BS = <
   * KEY_EQUAL = ´
   * KEY_BACKSLASH = '
   * KEY_RIGHT_BRACE = ^
   * KEY_MINUS = +
   * KEY_LEFT_ALT = Alt
   * KEY_RIGHT_ALT = AltGr
*/

/* Keymap config ------------------------------------------------------------------------- */
// clang-format off
Button buttons[NBR_OF_BUTTONS] =
{
/* KeyId    Fn0 key           Fn1 key           Fn2 key                 - Do not change! - */
/* 1  */   {KEY_TAB,          KEY_ESC,          KEY_F11,                IDLE, NO_KEY, false},
/* 2  */   {KEY_Q,            KEY_F1,           KEY_F12,                IDLE, NO_KEY, false},
/* 3  */   {KEY_W,            KEY_F2,           KEY_F13,                IDLE, NO_KEY, false},
/* 4  */   {KEY_E,            KEY_F3,           KEY_F14,                IDLE, NO_KEY, false},
/* 5  */   {KEY_R,            KEY_F4,           KEY_F15,                IDLE, NO_KEY, false},
/* 6  */   {KEY_T,            KEY_F5,           KEY_F16,                IDLE, NO_KEY, false},
/* 7  */   {KEY_Y,            KEY_F6,           KEY_TILDE,              IDLE, NO_KEY, false},
/* 8  */   {KEY_U,            KEY_F7,           KEY_GUI_LATCH,          IDLE, NO_KEY, false},
/* 9  */   {KEY_I,            KEY_F8,           KEY_LEFT_GUI,           IDLE, NO_KEY, false},
/* 10 */   {KEY_O,            KEY_F9,           NO_KEY,                 IDLE, NO_KEY, false},
/* 11 */   {KEY_P,            KEY_F10,          KEY_CAPS_LOCK,          IDLE, NO_KEY, false},
/* 12 */   {KEY_LEFT_BRACE,   KEY_BACKSPACE,    KEY_BACKSPACE,          IDLE, NO_KEY, false},
/* 13 */   {KEY_LEFT_CTRL,    KEY_LEFT_CTRL,    KEY_LEFT_CTRL,          IDLE, NO_KEY, false},
/* 14 */   {KEY_A,            KEY_1,            KEY_MEDIA_PLAY_PAUSE,   IDLE, NO_KEY, false},
/* 15 */   {KEY_S,            KEY_2,            KEY_MEDIA_NEXT_TRACK,   IDLE, NO_KEY, false},
/* 16 */   {KEY_D,            KEY_3,            KEY_F17,                IDLE, NO_KEY, false},
/* 17 */   {KEY_F,            KEY_4,            KEY_F18,                IDLE, NO_KEY, false},
/* 18 */   {KEY_G,            KEY_5,            KEY_F19,                IDLE, NO_KEY, false},
/* 19 */   {KEY_H,            KEY_6,            KEY_LEFT_ARROW,         IDLE, NO_KEY, false},
/* 20 */   {KEY_J,            KEY_7,            KEY_DOWN_ARROW,         IDLE, NO_KEY, false},
/* 21 */   {KEY_K,            KEY_8,            KEY_UP_ARROW,           IDLE, NO_KEY, false},
/* 22 */   {KEY_L,            KEY_9,            KEY_RIGHT_ARROW,        IDLE, NO_KEY, false},
/* 23 */   {KEY_SEMICOLON,    KEY_0,            KEY_DELETE,             IDLE, NO_KEY, false},
/* 24 */   {KEY_QUOTE,        KEY_ENTER,        KEY_ENTER,              IDLE, NO_KEY, false},
/* 25 */   {KEY_LEFT_SHIFT,   KEY_LEFT_SHIFT,   KEY_LEFT_SHIFT,         IDLE, NO_KEY, false},
/* 26 */   {KEY_Z,            KEY_6,            KEY_F20,                IDLE, NO_KEY, false},
/* 27 */   {KEY_X,            KEY_7,            KEY_F21,                IDLE, NO_KEY, false},
/* 28 */   {KEY_C,            KEY_8,            KEY_F22,                IDLE, NO_KEY, false},
/* 29 */   {KEY_V,            KEY_9,            KEY_F23,                IDLE, NO_KEY, false},
/* 30 */   {KEY_B,            KEY_0,            KEY_F24,                IDLE, NO_KEY, false},
/* 31 */   {KEY_N,            KEY_NON_US_BS,    KEY_HOME,               IDLE, NO_KEY, false}, 
/* 32 */   {KEY_M,            KEY_EQUAL,        KEY_PAGE_DOWN,          IDLE, NO_KEY, false},
/* 33 */   {KEY_COMMA,        KEY_BACKSLASH,    KEY_PAGE_UP,            IDLE, NO_KEY, false},
/* 34 */   {KEY_PERIOD,       KEY_RIGHT_BRACE,  KEY_END,                IDLE, NO_KEY, false},
/* 35 */   {KEY_SLASH,        KEY_MINUS,        KEY_INSERT,             IDLE, NO_KEY, false},
/* 36 */   {KEY_RIGHT_SHIFT,  KEY_RIGHT_SHIFT,  KEY_RIGHT_SHIFT,        IDLE, NO_KEY, false},
/* 37 */   {KEY_LEFT_ALT,     KEY_LEFT_ALT,     KEY_LEFT_ALT,           IDLE, NO_KEY, false},
/* 38 */   {KEY_FN,           NO_KEY,           NO_KEY,                 IDLE, NO_KEY, false},
/* 39 */   {KEY_SPACE,        KEY_BACKSPACE,    KEY_LEFT_GUI,           IDLE, NO_KEY, false},
/* 40 */   {KEY_SPACE,        KEY_BACKSPACE,    KEY_BACKSPACE,          IDLE, NO_KEY, false},
/* 41 */   {KEY_FN,           NO_KEY,           NO_KEY,                 IDLE, NO_KEY, false},
/* 42 */   {KEY_FN,           KEY_RIGHT_ALT,    NO_KEY,                 IDLE, NO_KEY, false}};

// clang-format on
/* End of keymap config ------------------------------------------------------------------ */

/**
 * @breif set_key.
 *
 * @param keycode code to apply action.
 * @param kstate PRESSED or RELEASED.
 */
void set_key(uint16_t keycode, uint8_t kstate) {
    if (keycode == NO_KEY || keycode == KEY_FN) return;

    if (keycode == KEY_GUI_LATCH) {
        win_latched = true;
        return;
    }

    if (kstate == RELEASED) {
        if (win_latched == true) {
            Keyboard.release(keycode);
            Keyboard.release(KEY_LEFT_GUI);
            win_latched = false;
        } else {
            Keyboard.release(keycode);
        }
    } else if (kstate == PRESSED) {
        if (win_latched == true) {
            Keyboard.press(KEY_LEFT_GUI);
            Keyboard.press(keycode);
        } else {
            Keyboard.press(keycode);
        }
    }
}

/**
 * @breif process_keys in keyboard matrix.
 */
void process_keys() {
    /* Scan keypad, exit if not ready */
    if (keyboard_matrix.getKeys() == false) return;

    int fn_mode = 0;
    int corner_pressed = 0;

    for (int i = 0; i < MAX_SIMULTANIOUS_KEYS; i++) {
        /* Update button table if key pressed/releaseed */
        if ((keyboard_matrix.key[i].kstate == PRESSED || keyboard_matrix.key[i].kstate == RELEASED) &&
            (keyboard_matrix.key[i].stateChanged == true)) {
            buttons[keyboard_matrix.key[i].kchar - 1].run_keycode = true;
            buttons[keyboard_matrix.key[i].kchar - 1].kstate = keyboard_matrix.key[i].kstate;
        }

        /* Skip if key is not pressed */
        if (keyboard_matrix.key[i].kstate == IDLE || keyboard_matrix.key[i].kstate == RELEASED) continue;

        /* Count number of corner keys pressed */
        if (keyboard_matrix.key[i].kchar == 1) corner_pressed++;  // Upper left
        if (keyboard_matrix.key[i].kchar == 25) corner_pressed++; // Upper right
        if (keyboard_matrix.key[i].kchar == 12) corner_pressed++; // Lower left
        if (keyboard_matrix.key[i].kchar == 36) corner_pressed++; // Lower right

        /* Count number of FN keys pressed */
        if (buttons[keyboard_matrix.key[i].kchar - 1].keycode == KEY_FN) fn_mode++;
    }

    if (corner_pressed == 4) {
        Keyboard.releaseAll();
        status_led.on();
        status_led.update();
        delay(200); // Wait for usb to settle before rebooting
        _reboot_Teensyduino_();
    }

    for (int i = 0; i < NBR_OF_BUTTONS; i++) {
        /* Check if key is ready to be set. Else skip to next button */
        if (buttons[i].run_keycode == false) continue;
        buttons[i].run_keycode = false;

        if (buttons[i].kstate == RELEASED) {
            set_key(buttons[i].last_keycode, RELEASED);
            continue;
        }

        if (fn_mode == 0) {
            set_key(buttons[i].keycode, PRESSED);
            buttons[i].last_keycode = buttons[i].keycode;
        } else if (fn_mode == 1) {
            set_key(buttons[i].fn1_keycode, PRESSED);
            buttons[i].last_keycode = buttons[i].fn1_keycode;
        } else if (fn_mode == 2) {
            set_key(buttons[i].fn2_keycode, PRESSED);
            buttons[i].last_keycode = buttons[i].fn2_keycode;
        }
    }
}

void setup() {
    /* Init HW */
    status_led.begin();
}

void loop() {
    /* Update current time (ms) */
    current_timestamp = millis();

    /* Scan buttons 1ms */
    if (current_timestamp >= button_timestamp) {
        process_keys();
        button_timestamp = current_timestamp + 1;
    }

    /* Update indicator 200ms */
    if (current_timestamp >= indicator_timestamp) {
        /* Set status indication */
        if (keyboard_leds & (1 << USB_LED_CAPS_LOCK)) {
            status_led.blink();
        } else if (win_latched) {
            status_led.on();
        } else {
            status_led.off();
        }

        status_led.update();
        indicator_timestamp = current_timestamp + 200;
    }
}
