/* Kaleidoscope - Firmware for computer input devices
 * Copyright (C) 2013-2018  Keyboard.io, Inc.
 *
 * 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, version 3.
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "kaleidoscope/Kaleidoscope.h"

// The maximum number of layers allowed. `layer_state_`, which stores
// the on/off status of the layers in a bitfield has only 32 bits, and
// that should be enough for almost any layout.
#define MAX_LAYERS sizeof(uint32_t) * 8;

// The total number of defined layers in the firmware sketch keymaps[]
// array. If the keymap wasn't defined using KEYMAPS(), set it to the
// highest possible number of layers.
uint8_t layer_count __attribute__((weak)) = MAX_LAYERS;

namespace kaleidoscope {
uint32_t Layer_::layer_state_;
uint8_t Layer_::top_active_layer_;
Key Layer_::live_composite_keymap_[ROWS][COLS];
uint8_t Layer_::active_layers_[ROWS][COLS];
Key(*Layer_::getKey)(uint8_t layer, byte row, byte col) = Layer.getKeyFromPROGMEM;

void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) {
  if (keymapEntry.keyCode >= LAYER_SHIFT_OFFSET) {
    uint8_t target = keymapEntry.keyCode - LAYER_SHIFT_OFFSET;

    switch (target) {
    case KEYMAP_NEXT:
      if (keyToggledOn(keyState))
        activateNext();
      else if (keyToggledOff(keyState))
        deactivateTop();
      break;

    case KEYMAP_PREVIOUS:
      if (keyToggledOn(keyState))
        deactivateTop();
      else if (keyToggledOff(keyState))
        activateNext();
      break;

    default:
      /* The default case is when we are switching to a layer by its number, and
       * is a bit more complicated than switching there when the key toggles on,
       * and away when it toggles off.
       *
       * We want to handle the case where we have more than one momentary layer
       * key on our keymap that point to the same target layer, and we hold
       * both, and release one. In this case, the layer should remain active,
       * because the second momentary key is still held.
       *
       * To do this, we turn the layer back on if the switcher key is still
       * held, not only when it toggles on. So when one of them is released,
       * that does turn the layer off, but with the other still being held, the
       * layer will toggle back on in the same cycle.
       */
      if (keyIsPressed(keyState)) {
        if (!Layer.isActive(target))
          activate(target);
      } else if (keyToggledOff(keyState)) {
        deactivate(target);
      }
      break;
    }
  } else if (keyToggledOn(keyState)) {
    // switch keymap and stay there
    if (Layer.isActive(keymapEntry.keyCode) && keymapEntry.keyCode)
      deactivate(keymapEntry.keyCode);
    else
      activate(keymapEntry.keyCode);
  }
}

Key Layer_::eventHandler(Key mappedKey, byte row, byte col, uint8_t keyState) {
  if (mappedKey.flags != (SYNTHETIC | SWITCH_TO_KEYMAP))
    return mappedKey;

  handleKeymapKeyswitchEvent(mappedKey, keyState);
  return Key_NoKey;
}

Key Layer_::getKeyFromPROGMEM(uint8_t layer, byte row, byte col) {
  Key key;

  key.raw = pgm_read_word(&(keymaps[layer][row][col]));

  return key;
}

void Layer_::updateLiveCompositeKeymap(byte row, byte col) {
  int8_t layer = active_layers_[row][col];
  live_composite_keymap_[row][col] = (*getKey)(layer, row, col);
}

void Layer_::updateActiveLayers(void) {
  memset(active_layers_, 0, ROWS * COLS);
  for (byte row = 0; row < ROWS; row++) {
    for (byte col = 0; col < COLS; col++) {
      int8_t layer = top_active_layer_;

      while (layer > 0) {
        if (Layer.isActive(layer)) {
          Key mappedKey = (*getKey)(layer, row, col);

          if (mappedKey != Key_Transparent) {
            active_layers_[row][col] = layer;
            break;
          }
        }
        layer--;
      }
    }
  }
}

void Layer_::updateTopActiveLayer(void) {
  // If layer_count is set, start there, otherwise search from the
  // highest possible layer (MAX_LAYERS) for the top active layer
  for (byte i = (layer_count - 1); i > 0; i--) {
    if (bitRead(layer_state_, i)) {
      top_active_layer_ = i;
      return;
    }
  }
  // It's not possible to turn off the default layer (see
  // updateActiveLayers()), so if no other layers are active:
  top_active_layer_ = 0;
}

void Layer_::move(uint8_t layer) {
  layer_state_ = 0;
  activate(layer);
}

// Activate a given layer
void Layer_::activate(uint8_t layer) {
  // If we're trying to turn on a layer that doesn't exist, abort (but
  // if the keymap wasn't defined using the KEYMAPS() macro, proceed anyway
  if (layer >= layer_count)
    return;

  // If the target layer was already on, return
  if (isActive(layer))
    return;

  // Otherwise, turn on its bit in layer_state_
  bitSet(layer_state_, layer);

  // If the target layer is above the previous highest active layer,
  // update top_active_layer_
  if (layer > top_active_layer_)
    updateTopActiveLayer();

  // Update the keymap cache (but not live_composite_keymap_; that gets
  // updated separately, when keys toggle on or off. See layers.h)
  updateActiveLayers();

  kaleidoscope::Hooks::onLayerChange();
}

// Deactivate a given layer
void Layer_::deactivate(uint8_t layer) {
  // If the target layer was already off, return
  if (!bitRead(layer_state_, layer))
    return;

  // Turn off its bit in layer_state_
  bitClear(layer_state_, layer);

  // If the target layer was the previous highest active layer,
  // update top_active_layer_
  if (layer == top_active_layer_)
    updateTopActiveLayer();

  // Update the keymap cache (but not live_composite_keymap_; that gets
  // updated separately, when keys toggle on or off. See layers.h)
  updateActiveLayers();

  kaleidoscope::Hooks::onLayerChange();
}

boolean Layer_::isActive(uint8_t layer) {
  return bitRead(layer_state_, layer);
}

void Layer_::activateNext(void) {
  activate(top_active_layer_ + 1);
}

void Layer_::deactivateTop(void) {
  deactivate(top_active_layer_);
}

}

kaleidoscope::Layer_ Layer;