/* ----------------------------------------------------------------------------
 * main()
 * ----------------------------------------------------------------------------
 * 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 <stdbool.h>
#include <stdint.h>

// --------------------------------------------------------------------
// hardware
// --------------------------------------------------------------------

#define KBD_DEBUG // comment out to disable the debug interface completely
#include "./keyboard/controller.c"
#include "./keyboard/keyboard.h"

// --------------------------------------------------------------------
// types and forward declarations
// --------------------------------------------------------------------

typedef uint8_t u8;
typedef uint16_t u16;

typedef enum StickyState {
  StickyNone,
  StickyOnceDown,
  StickyOnceUp,
} StickyState;

typedef u8 	keycode;
typedef u16	media_keycode;
typedef u8 	layer;

typedef void (*keyfunc)(void);

#include "./main.h"

// ----------------------------------------------------------------------------
// layout data
// ----------------------------------------------------------------------------

#include "./keyboard/layout.c"
// defines:
// #define KB_Layers #{Layers.size}
// static const keycode	PROGMEM _kb_layout[KB_LAYERS][KB_ROWS][KB_COLUMNS];
// static const keyfunc	PROGMEM _kb_layout_press[KB_LAYERS][KB_ROWS][KB_COLUMNS];
// static const keyfunc	PROGMEM _kb_layout_release[KB_LAYERS][KB_ROWS][KB_COLUMNS];

// ----------------------------------------------------------------------------
// globals
// ----------------------------------------------------------------------------

static bool	_kb_is_pressed[KB_ROWS][KB_COLUMNS];
static bool	(*kb_is_pressed)[KB_ROWS][KB_COLUMNS] = &_kb_is_pressed;

static bool	_kb_was_pressed[KB_ROWS][KB_COLUMNS];
static bool	(*kb_was_pressed)[KB_ROWS][KB_COLUMNS] = &_kb_was_pressed;

static bool 	kb_was_transparent[KB_ROWS][KB_COLUMNS];
static layer	layers_pressed[KB_ROWS][KB_COLUMNS];

static layer	current_layer;
static layer	layer_offset;
static u8   	current_row;
static u8   	current_col;
static bool 	current_is_pressed;
static bool 	non_trans_key_pressed;
static bool 	trans_key_pressed;

static bool       	layers_active[KB_LAYERS];
static StickyState	layers_sticky[KB_LAYERS];
static layer      	layers_top = 0;

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

int main() {
  kb_init();
  usb_init();
  while (!usb_configured());

  // initialize layers
  init_layers();

  // never return
  main_key_loop();
  return 0;
}

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

void main_key_loop() {
  for (;;) {
    // swap `kb_is_pressed` and `kb_was_pressed`, then update
    bool (*temp)[KB_ROWS][KB_COLUMNS]	= kb_was_pressed;
    kb_was_pressed                   	= kb_is_pressed;
    kb_is_pressed                    	= temp;

    kb_update_matrix(*kb_is_pressed);

    // - execute key functions when their key changes state
    // - keep track of which layers the keys were on when they were pressed
    //   (so they can be released using the function from that layer)
    for (u8 row=0; row<KB_ROWS; row++) {
      for (u8 col=0; col<KB_COLUMNS; col++) {
        current_is_pressed	= (*kb_is_pressed)[row][col];
        bool was_pressed  	= (*kb_was_pressed)[row][col];

        if (current_is_pressed != was_pressed) {
          if (current_is_pressed) {
            current_layer           	= layers_top;
            layers_pressed[row][col]	= current_layer;
            trans_key_pressed       	= false;
          } else {
            current_layer    	= layers_pressed[row][col];
            trans_key_pressed	= kb_was_transparent[row][col];
          }

          // set remaining vars, and "execute" key
          current_row 	= row;
          current_col 	= col;
          layer_offset	= 0;
          exec_key();
          kb_was_transparent[row][col] = trans_key_pressed;
        }
      }
    }

    // send the USB report (even if nothing's changed)
    usb_keyboard_send();
    usb_extra_consumer_send();

    // debounce in ms; see keyswitch spec for necessary value
    _delay_ms(5);
  }
}

// execute the keypress or keyrelease function (if it exists) of the key at the current possition
void exec_key(void) {
  void (*key_function)(void) =
    ( (current_is_pressed)
      ? kb_keyfunc_press(current_layer, current_row, current_col)
      : kb_keyfunc_release(current_layer, current_row, current_col) );

  if (key_function) { (*key_function)(); }

  // FIXME
  // If the current layer is in the sticky once up state and a key defined
  //  for this layer (a non-transparent key) was pressed, pop the layer
  if (layer_top_sticky() == StickyOnceUp && non_trans_key_pressed) {
    layer_disable_top();
  }
}

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

void init_layers() {
  for (layer l=0; l < KB_LAYERS; l++) {
    layers_active[l] = false;
    layers_sticky[l] = StickyNone;
  }
  layers_active[0] = true;
}


// find highest active layer
layer highest_active_layer(layer offset) {
  if (offset < layers_top) {
    for (layer l = layers_top - offset; l > 0 && l < KB_LAYERS; l--) {
      if (layers_active[l]) { return l; }
    }
  }

  // the base layer is always active
  return 0;
}

// return if highest active layer is sticky
StickyState layer_top_sticky() {
  return layer_sticky(layers_top);
}

// return if layer is sticky
StickyState layer_sticky(layer l) {
  if (l < KB_LAYERS) {
    return layers_sticky[l];
  }
  return StickyNone;
}

// enable a layer
void layer_enable(layer l, StickyState sticky) {
  // FIXME split off sticky part
  if (l >= KB_LAYERS) { return; }

  layers_active[l] = true;
  layers_sticky[l] = sticky;

  if (l > layers_top) {
    layers_top = l;
  }
}

// disable a layer
void layer_disable(layer l) {
  // base layer stays always on
  if (l >= KB_LAYERS || l == 0) { return; }

  layers_active[l] = false;

  if (layers_sticky[l] != StickyNone) {
    debug_printf("sticky %d up!\n", l);
  }
  layers_sticky[l] = StickyNone;

  if (l == layers_top) {
    layers_top = highest_active_layer(1);
  }
}

// disable the highest active layer
void layer_disable_top() {
  layer_disable(layers_top);
}

// return layer offset elements below the top
layer layer_peek(layer offset) {
  return highest_active_layer(offset);
}

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

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

void layer_enable_upto(layer max_layer) {
  // FIXME clean this up

  // pressing a key implicitly activates all lower layers as well
  for (layer l=0; l <= KB_LAYERS; l++) {
    void (*key_function)(void) = kb_keyfunc_press(l, current_row, current_col);

    if (is_layer_enable(key_function)) {
      layer enable_layer = (layer) kb_keycode(l, current_row, current_col);
      if (enable_layer <= max_layer) {
        layer_enable(enable_layer, StickyNone);
      }
    }
  }
}

// ----------------------------------------------------------------------------
// layout info
// ----------------------------------------------------------------------------

keycode kb_keycode(layer l, u8 row, u8 col) {
  return (keycode) pgm_read_byte(&(_kb_layout[l][row][col]));
}

keyfunc kb_keyfunc_press(layer l, u8 row, u8 col) {
  return (keyfunc) pgm_read_word(&(_kb_layout_press[l][row][col]));
}

keyfunc kb_keyfunc_release(layer l, u8 row, u8 col) {
  return (keyfunc) pgm_read_word(&(_kb_layout_release[l][row][col]));
}

// ----------------------------------------------------------------------------
// keyfunc primitives
// ----------------------------------------------------------------------------

// basic keypresses
void _kbfun_press_release(bool press, keycode key) {
  // no-op
  if (key == 0) { return; }

  if (press) {
    _kbfun_press(key);
  } else {
    _kbfun_release(key);
  }
}

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

// all others
  for (u8 i=0; i < sizeof(keyboard_keys); i++) {
    if (keyboard_keys[i] == 0) {
      keyboard_keys[i] = key;
      return;
    }
  }
}

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

  // all others
  for (u8 i=0; i < sizeof(keyboard_keys); i++) {
    if (keyboard_keys[i] == key) {
      keyboard_keys[i] = 0;
      return;
    }
  }
}

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

  // all others
  for (u8 i=0; i < sizeof(keyboard_keys); i++) {
    if (keyboard_keys[i] == key) {
      return true;
    }
  }

  return false;
}

void _kbfun_mediakey_press_release(bool press, keycode key) {
  media_keycode media_key = _media_code_lookup_table[key];
  if (press) {
    consumer_key = media_key;
  } else {
  // only one media key can be pressed at a time, so only clear most recent one
    if (media_key == consumer_key) {
      consumer_key = 0;
    }
  }
}


// ----------------------------------------------------------------------------
// basic keyfuncs
// ----------------------------------------------------------------------------

keycode current_keycode()        	{ return kb_keycode(current_layer, current_row, current_col); }
keyfunc current_keyfunc_press()  	{ return kb_keyfunc_press(current_layer, current_row, current_col); }
keyfunc current_keycode_release()	{ return kb_keyfunc_release(current_layer, current_row, current_col); }

// normal key
void kbfun_press_release() {
  if (!trans_key_pressed) {
    non_trans_key_pressed = true;
  }
  _kbfun_press_release(current_is_pressed, current_keycode());
}

// media key
void kbfun_mediakey_press_release() {
  keycode key = current_keycode();
  _kbfun_mediakey_press_release(current_is_pressed, key);
}

// enable layer
void kbfun_layer_enable() {
  layer l = (layer) current_keycode();
  layer_enable_upto(l);
}

// disable layer
void kbfun_layer_disable() {
  // letting go off a key releases *all* layers on that key
  for (layer l=0; l <= KB_LAYERS; l++) {
    void (*key_function)(void) = kb_keyfunc_release(l, current_row, current_col);

    if (is_layer_disable(key_function)) {
      layer disable_layer = (layer) kb_keycode(l, current_row, current_col);
      layer_disable(disable_layer);
    }
  }
}

// sticky layer key
void kbfun_layer_sticky() {
  layer      	l        	= (layer) current_keycode();
  StickyState	top_sticky	= layer_top_sticky();

  if (current_is_pressed) {
    if (l != layers_top) {
      // only the topmost layer on the stack should be in sticky once state
      if (top_sticky == StickyOnceDown || top_sticky == StickyOnceUp) {
        layer_disable_top();
      }
      layer_enable(l, StickyOnceDown);
      debug_printf("sticky %d down!\n", l);

      // this should be the only place we care about this flag being cleared
      non_trans_key_pressed = false;
    }
  } else {
    if (layer_sticky(l) == StickyOnceDown) {
      // When releasing this sticky key, pop the layer always
      layer_disable(l);

      if (!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
        layer_enable(l, StickyOnceUp);
        debug_printf("sticky %d still down!\n", l);
      }
    }
  }
}

// ----------------------------------------------------------------------------
// combo keyfuncs
// ----------------------------------------------------------------------------

// +shift
void kbfun_shift_press_release() {
  _kbfun_press_release(current_is_pressed, KEY_LeftShift);
  kbfun_press_release();
}

// +control
void kbfun_control_press_release() {
  _kbfun_press_release(current_is_pressed, KEY_LeftControl);
  kbfun_press_release();
}

// +alt
void kbfun_alt_press_release() {
  _kbfun_press_release(current_is_pressed, KEY_LeftAlt);
  kbfun_press_release();
}

// +win
void kbfun_win_press_release() {
  _kbfun_press_release(current_is_pressed, KEY_LeftGUI);
  kbfun_press_release();
}

// capslock
void kbfun_2_keys_capslock_press_release() {
  static u8  	keys_pressed;
  static bool	lshift_pressed;
  static bool	rshift_pressed;

  keycode key = current_keycode();

  if (!current_is_pressed) { keys_pressed--; }

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

  // take care of capslock (only on the press of the 2nd key)
  if (keys_pressed == 1 && current_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 (current_is_pressed) { keys_pressed++; }
}
