/* ----------------------------------------------------------------------------
 * key functions : private : 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 "./keyfunctions.h"

/*
 * MediaCodeLookupTable is used to translate from enumeration in keyboard.h to
 *  consumer key scan code in usb_keyboard.h
 */
static const uint16_t _media_code_lookup_table[] = {
  AUDIO_MUTE,          	// MEDIAKEY_AUDIO_MUTE
  AUDIO_VOL_UP,        	// MEDIAKEY_AUDIO_VOL_UP
  AUDIO_VOL_DOWN,      	// MEDIAKEY_AUDIO_VOL_DOWN
  TRANSPORT_NEXT_TRACK,	// MEDIAKEY_NEXT_TRACK
  TRANSPORT_PREV_TRACK,	// MEDIAKEY_PREV_TRACK
  TRANSPORT_STOP,      	// MEDIAKEY_STOP
  TRANSPORT_PLAY_PAUSE,	// MEDIAKEY_PLAY_PAUSE
  TRANSPORT_RECORD,    	// MEDIAKEY_RECORD
  TRANSPORT_REWIND,    	// MEDIAKEY_REWIND
  TRANSPORT_EJECT,     	// MEDIAKEY_EJECT
  AL_CC_CONFIG,        	// MEDIAKEY_CC_CONFIG
  AL_EMAIL,            	// MEDIAKEY_EMAIL
  AL_CALCULATOR,       	// MEDIAKEY_CALCULATOR
  AL_LOCAL_BROWSER,    	// MEDIAKEY_LOCAL_BROWSER
  AC_SEARCH,           	// MEDIAKEY_BROWSER_SEARCH
  AC_HOME,             	// MEDIAKEY_BROWSER_HOME
  AC_BACK,             	// MEDIAKEY_BROWSER_BACK
  AC_FORWARD,          	// MEDIAKEY_BROWSER_FORWARD
  AC_STOP,             	// MEDIAKEY_BROWSER_STOP
  AC_REFRESH,          	// MEDIAKEY_BROWSER_REFRESH
  AC_BOOKMARKS,        	// MEDIAKEY_BROWSER_BOOKMARKS
};

// ----------------------------------------------------------------------------

/*
 * Generate a normal keypress or keyrelease
 *
 * Arguments
 * - press: whether to generate a keypress (true) or keyrelease (false)
 * - keycode: the keycode to use
 *
 * Note
 * - Because of the way USB does things, what this actually does is either add
 *   or remove 'keycode' from the list of currently pressed keys, to be sent at
 *   the end of the current cycle (see main.c)
 */
void _kbfun_press_release(bool press, uint8_t keycode) {
// no-op
  if (keycode == 0) {
    return;
  }

// modifier keys
  switch (keycode) {
  case KEY_LeftControl: 	(press)	? (keyboard_modifier_keys |= (1<<0))	: (keyboard_modifier_keys &= ~(1<<0));	return;
  case KEY_LeftShift:   	(press)	? (keyboard_modifier_keys |= (1<<1))	: (keyboard_modifier_keys &= ~(1<<1));	return;
  case KEY_LeftAlt:     	(press)	? (keyboard_modifier_keys |= (1<<2))	: (keyboard_modifier_keys &= ~(1<<2));	return;
  case KEY_LeftGUI:     	(press)	? (keyboard_modifier_keys |= (1<<3))	: (keyboard_modifier_keys &= ~(1<<3));	return;
  case KEY_RightControl:	(press)	? (keyboard_modifier_keys |= (1<<4))	: (keyboard_modifier_keys &= ~(1<<4));	return;
  case KEY_RightShift:  	(press)	? (keyboard_modifier_keys |= (1<<5))	: (keyboard_modifier_keys &= ~(1<<5));	return;
  case KEY_RightAlt:    	(press)	? (keyboard_modifier_keys |= (1<<6))	: (keyboard_modifier_keys &= ~(1<<6));	return;
  case KEY_RightGUI:    	(press)	? (keyboard_modifier_keys |= (1<<7))	: (keyboard_modifier_keys &= ~(1<<7));	return;
  }

// all others
  for (uint8_t i=0; i<6; i++) {
    if (press) {
      if (keyboard_keys[i] == 0) {
        keyboard_keys[i] = keycode;
        return;
      }
    } else {
      if (keyboard_keys[i] == keycode) {
        keyboard_keys[i] = 0;
        return;
      }
    }
  }
}

/*
 * Is the given keycode pressed?
 */
bool _kbfun_is_pressed(uint8_t keycode) {
// modifier keys
  switch (keycode) {
  case KEY_LeftControl: 	if (keyboard_modifier_keys & (1<<0))	{ return true; }
  case KEY_LeftShift:   	if (keyboard_modifier_keys & (1<<1))	{ return true; }
  case KEY_LeftAlt:     	if (keyboard_modifier_keys & (1<<2))	{ return true; }
  case KEY_LeftGUI:     	if (keyboard_modifier_keys & (1<<3))	{ return true; }
  case KEY_RightControl:	if (keyboard_modifier_keys & (1<<4))	{ return true; }
  case KEY_RightShift:  	if (keyboard_modifier_keys & (1<<5))	{ return true; }
  case KEY_RightAlt:    	if (keyboard_modifier_keys & (1<<6))	{ return true; }
  case KEY_RightGUI:    	if (keyboard_modifier_keys & (1<<7))	{ return true; }
  }

// all others
  for (uint8_t i=0; i<6; i++)
    if (keyboard_keys[i] == keycode) {
      return true;
    }

  return false;
}

void _kbfun_mediakey_press_release(bool press, uint8_t keycode) {
  uint16_t mediakey_code = _media_code_lookup_table[keycode];
  if (press) {
    consumer_key = mediakey_code;
  } else {
// Only one key can be pressed at a time so only clear the keypress for
//  active key (most recently pressed)
    if (mediakey_code == consumer_key) {
      consumer_key = 0;
    }
  }
}

uint8_t _kbfun_get_keycode() {
  return kb_layout_get(main_arg_layer, main_arg_row, main_arg_col);
}

// ----------------------------------------------------------------------------
// basic
// ----------------------------------------------------------------------------

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 helper functions
// ----------------------------------------------------------------------------

static bool is_layer_enable(void_funptr_t f) {
  if (f == &kbfun_layer_enable || f == &kbfun_layer_sticky) {
    return true;
  }
  return false;
}

static bool is_layer_disable(void_funptr_t f) {
  if (f == &kbfun_layer_disable || f == &kbfun_layer_sticky) {
    return true;
  }
  return false;
}

static void layer_enable_upto(uint8_t max_layer) {
  // FIXME clean this up

  // pressing a key implicitly activates all lower layers as well
  for (uint8_t layer=0; layer <= KB_LAYERS; layer++) {
    void (*key_function)(void) = kb_layout_press_get(layer, main_arg_row, main_arg_col);

    if (is_layer_enable(key_function)) {
      uint8_t enable_layer = kb_layout_get(layer, main_arg_row, main_arg_col);
      if (enable_layer <= max_layer) {
        main_layers_enable(enable_layer, eStickyNone);
      }
    }
  }
}

static void layer_disable_all() {
  // FIXME clean this up

  // letting go off a key releases *all* layers on that key
  for (uint8_t layer=0; layer <= KB_LAYERS; layer++) {
    void (*key_function)(void) = kb_layout_release_get(layer, main_arg_row, main_arg_col);

    if (is_layer_disable(key_function)) {
      uint8_t disable_layer = kb_layout_get(layer, main_arg_row, main_arg_col);
      main_layers_disable(disable_layer);
    }
  }
}

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

// enable given layer
void kbfun_layer_enable() {
  uint8_t layer = _kbfun_get_keycode();

  // FIXME useful for anything?
  // 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(); */
  /* } */

  layer_enable_upto(layer);
}

// disable given layer
void kbfun_layer_disable() {
  /* uint8_t layer = _kbfun_get_keycode(); */
  layer_disable_all();
}

/*
 * This function gives similar behavior to sticky keys for modifiers available
 *  on most operating systems.
 * 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.
 */
void kbfun_layer_sticky() {
  uint8_t layer    	= _kbfun_get_keycode();
  uint8_t topLayer 	= main_layers_top_layer();
  uint8_t topSticky	= main_layers_top_sticky();

  if (main_arg_is_pressed) {
    if (topLayer == layer) {
      // FIXME
      /* 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 (main_layers_sticky(layer) == 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);
      }
    }
  }
}

// ----------------------------------------------------------------------------
// device
// ----------------------------------------------------------------------------

/*
 * [name]
 *   Jump to Bootloader
 *
 * [description]
 *   For reflashing the controller
 */

// from PJRC (slightly modified)
// <http://www.pjrc.com/teensy/jump_to_bootloader.html>
void kbfun_jump_to_bootloader(void) {
	// --- for all Teensy boards ---

	cli();

	// disable watchdog, if enabled
	// disable all peripherals
	UDCON = 1;
	USBCON = (1<<FRZCLK);  // disable USB
	UCSR1B = 0;
	_delay_ms(5);

	// --- Teensy 2.0 specific ---

	EIMSK = 0; PCICR = 0; SPCR = 0; ACSR = 0; EECR = 0; ADCSRA = 0;
	TIMSK0 = 0; TIMSK1 = 0; TIMSK3 = 0; TIMSK4 = 0; UCSR1B = 0; TWCR = 0;
	DDRB = 0; DDRC = 0; DDRD = 0; DDRE = 0; DDRF = 0; TWCR = 0;
	PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; PORTF = 0;
	asm volatile("jmp 0x7E00");
}

// ----------------------------------------------------------------------------
// special
// ----------------------------------------------------------------------------

/*
 * Generate a 'shift' press or release before the normal keypress or release
 */
void kbfun_shift_press_release(void) {
  _kbfun_press_release(main_arg_is_pressed, KEY_LeftShift);
  kbfun_press_release();
}

/*
 * Generate a 'control' press or release before the normal keypress or release
 */
void kbfun_control_press_release(void) {
  _kbfun_press_release(main_arg_is_pressed, KEY_LeftControl);
  kbfun_press_release();
}

/*
 * When assigned to two keys (e.g. the physical left and right shift keys)
 * (in both the press and release matrices), pressing and holding down one of
 * the keys will make the second key toggle capslock
 *
 * If either of the shifts are pressed when the second key is pressed, they
 * wil be released so that capslock will register properly when pressed.
 * Capslock will then be pressed and released, and the original state of the
 * shifts will be restored
 */
void kbfun_2_keys_capslock_press_release(void) {
  static uint8_t keys_pressed;
  static bool lshift_pressed;
  static bool rshift_pressed;

  uint8_t keycode = _kbfun_get_keycode();

  if (!main_arg_is_pressed) { keys_pressed--; }

// take care of the key that was actually pressed
  _kbfun_press_release(main_arg_is_pressed, keycode);

// take care of capslock (only on the press of the 2nd key)
  if (keys_pressed == 1 && main_arg_is_pressed) {
// save the state of left and right shift
    lshift_pressed = _kbfun_is_pressed(KEY_LeftShift);
    rshift_pressed = _kbfun_is_pressed(KEY_RightShift);
// disable both
    _kbfun_press_release(false, KEY_LeftShift);
    _kbfun_press_release(false, KEY_RightShift);

// press capslock, then release it
    _kbfun_press_release(true, KEY_CapsLock); 	usb_keyboard_send();
    _kbfun_press_release(false, KEY_CapsLock);	usb_keyboard_send();

// restore the state of left and right shift
    if (lshift_pressed)	{ _kbfun_press_release(true, KEY_LeftShift); 	}
    if (rshift_pressed)	{ _kbfun_press_release(true, KEY_RightShift);	}
  }

  if (main_arg_is_pressed) { keys_pressed++; }
}

/*
 * Generate a keypress for a media key
 */
void kbfun_mediakey_press_release(void) {
  uint8_t keycode = _kbfun_get_keycode();
  _kbfun_mediakey_press_release(main_arg_is_pressed, keycode);
}
