// File recommented by recomment.cpp
// on Dec  9 2019 at 10:14:05.
//
// -*- mode: C; tab-width: 2 ; indent-tabs-mode: nil -*-
//
//  Universal Keyboard Project
//  ASDF firmware - small, fast, and simple keyboard encoder.
//
//  asdf.c
//
// This file contains code for:
// - the main scan code and key handler routines
// - key matrix declarations and keyboard state variables not delegated
//   elsewhere.
// - Key debouncing logic and data stuctures
// - dispatch of special functions bound to keys.
//
// Copyright 2019 David Fenyes
//
//  This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.

#include <stdio.h>
#include <stdint.h>
#include "asdf.h"
#include "asdf_ascii.h"
#include "asdf_physical.h"
#include "asdf_virtual.h"
#include "asdf_keymaps.h"
#include "asdf_repeat.h"
#include "asdf_modifiers.h"
#include "asdf_buffer.h"
#include "asdf_arch.h"
#include "asdf_hook.h"

// The key scanner keeps track of the last stable (debounced) state of each key
// in the matrix, one bit per key, 8 bits per row.
static asdf_cols_t last_stable_key_state[ASDF_MAX_ROWS];

// Each key is debounced separately, supporting true N-key rollover, allowing a
// new key to be pressed when the previously pressed key is still debouncing.
// This requires a debounce counter for each key. This is is a large RAM
// footprint, 64 bytes for an 8 x 8 key matrix. It would be possible to only
// debounce the most recently pressed key and reduce the RAM usage to a single
// debounce counuter. However, this would probably require different handling of
// key presses and releases, and special handling for modifier keys, perhaps
// including separate debounce logic in the handlers for toggle keys such as
// CAPS_LOCK.
static uint8_t debounce_counters[ASDF_MAX_ROWS][ASDF_MAX_COLS];

// Stores the last key pressed
static asdf_keycode_t last_key;

// This is the handle for the char output buffer
static asdf_buffer_handle_t asdf_keycode_buffer;

// This is the handle for the message output buffer
static asdf_buffer_handle_t asdf_message_buffer;

// The delay time for strings sent via parallel port. This is needed because
// most keyboards have no handshaking, and we need to allow time for various
// environments to process the characters. Some, such as basic interpreters, are
// rather slow.  So we we allow keyboard modules to modifiy this delay.
static uint16_t asdf_print_delay_ms;

// PROCEDURE: asdf_put_code
// INPUTS: (asdf_keycode_t) code: code to be buffered for output
// OUTPUTS: none
//
// DESCRIPTION: Takes a keycode argument and buffers for output.
//
// SIDE EFFECTS: modifies buffer state.
//
// NOTES: If buffer is full, silently drop the code.
//
// SCOPE: public
//
// COMPLEXITY: 1
//
void asdf_put_code(asdf_keycode_t code)
{
  asdf_buffer_put(asdf_keycode_buffer, code);
}

// PROCEDURE: asdf_putc
// INPUTS: (char) c: character to be buffered for output
//         (FILE *) stream - only used to match prototype.
// OUTPUTS: none
//
// DESCRIPTION: Takes a character generated by the system and buffers for
// output.
//
// SIDE EFFECTS: modifies buffer state.
//
// NOTES: If buffer is full, silently drop the code.
//
// SCOPE: public
//
// COMPLEXITY: 1
//
int asdf_putc(char c, FILE *stream)
{
  // for messages, add CR to LF:
  if ('\n' == c) {
    asdf_putc('\r', stream);
  }
  asdf_buffer_put(asdf_message_buffer, (asdf_keycode_t) c);
  return (int) c;
}

// PROCEDURE: asdf_next_code
// INPUTS: none
//
// OUTPUTS: (asdf_keycode_t) returns next value in buffer. If both buffers are
// empty, the code ASDF_INVALID_CODE is returned.
//
// DESCRIPTION: Checks the message buffer, and returns a character
// if present.  Otherwise, return the next code in the keycode
// buffer.
//
// SIDE EFFECTS: modifies buffer state.
//
// NOTES: A delay is enforced for system messages, to reduce the risk of dropped
// characters with unbuffered polling hosts. No delay is needed for typed
// keycodes, as these are generated at human speeds.
//
// SCOPE: public
//
// COMPLEXITY: 2
//
asdf_keycode_t asdf_next_code(void)
{
  asdf_keycode_t code = asdf_buffer_get(asdf_message_buffer);
  if (ASDF_INVALID_CODE == code) {
    code = asdf_buffer_get(asdf_keycode_buffer);
  } else {
    // for system message
    asdf_arch_delay_ms(asdf_print_delay_ms);
  }
  return code;
}

// PROCEDURE: asdf_set_print_delay
// INPUTS: (uint8_t) delay_ms
//
// OUTPUTS: none
//
// DESCRIPTION: sets the delay to be used by the system print buffer
//
// SIDE EFFECTS: modifies character delay variable.
//
// NOTES: A delay is enforced for system messages, to reduce the risk of dropped
// characters with unbuffered polling hosts. No delay is needed for typed
// keycodes, as these are generated at human speeds.
//
// SCOPE: public
//
// COMPLEXITY: 1
//
void asdf_set_print_delay(uint8_t delay_ms)
{
  asdf_print_delay_ms = delay_ms;
}

// PROCEDURE: asdf_lookup_keycode
// INPUTS: row, col: specify a row and column in the keyboard matrix
// OUTPUTS: returns a keycode
//
// DESCRIPTION: Given a row and column in the keyboard matrix, returns the
// corresponding keycode, depending on the state of the modifier keys.
//
// SIDE EFFECTS: none
//
// NOTES:
//
// SCOPE: private
//
// COMPLEXITY: 1
//
asdf_keycode_t asdf_lookup_keycode(uint8_t row, uint8_t col)
{
  return asdf_keymaps_get_code(row, col, asdf_modifier_index());
}

// PROCEDURE: asdf_activate_action
// INPUTS: keycode: an action key code
// OUTPUTS: none
//
// DESCRIPTION: This routine is called when a key bound to an action code is
// pressed, and maps the action code to a function call, or other action
// appropriate for activation of the function.
//
// SIDE EFFECTS: All the actions may have side effects, depending on the function called.
//
// SCOPE: private
//
// NOTES: The switch() could be implemented as an array of function pointers,
// essentially a jump table. However, the switch statement will also be
// implemented as a jump table, and may be more efficiently implemented as a
// code jump table than an array of pointers stored in flash that would require
// an additional flash-read operation. Also, the switch jumptable will make more
// efficient use of space than a sparse array of function pointers. The
// reduction in cyclometric complexity by using an array is a technicality,
// since the elements must still be entered as lines of code in some part of the
// program.  Here, they are arranged in the place they are used.
//
// COMPLEXITY: 1 (+18 for the switch)
//
static void asdf_activate_action(action_t keycode)
{
  switch (keycode) {

    case ACTION_SHIFT: {
      asdf_modifier_shift_activate();
      break;
    }
    case ACTION_SHIFTLOCK_ON: {
      asdf_modifier_shiftlock_on_activate();
      break;
    }
    case ACTION_SHIFTLOCK_TOGGLE: {
      asdf_modifier_shiftlock_toggle_activate();
      break;
    }
    case ACTION_CAPS: {
      asdf_modifier_capslock_activate();
      break;
    }
    case ACTION_CTRL: {
      asdf_modifier_ctrl_activate();
      break;
    }
    case ACTION_REPEAT: {
      asdf_repeat_activate();
      break;
    }
    case ACTION_MAPSEL_0: {
      asdf_keymaps_map_select_0_set();
      break;
    }
    case ACTION_MAPSEL_1: {
      asdf_keymaps_map_select_1_set();
      break;
    }
    case ACTION_MAPSEL_2: {
      asdf_keymaps_map_select_2_set();
      break;
    }
    case ACTION_MAPSEL_3: {
      asdf_keymaps_map_select_3_set();
      break;
    }
    case ACTION_STROBE_POLARITY_SELECT: {
      asdf_arch_set_pos_strobe();
      break;
    }
    case ACTION_AUTOREPEAT_SELECT: {
      asdf_repeat_auto_on();
      break;
    }
    case ACTION_VLED1: {
      asdf_virtual_activate(VLED1);
      break;
    }
    case ACTION_VLED2: {
      asdf_virtual_activate(VLED2);
      break;
    }
    case ACTION_VLED3: {
      asdf_virtual_activate(VLED3);
      break;
    }
    case ACTION_VOUT1: {
      asdf_virtual_activate(VOUT1);
      break;
    }
    case ACTION_VOUT2: {
      asdf_virtual_activate(VOUT2);
      break;
    }
    case ACTION_VOUT3: {
      asdf_virtual_activate(VOUT3);
      break;
    }
    case ACTION_VOUT4: {
      asdf_virtual_activate(VOUT4);
      break;
    }
    case ACTION_VOUT5: {
      asdf_virtual_activate(VOUT5);
      break;
    }
    case ACTION_VOUT6: {
      asdf_virtual_activate(VOUT6);
      break;
    }
    case ACTION_NOTHING:
    case ACTION_HERE_IS:
    case ACTION_FN_1:
      asdf_hook_execute(ASDF_HOOK_USER_1);
      break;
    case ACTION_FN_2:
      asdf_hook_execute(ASDF_HOOK_USER_2);
      break;
    case ACTION_FN_3:
      asdf_hook_execute(ASDF_HOOK_USER_3);
      break;
    case ACTION_FN_4:
      asdf_hook_execute(ASDF_HOOK_USER_4);
      break;
    case ACTION_FN_5:
      asdf_hook_execute(ASDF_HOOK_USER_5);
      break;
    case ACTION_FN_6:
      asdf_hook_execute(ASDF_HOOK_USER_6);
      break;
    case ACTION_FN_7:
      asdf_hook_execute(ASDF_HOOK_USER_7);
      break;
    case ACTION_FN_8:
      asdf_hook_execute(ASDF_HOOK_USER_8);
      break;
    case ACTION_FN_9:
      asdf_hook_execute(ASDF_HOOK_USER_9);
      break;
    case ACTION_FN_10:
      asdf_hook_execute(ASDF_HOOK_USER_10);
      break;
    default: break;
  }
}

// PROCEDURE: asdf_deactivate_action
// INPUTS: keycode: an action key code
// OUTPUTS: none
//
// DESCRIPTION: This routine is called when a key bound to an action code is
// released, and maps the action code to a function call, or other action
// appropriate for deactivation of the function.
//
// SIDE EFFECTS: All the actions may have side effects, depending on the function called.
//
// SCOPE: private
//
// NOTES: See NOTE for asdf_activate_action(). The Virtual Outputs have
// activate() functions, but no deactivate() functions, and are handled by the
// default case.
//
// COMPLEXITY: 1 (+9 for the switch, see note)
//
static void asdf_deactivate_action(action_t keycode)
{
  switch (keycode) {
    case ACTION_SHIFT: {
      asdf_modifier_shift_deactivate();
      break;
    }
    case ACTION_CTRL: {
      asdf_modifier_ctrl_deactivate();
      break;
    }
    case ACTION_REPEAT: {
      asdf_repeat_deactivate();
      break;
    }
    case ACTION_MAPSEL_0: {
      asdf_keymaps_map_select_0_clear();
      break;
    }
    case ACTION_MAPSEL_1: {
      asdf_keymaps_map_select_1_clear();
      break;
    }
    case ACTION_MAPSEL_2: {
      asdf_keymaps_map_select_2_clear();
      break;
    }
    case ACTION_MAPSEL_3: {
      asdf_keymaps_map_select_3_clear();
      break;
    }
    case ACTION_STROBE_POLARITY_SELECT: {
      asdf_arch_set_neg_strobe();
      break;
    }
    case ACTION_AUTOREPEAT_SELECT: {
      asdf_repeat_auto_off();
      break;
    }
    case ACTION_NOTHING:
    default: break;
  }
}

// PROCEDURE: asdf_activate_key
// INPUTS: keycode - the code for a key that has been pressed.
// OUTPUTS: none
//
// DESCRIPTION: Called when a key has been pressed. If the key is bound to a
// value, output the value. If the key is bound to an action, call the activate action
// handler with the keycode.
//
// SIDE EFFECTS: If the code is a value, the last key is updated to the current
// value, and repeat timer is reset. If the code is an action code, the activate
// action dispatcher produces side effects.  See asdf_activate_action()
//
// SCOPE: private
//
// COMPLEXITY: 3
//
static void asdf_activate_key(asdf_keycode_t keycode)
{
  if (keycode > ASDF_ACTION) { // ASDF_ACTION = ASDF_NOTHING = no action.
    asdf_activate_action((action_t) keycode);
  }
  else {

    // activate a new codable keypress
    asdf_put_code(keycode);
    if (last_key != keycode) {
      last_key = keycode;
      asdf_repeat_reset_count();
    }
  }
}

// PROCEDURE: asdf_deactivate_key
// INPUTS: keycode - the code for a key that has been released
// OUTPUTS: none
//
// DESCRIPTION: Called when a key has been released. If the key is bound to a
// value, output the value. If the key is bound to an action, call the deactivate
// action handler with the keycode.
//
// SIDE EFFECTS: If the code is a value, the last key is set to ACTION_NOTHING,
// which is effectively a NOP. If the code is an action code, the deactivate action
// dispatcher produces side effects. See asdf_deactivate_action()
//
// SCOPE: private
//
// COMPLEXITY: 3
//
static void asdf_deactivate_key(asdf_keycode_t keycode)
{
  if (keycode > ASDF_ACTION) {
    asdf_deactivate_action((action_t) keycode);
  }
  else {
    // deactivate a released keypress
    if (last_key == keycode) {
      last_key = ACTION_NOTHING;
    }
  }
}

// PROCEDURE: asdf_handle_key_press_or_release
// INPUTS: row, col: the row and column number of the key that has changed.
// OUTPUTS: none
//
// DESCRIPTION: Given a row and column of a key that has changed:
//
// 1) Debounce the key by decrementing a debounce timer. If not yet debounced,
//    return.
// 2) When the key is debounced, if the key is pressed, then call the activation
//    function associated with the key. Otherwise, if the key is released, call
//    the deactivation function for the key.
//
// SIDE EFFECTS: - Modifies debounce counter for the row and column
//               - After key debounces, modifies the table of stable key states
//               - Activate/deactivate functions have side effects
//
// NOTES:
//
// SCOPE: private
//
// COMPLEXITY: 3
//
static void asdf_handle_key_press_or_release(uint8_t row, uint8_t col, uint8_t key_was_pressed)
{
  uint8_t *debounce_count = &debounce_counters[row][col];

  if (!(--(*debounce_count))) {

    // debounce timed out. Set new stable state and activate or

    // deactivate key.
    *debounce_count = ASDF_DEBOUNCE_TIME_MS;
    if (key_was_pressed) {
      last_stable_key_state[row] |= 1 << col;
      asdf_activate_key(asdf_lookup_keycode(row, col));
    }
    else {
      // key was released
      last_stable_key_state[row] &= ~(1 << col);
      asdf_deactivate_key(asdf_lookup_keycode(row, col));
    }
  }
}

// PROCEDURE: asdf_handle_key_held_pressed
// INPUTS: row, col: The row and column of a key that is being held down
// OUTPUTS: none
//
// DESCRIPTION: Given a row and column of a key that has been debounced and
// continues to be pressed:
// 1) Determine if this is the last key pressed.
// 2) If it's the most recent key, then check to see if it's time to repeat the key code.
// 3) If it's time to repeat, then do the repeat and reset the repeat timer.
//
// SIDE EFFECTS:
// - Causes repeat timer to tick, and may reset the repeat timer.
// - activate_key() will send a key code.
//
// NOTES: last_key is always a key code, never an action function, so only valid
// key codes will be repeated.
//
// SCOPE: private
//
// COMPLEXITY: 3
//
static void asdf_handle_key_held_pressed(uint8_t row, uint8_t col)
{
  if (asdf_lookup_keycode(row, col) == last_key) {

    // if last debounced code-producing key is still pressed, handle repeat
    if (asdf_repeat()) {
      asdf_activate_key(last_key);
    }
  }
}

// PROCEDURE: asdf_keyscan
// INPUTS: none
// OUTPUTS: none
//
// DESCRIPTION: Scans the key matrix. For each row, read the columns and compare
// with last stable state. For each changed key, call a key-change handler
// function. For each stable pressed key, call a "continued press" handler
// function.
//
// SIDE EFFECTS:
//
// NOTES: 1) The keyboard state is stored as an array of words, with one word
//           per row, and each bit in a word representing one key in the row.
//
//        2) While it is tempting to use bit hacks to reduce the number of
//           cycles through the inner loop, I have opted to just loop over all
//           the bits (as long as there are some changed or pressed keys). For
//           an 8-bit word, bit tricks don't deliver much performance boost,
//           but do decrease code clarity.
//
// COMPLEXITY: 5
//
void asdf_keyscan(void)
{
  asdf_cols_t (*row_reader)(uint8_t) = (asdf_cols_t(*)(uint8_t)) asdf_hook_get(ASDF_HOOK_SCANNER);

  asdf_hook_execute(ASDF_HOOK_EACH_SCAN);
  for (uint8_t row = 0; row < asdf_keymaps_num_rows(); row++) {
    asdf_cols_t row_key_state = (*row_reader)(row);

    asdf_cols_t changed = row_key_state ^ last_stable_key_state[row];

    // loop over the bits until all changed or pressed keys in the row are handled.
    for (uint8_t col = 0; /*(changed || row_key_state) && */col < asdf_keymaps_num_cols(); col++) {
      if (changed & 1) {
        // key state is different from last stable state
        asdf_handle_key_press_or_release(row, col, row_key_state & 1);
      }
      else if (row_key_state & 1) {
        asdf_handle_key_held_pressed(row, col);
      }
      changed >>= 1;
      row_key_state >>= 1;
    }
  }
}

// PROCEDURE: asdf_apply_all_actions
// INPUTS: none
// OUTPUTS: none
//
// DESCRIPTION: Scans the key matrix. For each row, read the columns, and for
// each bit, if an action is specified, then call the activate or deactivate
// function for that action, based on the key state. This is necessary to ensure
// that configuration settings, such as DIP switches and jumper settings, are
// properly handled when a keymap is changed.

// SIDE EFFECTS: See DESCRIPTION
//
// COMPLEXITY: 5
//
// SCOPE: private
//
void asdf_apply_all_actions(void)
{
  asdf_cols_t (*row_reader)(uint8_t) = (asdf_cols_t(*)(uint8_t)) asdf_hook_get(ASDF_HOOK_SCANNER);

  for (uint8_t row = 0; row < asdf_keymaps_num_rows(); row++) {
    asdf_cols_t row_key_state = (*row_reader)(row);

    // To avoid double actions, assign last stable key state to current state.
    last_stable_key_state[row] = row_key_state;

    // loop over the bits until all changed or pressed keys in the row are handled.
    for (uint8_t col = 0; col < asdf_keymaps_num_cols(); col++) {
      asdf_keycode_t code = asdf_lookup_keycode(row, col);
      if (row_key_state & 1) {
        asdf_activate_action(code);
      } else {
        asdf_deactivate_action(code);
      }
      row_key_state >>= 1;
    }
  }
}


// PROCEDURE: asdf_init
// INPUTS: none
// OUTPUTS: none
//
// DESCRIPTION: Reserve an output buffer to hold keycodes to be sent, and
// initialize the keyboard state and debounce counters
//
// SIDE EFFECTS: see DESCRIPTION
//
// SCOPE: public
//
// COMPLEXITY: 3
//
void asdf_init(void)
{

  last_key = ACTION_NOTHING;

  asdf_buffer_init();  // initialize the buffers
  asdf_repeat_init();  // initialize the repeat counters
  asdf_keymaps_init(); // initialize keymaps. This also initializes the modifier
                       // key states.
  // reserve a buffer for the ASCII output:
  asdf_keycode_buffer = asdf_buffer_new(ASDF_KEYCODE_BUFFER_SIZE);
  asdf_message_buffer = asdf_buffer_new(ASDF_MESSAGE_BUFFER_SIZE);

  // Initialize all the keys to the unpressed state, and initialze the debounce
  // counters.
  for (uint8_t row = 0; row < ASDF_MAX_ROWS; row++) {
    last_stable_key_state[row] = 0;
    for (uint8_t col = 0; col < ASDF_MAX_COLS; col++) {
      debounce_counters[row][col] = ASDF_DEBOUNCE_TIME_MS;
    }
  }
}


//-------|---------|---------+---------+---------+---------+---------+---------+
// Above line is 80 columns, and should display completely in the editor.
