/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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; either version 2 of the License, or (at your option)
 * any later version.
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.swing.Action;
import javax.swing.KeyStroke;

import jnpad.action.JNPadAction;
import jnpad.config.Accelerators;

/**
 * The Class JNPadKeyboardHandler.
 *
 * @version 0.3
 * @since jNPad 0.1
 */
@SuppressWarnings("unchecked")
public class JNPadKeyboardHandler extends KeyAdapter {
  private Map bindings        = new HashMap();
  private Map currentBindings = new HashMap();

  /**
   * Adds the key binding.
   *
   * @param keyBinding the key binding
   * @param action the action
   */
  public void addKeyBinding(String keyBinding, Action action) {
    Map current = bindings;

    StringTokenizer st = new StringTokenizer(keyBinding);
    while (st.hasMoreTokens()) {
      KeyStroke keyStroke = Accelerators.parseKeyStroke(st.nextToken());
      if (keyStroke == null) {
        return;
      }

      if (st.hasMoreTokens()) {
        Object o = current.get(keyStroke);
        if (o instanceof Map) {
          current = (Map) o;
        }
        else {
          o = new HashMap();
          current.put(keyStroke, o);
          current = (Map) o;
        }
      }
      else {
        current.put(keyStroke, action);
      }
    }

    currentBindings = bindings;
  }

  /**
   * Key pressed.
   * 
   * Se le llama después de que el usario pulse una tecla mientras el componente
   * escuchado tiene el foco.
   * 
   * @param e KeyEvent
   * @see java.awt.event.KeyAdapter#keyPressed(java.awt.event.KeyEvent)
   */
  @Override
  public void keyPressed(KeyEvent e) {
    //System.out.println("-- keyPressed --");

    int keyCode = e.getKeyCode();
    int modifiers = e.getModifiers();

    if (keyCode == KeyEvent.VK_CONTROL ||
        keyCode == KeyEvent.VK_SHIFT ||
        keyCode == KeyEvent.VK_ALT ||
        keyCode == KeyEvent.VK_META) {
      return;
    }

    if (e.isShiftDown()) {
      KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
      Object o = currentBindings.get(keyStroke);

      if (o == null) {
        // Don't beep if the user presses some
        // key we don't know about unless a
        // prefix is active. Otherwise it will
        // beep when caps lock is pressed, etc.
        if (currentBindings != bindings) {
          Toolkit.getDefaultToolkit().beep();
          e.consume();
        }
        currentBindings = bindings;
      }
      else if (o instanceof Action) {
        currentBindings = bindings;

        executeAction(((Action) o), e.getSource(), null);

        e.consume();
      }
      else if (o instanceof Map) {
        currentBindings = (Map) o;
        e.consume();
      }
    }
    else if (!e.isShiftDown()
             || e.isActionKey()
             || keyCode == KeyEvent.VK_BACK_SPACE
             || keyCode == KeyEvent.VK_DELETE
             || keyCode == KeyEvent.VK_ENTER
             || keyCode == KeyEvent.VK_TAB
             || keyCode == KeyEvent.VK_ESCAPE) {

      KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
      Object o = currentBindings.get(keyStroke);
      
      if (o == null) {
        // Don't beep if the user presses some
        // key we don't know about unless a
        // prefix is active. Otherwise it will
        // beep when caps lock is pressed, etc.
        if (currentBindings != bindings) {
          Toolkit.getDefaultToolkit().beep();
          e.consume();
        }
        currentBindings = bindings;
      }
      else if (o instanceof Action) {
        currentBindings = bindings;
        executeAction(((Action) o), e.getSource(), null);
        e.consume();
      }
      else if (o instanceof Map) {
        currentBindings = (Map) o;
        e.consume();
      }
    }
  }

  /**
   * Key typed.
   * 
   * Se le llama después de que el usuario teclee un caracter Unicode dentro del
   * componente escuchado.
   * 
   * @param e KeyEvent
   * @see java.awt.event.KeyAdapter#keyTyped(java.awt.event.KeyEvent)
   */
  @Override
  public void keyTyped(KeyEvent e) {
    //System.out.println("-- keyTyped --");

    char c = e.getKeyChar();

    if (c != KeyEvent.CHAR_UNDEFINED && !e.isAltDown()) {
      if (c >= 0x20 && c != 0x7f) {
        KeyStroke keyStroke = KeyStroke.getKeyStroke(Character.toUpperCase(c));
        Object o = currentBindings.get(keyStroke);

        if (o == null) {
          // Don't beep if the user presses some
          // key we don't know about unless a
          // prefix is active. Otherwise it will
          // beep when caps lock is pressed, etc.
          if (currentBindings != bindings) {
            Toolkit.getDefaultToolkit().beep();
            e.consume();
          }
          currentBindings = bindings;
          return;
        }
        else if (o instanceof Map) {
          currentBindings = (Map) o;
          e.consume();
          return;
        }
        else if (o instanceof Action) {
          currentBindings = bindings;
          executeAction((Action) o, e.getSource(), String.valueOf(c));
          e.consume();
          return;
        }
        currentBindings = bindings;
      }
    }
  }

  /**
   * Key released.
   * 
   * Se le llama después de que el usario libere una tecla mientras el
   * componente escuchado tiene el foco.
   * 
   * @param e KeyEvent
   * @see java.awt.event.KeyAdapter#keyReleased(java.awt.event.KeyEvent)
   */
  @Override
  public void keyReleased(KeyEvent e) {
    //System.out.println("-- keyReleased --");
  }

  /**
   * Execute action.
   *
   * @param action Action
   * @param source Object
   * @param actionCommand String
   */
  private static void executeAction(Action action, Object source, String actionCommand) {
    if (action.isEnabled()) {
      if (action instanceof JNPadAction && ((JNPadAction) action).isStateAction()) {
        ((JNPadAction) action).setSelected(!((JNPadAction) action).isSelected());
      }
      else {
        ActionEvent e = new ActionEvent(source, ActionEvent.ACTION_PERFORMED, actionCommand);
        action.actionPerformed(e);
      }
    }
  }

}
