/* ----------------------------------------------------------------------------
 * key functions : basic : code
 * ----------------------------------------------------------------------------
 * Copyright (c) 2012 Ben Blazak <benblazak.dev@gmail.com>
 * Released under The MIT License (MIT) (see "license.md")
 * Project located at <https://github.com/benblazak/ergodox-firmware>
 * ------------------------------------------------------------------------- */


#include "../../../main.h"
#include "../../../keyboard/layout.h"
#include "../public.h"
#include "../private.h"

/*
 * Generate a normal keypress or keyrelease
 */
void kbfun_press_release() {
  if (!main_arg_trans_key_pressed) {
    main_arg_any_non_trans_key_pressed = true;
  }
  kbfun_press_release_preserve_sticky();
}

/*
 * Generate a normal keypress or keyrelease
 * While basing the sticky key state transition on whether
 *  kbfun_press_release() was called after kbfun_transparent() generally
 *  works in practice, it is not always the desired behavior. One of the
 *  benefits of sticky keys is avoiding key chording, so we want to make sure
 *  that standard modifiers do not interrupt the sticky key cycle. Use
 *  kbfun_press_release_preserve_sticky() if you want to define a standard
 *  modifier key (shift, control, alt, gui) on the sticky layer instead of
 *  defining the key to be transparent for the layer.
 */
void kbfun_press_release_preserve_sticky() {
  uint8_t keycode = _kbfun_get_keycode();
  _kbfun_press_release(main_arg_is_pressed, keycode);
}

/*
 * Toggle the key pressed or unpressed
 */
void kbfun_toggle(void) {
  uint8_t keycode	= _kbfun_get_keycode();
  bool is_pressed	= _kbfun_is_pressed(keycode);
  _kbfun_press_release(!is_pressed, keycode);
}

/*
 * Execute the key that would have been executed if the current layer was not
 * active
 */
void kbfun_transparent(void) {
  // TODO maybe re-implement this cleaner?
  main_arg_trans_key_pressed = true;
  main_arg_layer_offset++;
  main_arg_layer = main_layers_peek(main_arg_layer_offset);
  main_layers_pressed[main_arg_row][main_arg_col] = main_arg_layer;
  main_exec_key();
}


/* ----------------------------------------------------------------------------
 * layer functions
 * ------------------------------------------------------------------------- */

// all lower layers on a key should be secretly set/unset to enable proper layer stacking
static void layers_enable_downwards(uint8_t layer) {
  // TODO
  main_layers_enable(layer, eStickyNone);
}

static void layers_disable_downwards(uint8_t layer) {
  // TODO
  main_layers_disable(layer);
}

// enable given layer
static void layer_enable(uint8_t layer) {
  // Only the topmost layer on the stack should be in sticky once state, pop
  // the top layer if it is in sticky once state
  uint8_t topSticky = main_layers_top_sticky();
  if (topSticky == eStickyOnceDown || topSticky == eStickyOnceUp) {
    main_layers_disable_top();
  }

  layers_enable_downwards(layer);
}

// disable given layer
static void layer_disable(uint8_t layer) {
  layers_disable_downwards(layer);
}

/*
 * This function gives similar behavior to sticky keys for modifiers available
 *  on most operating systems. It is considered an accessibility feature
 *  because it alleviates the user from having to hold down modifiers while
 *  pressing a key to produce the modified key function. It is useful for fast
 *  touch typing because you can avoid chording motions which both strain your
 *  hands and take your hands out of home-row position while pressing normal
 *  alpha keys.
 * This function emulates the 3-state behavior which is default on OS X and
 *  optional in Windows where the modifier cycles between Off->Once->Locked
 *  states. This is particularly handy for symbol layers where you typically
 *  only type one symbol before you want to return to unmodified typing (layer
 *  0), e.g. 'if (condition) { a = "b" + "c"; }'. If you assign a symbol layer
 *  to a thumb key as a layer sticky cycle, you can type the entire line of
 *  code without taking your hands out of home row position and you do not need
 *  to toggle off the layer after each symbol is pressed, only immediately
 *  before keying the symbol.
 * The exact behavior of the layer sticky cycle function is defined as follows
 *  for each state:
 * 1) One time down (set on key press) - The layer was not active and the key
 *     has been pressed but not yet released. The layer is pushed in the one
 *     time down state.
 * 2) One time up (set on key release) - The layer was active when the layer
 *     sticky key was released. If a key on this layer (not set to
 *     transparent) was pressed before the key was released, the layer will be
 *     popped. If a non-transparent key was not pressed, the layer is popped
 *     and pushed again in the one time up state.
 * 3) Locked (set on key press) - The layer was active and in the one time up
 *     state when the layer sticky key was pressed again. The layer will be
 *     popped if the function is invoked on a subsequent keypress.
 */
static void layer_sticky(uint8_t layer) {
  uint8_t topLayer 	= main_layers_top_layer();
  uint8_t topSticky	= main_layers_top_sticky();

  if (main_arg_is_pressed) {
    if (topLayer == layer) {
      if (topSticky == eStickyOnceUp) {
        main_layers_enable(layer, eStickyLock);
      }
    } else {
      // only the topmost layer on the stack should be in sticky once state
      if (topSticky == eStickyOnceDown || topSticky == eStickyOnceUp) {
        main_layers_disable_top();
      }
      main_layers_enable(layer, eStickyOnceDown);

      // this should be the only place we care about this flag being cleared
      main_arg_any_non_trans_key_pressed = false;
    }
  } else {
    if (topLayer == layer) {
      if (topSticky == eStickyOnceDown) {
        // When releasing this sticky key, pop the layer always
        main_layers_disable(layer);
        if (!main_arg_any_non_trans_key_pressed) {
          // If no key defined for this layer (a non-transparent key)
          //  was pressed, push the layer again, but in the
          //  StickyOnceUp state
          main_layers_enable(layer, eStickyOnceUp);
        }
      }
    }
  }
}

// functions for all layers

#define define_layer(n)                                        \
  void kbfun_layer_enable_##n 	(void)	{ layer_enable(n); 	} \
  void kbfun_layer_sticky_##n 	(void)	{ layer_sticky(n); 	} \
  void kbfun_layer_disable_##n	(void)	{ layer_disable(n);	}

define_layer(1);
define_layer(2);
define_layer(3);
define_layer(4);
define_layer(5);
define_layer(6);
define_layer(7);
define_layer(8);
define_layer(9);
define_layer(10);
