/* ----------------------------------------------------------------------------
 * 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 "./main.h"
#include "./keyboard/layout_gen.h"

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

static bool _main_kb_is_pressed[KB_ROWS][KB_COLUMNS];
bool (*main_kb_is_pressed)[KB_ROWS][KB_COLUMNS] = &_main_kb_is_pressed;

static bool _main_kb_was_pressed[KB_ROWS][KB_COLUMNS];
bool (*main_kb_was_pressed)[KB_ROWS][KB_COLUMNS] = &_main_kb_was_pressed;

static bool main_kb_was_transparent[KB_ROWS][KB_COLUMNS];

uint8_t main_layers_pressed[KB_ROWS][KB_COLUMNS];

uint8_t main_arg_layer;
uint8_t main_arg_layer_offset;
uint8_t main_arg_row;
uint8_t main_arg_col;
bool    main_arg_is_pressed;
bool    main_arg_was_pressed;
bool    main_arg_any_non_trans_key_pressed;
bool    main_arg_trans_key_pressed;

// layer data
struct layer {
  bool   	active;
  uint8_t	sticky;
};

struct layer	layers[KB_LAYERS];
uint8_t     	layers_top = 0;


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

/*
 * main()
 */
int main(void) {
  kb_init(); // does controller initialization too

  usb_init();
  while (!usb_configured());

  // initialize layers
  for (uint8_t layer=0; layer < KB_LAYERS; layer++) {
    layers[layer].active = false;
    layers[layer].sticky = eStickyNone;
  }
  layers[0].active = true;

  for (;;) {
    // swap `main_kb_is_pressed` and `main_kb_was_pressed`, then update
    bool (*temp)[KB_ROWS][KB_COLUMNS]	= main_kb_was_pressed;
    main_kb_was_pressed              	= main_kb_is_pressed;
    main_kb_is_pressed               	= temp;

    kb_update_matrix(*main_kb_is_pressed);

    // this loop is responsible to
    // - "execute" keys when they change 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)
    //
    // note
    // - everything else is the key function's responsibility
    //   - see the keyboard layout file ("keyboard/layout/*.c") for
    //     which key is assigned which function (per layer)
    //   - see "lib/key-functions/public/*.c" for the function definitions
    for (uint8_t row=0; row<KB_ROWS; row++) {
      for (uint8_t col=0; col<KB_COLUMNS; col++) {
        main_arg_is_pressed 	= (*main_kb_is_pressed)[row][col];
        main_arg_was_pressed	= (*main_kb_was_pressed)[row][col];

        if (main_arg_is_pressed != main_arg_was_pressed) {
          if (main_arg_is_pressed) {
            main_arg_layer               	= main_layers_top_layer();
            main_layers_pressed[row][col]	= main_arg_layer;
            main_arg_trans_key_pressed   	= false;
          } else {
            main_arg_layer            	= main_layers_pressed[row][col];
            main_arg_trans_key_pressed	= main_kb_was_transparent[row][col];
          }

          // set remaining vars, and "execute" key
          main_arg_row         	= row;
          main_arg_col         	= col;
          main_arg_layer_offset	= 0;
          main_exec_key();
          main_kb_was_transparent[row][col] = main_arg_trans_key_pressed;
        }
      }
    }

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

  return 0;
}


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


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

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

// return the highest active layer
uint8_t main_layers_top_layer() {
  return layers_top;
}

// return if highest active layer is sticky
uint8_t main_layers_top_sticky() {
  return main_layers_sticky(layers_top);
}

// return if layer is sticky
uint8_t main_layers_sticky(uint8_t layer) {
  if (layer < KB_LAYERS) {
    return layers[layer].sticky;
  }
}

// enable a layer
void main_layers_enable(uint8_t layer, uint8_t sticky) {
  if (layer >= KB_LAYERS) { return; }

  layers[layer].active = true;
  layers[layer].sticky = sticky;

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

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

  layers[layer].active = false;
  layers[layer].sticky = eStickyNone;

  if (layer == layers_top) {
    layers_top = _highest_active_layer(1);
  }
}

// disable the highest active layer
void main_layers_disable_top() {
  main_layers_disable(layers_top);
}

// return layer offset elements below the top
uint8_t main_layers_peek(uint8_t offset) {
  return _highest_active_layer(offset);
}

// execute the keypress or keyrelease function (if it exists) of the key at the current possition
void main_exec_key(void) {
  void (*key_function)(void) =
    ( (main_arg_is_pressed)
      ? kb_layout_press_get(main_arg_layer, main_arg_row, main_arg_col)
      : kb_layout_release_get(main_arg_layer, main_arg_row, main_arg_col) );

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

  // 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 (main_layers_top_sticky() == eStickyOnceUp && main_arg_any_non_trans_key_pressed) {
    main_layers_disable_top();
  }
}
