/*
 * 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.action;

import static jnpad.action.JNPadActions.ACTION_NAME_EXECUTE_ACTION;

import java.awt.BorderLayout;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JTextField;

import jnpad.GUIUtilities;
import jnpad.JNPadFrame;
import jnpad.action.JNPadActions.Group;
import jnpad.config.Accelerators;
import jnpad.config.Config;
import jnpad.config.Configurable;
import jnpad.ui.EscapableDialog;
import jnpad.util.Utilities;

/**
 * The Class ExecuteActionAction.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class ExecuteActionAction extends JNPadAction implements Configurable {
  private ExecuteAction     executeAction;

  /** UID */
  private static final long serialVersionUID = -4565058844910246496L;

  /**
   * Instantiates a new execute action action.
   *
   * @param jNPad the jNPad's frame
   */
  public ExecuteActionAction(JNPadFrame jNPad) {
    super(jNPad,
          ACTION_NAME_EXECUTE_ACTION,
          Group.FILE,
          Accelerators.EXECUTE_ACTION, Utilities.EMPTY_STRING);
  }

  /**
   * Perform action.
   * 
   * @see jnpad.action.JNPadAction#performAction()
   */
  @Override
  public void performAction() {
    if(executeAction == null) {
      executeAction = new ExecuteAction(jNPad);
    }
    executeAction.setVisible(true);
  }

  /**
   * Configure.
   *
   * @param cfg int
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    if(executeAction != null) {
      executeAction.configure(cfg);
    }
  }
  
}

////////////////////////////////////////////////////////////////////////////////
/**
 * The Class ExecuteAction.
 */
class ExecuteAction extends EscapableDialog implements Configurable {
  JTextField                textField        = new JTextField();

  private List<String>      history;
  private int               index_history;

  private List<String>      completions;
  private int               index_completions;
  private JNPadFrame        jNPad;

  /** UID */
  private static final long serialVersionUID = -7122058605587960992L;

  /**
   * Instantiates a new execute action.
   *
   * @param jNPad JNPadFrame
   */
  ExecuteAction(JNPadFrame jNPad) {
    super(jNPad, true);

    this.jNPad = jNPad;

    setUndecorated(true);

    textField.setColumns(Config.PERFORM_ACTION_COLUMNS.getValue());
    textField.setFocusTraversalKeysEnabled(false);
    textField.addKeyListener(new KeyAdapter() {
      @Override public void keyPressed(final KeyEvent e) {
        handleKeyPressed(e);
      }
    });

    getContentPane().add(textField, BorderLayout.CENTER);
    
    configure(CFG_COLOR);

    pack();
    
    // location
    final double h = Config.PERFORM_ACTION_LOCATION_HORIZONTAL.getValue();
    final double v = Config.PERFORM_ACTION_LOCATION_VERTICAL.getValue();
    if ((Math.abs(h - 0.5) < .0000001 && Math.abs(v - 0.5) < .0000001) || h < 0 || v < 0) // Keep FindBugs happy if ((h == 0.5 && v == 0.5) || h < 0 || v < 0)
      setLocationRelativeTo(jNPad);
    else if (h >= 0 && h <= 1 && v >= 0 && v <= 1)
      GUIUtilities.setPositionFrameOnScreen(this, h, v);
    else
      setLocation((int) h, (int) v);

    // transparency
    final float transparency = Config.PERFORM_ACTION_TRANSPARENCY.getValue();
    if(transparency >= 0 && transparency < 1) {
      GUIUtilities.setWindowTransparency(this, transparency);
    }
  }

  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    textField.setFont(Config.TEXT_FONT.getValue());
    textField.setBackground(Config.TEXT_BACKGROUND.getValue());
    textField.setForeground(Config.TEXT_FOREGROUND.getValue());
    textField.setSelectionColor(Config.TEXT_SELECTION_BACKGROUND.getValue());
    textField.setSelectedTextColor(Config.TEXT_SELECTION_FOREGROUND.getValue());
    textField.setCaretColor(Config.TEXT_CARET_INS_COLOR.getValue());
  }

  /**
   * Sets the position frame on screen.
   *
   * @param horizontalPercent the horizontal percent
   * @param verticalPercent the vertical percent
   */
  private void setPositionFrameOnScreen(final double horizontalPercent, final double verticalPercent) {
    GUIUtilities.setPositionFrameOnScreen(this, horizontalPercent, verticalPercent);
    Config.PERFORM_ACTION_LOCATION_HORIZONTAL.setValue(horizontalPercent);
    Config.PERFORM_ACTION_LOCATION_VERTICAL.setValue(verticalPercent);
  }

  /**
   * Request focus.
   *
   * @see javax.swing.JComponent#requestFocus()
   */
  @Override
  public void requestFocus() {
    textField.requestFocus();
  }

  /**
   * Request focus in window.
   *
   * @return boolean
   * @see javax.swing.JComponent#requestFocusInWindow()
   */
  @Override
  public boolean requestFocusInWindow() {
    return textField.requestFocusInWindow();
  }
  
  /**
   *
   * @param e KeyEvent
   */
  void handleKeyPressed(final KeyEvent e) {
    final int keyCode = e.getKeyCode();
    final int modifiers = e.getModifiers();

    // ENTER
    if (keyCode == KeyEvent.VK_ENTER) {
      // Make sure user can see what he typed.
      textField.paintImmediately(0, 0, getWidth(), getHeight());
      e.consume();
      enter();
    }
    // TAB
    else if (keyCode == KeyEvent.VK_TAB && modifiers == 0) {
      e.consume();
      tab();
    }
    // VK_UP | VK_KP_UP
    else if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_KP_UP) {
      previousHistory();
    }
    // VK_DOWN | VK_KP_DOWN
    else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_KP_DOWN) {
      nextHistory();
    }
    // C+r
    else if (keyCode == KeyEvent.VK_R && modifiers == InputEvent.CTRL_MASK) {
      resetHistory();
    }
    // C+DELETE
    else if (keyCode == KeyEvent.VK_DELETE && modifiers == InputEvent.CTRL_MASK) {
      e.consume();
      textField.setText(Utilities.EMPTY_STRING);
    }
    // C+NUMPAD1 | C+1
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD1 || keyCode == KeyEvent.VK_1)) {
      setPositionFrameOnScreen(0, 1);
    }
    // C+NUMPAD2 | C+2
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD2 || keyCode == KeyEvent.VK_2)) {
      e.consume();
      setPositionFrameOnScreen(0.5, 1);
    }
    // C+NUMPAD3 | C+3
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD3 || keyCode == KeyEvent.VK_3)) {
      e.consume();
      setPositionFrameOnScreen(1, 1);
    }
    // C+NUMPAD4 | C+4
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD4 || keyCode == KeyEvent.VK_4)) {
      setPositionFrameOnScreen(0, 0.5);
    }
    // C+NUMPAD5 | C+5
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD5 || keyCode == KeyEvent.VK_5)) {
      setPositionFrameOnScreen(0.5, 0.5);
    }
    // C+NUMPAD6 | C+6
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD6 || keyCode == KeyEvent.VK_6)) {
      setPositionFrameOnScreen(1, 0.5);
    }
    // C+NUMPAD7 | C+7
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD7 || keyCode == KeyEvent.VK_7)) {
      setPositionFrameOnScreen(0, 0);
    }
    // C+NUMPAD8 | C+8
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD8 || keyCode == KeyEvent.VK_8)) {
      setPositionFrameOnScreen(0.5, 0);
    }
    // C+NUMPAD9 | C+9
    else if (modifiers == InputEvent.CTRL_MASK &&
             (keyCode == KeyEvent.VK_NUMPAD9 || keyCode == KeyEvent.VK_9)) {
      setPositionFrameOnScreen(1, 0);
    }
    else {
      resetCompletions();
    }
  }

  /**
   * Enter.
   */
  private void enter() {
    String input = textField.getText();
    if (Utilities.isEmptyString(input)) {
      return;
    }
    
    if(!performAction(input)) {
      jNPad.setStatus(input);
    }
    addToHistory(input);
    
    setVisible(false);
    
    textField.setText(Utilities.EMPTY_STRING);
    jNPad.getViewer().focusOnActiveBuffer();
  }
  
  /**
   * Perform action.
   *
   * @param input the input
   * @return true, if successful
   */
  private boolean performAction(String input) {
    if(input.startsWith(SelectColorSchemeAction.NAME)) 
      return SelectColorSchemeAction.performAction(input);
    if(input.startsWith(SelectModeAction.NAME)) 
      return SelectModeAction.performAction(input);
    if(input.startsWith(SelectFontSizeAction.NAME)) 
      return SelectFontSizeAction.performAction(input);
    if(input.startsWith(SelectEncodingAction.NAME)) 
      return SelectEncodingAction.performAction(input);
    if(input.startsWith(SelectLAFAction.NAME)) 
      return SelectLAFAction.performAction(input);
    if(input.startsWith(SelectKeymapAction.NAME)) 
      return SelectKeymapAction.performAction(input);
    if(input.startsWith(SelectLanguageAction.NAME)) 
      return SelectLanguageAction.performAction(input);
    if(input.startsWith(SelectTabSizeAction.NAME)) 
      return SelectTabSizeAction.performAction(input);
    if(input.startsWith(SelectAutoCompletionFromNthAction.NAME)) 
      return SelectAutoCompletionFromNthAction.performAction(input);
    if(input.startsWith(OpenRecentFileAction.NAME)) 
      return OpenRecentFileAction.performAction(input);
    return ActionManager.INSTANCE.executeAction(input);
  }

  /**
   * Tab.
   */
  private void tab() {
    String prefix = textField.getText();
    String s = getCompletion(prefix);
    if (s != null && !s.equals(prefix)) {
      textField.setText(s);
      textField.setCaretPosition(s.length());
    }
  }

  /**
   * Gets the completion.
   *
   * @param prefix the prefix
   * @return the completion
   */
  private String getCompletion(String prefix) {
    if (completions == null) {
      completions = getCompletions(prefix);
      index_completions = 0;
    }
    if (Utilities.isEmptyList(completions)) {
      GUIUtilities.beep();
      return null;
    }
    if (index_completions >= completions.size()) {
      index_completions = 0;
    }
    return completions.get(index_completions++);
  }
  
  /**
   * Gets the completions.
   *
   * @param prefix the prefix
   * @return the completions
   */
  private List<String> getCompletions(String prefix) {
    int index_space = prefix.indexOf(Utilities.SPACE);
    if(index_space != Utilities.INDEX_NOT_FOUND) {
      String action_name = prefix.substring(0, index_space);
      if(SelectColorSchemeAction.NAME.equals(action_name)) 
        return SelectColorSchemeAction.getCompletionsForPrefix(prefix);
      if(SelectModeAction.NAME.equals(action_name)) 
        return SelectModeAction.getCompletionsForPrefix(prefix);
      if(SelectFontSizeAction.NAME.equals(action_name)) 
        return SelectFontSizeAction.getCompletionsForPrefix(prefix);
      if(SelectEncodingAction.NAME.equals(action_name)) 
        return SelectEncodingAction.getCompletionsForPrefix(prefix);
      if(SelectLAFAction.NAME.equals(action_name)) 
        return SelectLAFAction.getCompletionsForPrefix(prefix);
      if(SelectKeymapAction.NAME.equals(action_name)) 
        return SelectKeymapAction.getCompletionsForPrefix(prefix);
      if(SelectLanguageAction.NAME.equals(action_name)) 
        return SelectLanguageAction.getCompletionsForPrefix(prefix);
      if(SelectTabSizeAction.NAME.equals(action_name)) 
        return SelectTabSizeAction.getCompletionsForPrefix(prefix);
      if(SelectAutoCompletionFromNthAction.NAME.equals(action_name)) 
        return SelectAutoCompletionFromNthAction.getCompletionsForPrefix(prefix);
      if(OpenRecentFileAction.NAME.equals(action_name)) 
        return OpenRecentFileAction.getCompletionsForPrefix(prefix);
    }
    
    List<String> list = ActionManager.INSTANCE.getCompletionsForPrefix(prefix);

    boolean sort = false;
    if(SelectColorSchemeAction.NAME.startsWith(prefix)) {
      list.add(SelectColorSchemeAction.NAME);
      sort = true;
    }
    if(SelectModeAction.NAME.startsWith(prefix)) {
      list.add(SelectModeAction.NAME);
      sort = true;
    }
    if(SelectFontSizeAction.NAME.startsWith(prefix)) {
      list.add(SelectFontSizeAction.NAME);
      sort = true;
    }
    if(SelectEncodingAction.NAME.startsWith(prefix)) {
      list.add(SelectEncodingAction.NAME);
      sort = true;
    }
    if(SelectLAFAction.NAME.startsWith(prefix)) {
      list.add(SelectLAFAction.NAME);
      sort = true;
    }
    if(SelectKeymapAction.NAME.startsWith(prefix)) {
      list.add(SelectKeymapAction.NAME);
      sort = true;
    }
    if(SelectLanguageAction.NAME.startsWith(prefix)) {
      list.add(SelectLanguageAction.NAME);
      sort = true;
    }
    if(SelectTabSizeAction.NAME.startsWith(prefix)) {
      list.add(SelectTabSizeAction.NAME);
      sort = true;
    }
    if(SelectAutoCompletionFromNthAction.NAME.startsWith(prefix)) {
      list.add(SelectAutoCompletionFromNthAction.NAME);
      sort = true;
    }
    if(OpenRecentFileAction.NAME.startsWith(prefix)) {
      list.add(OpenRecentFileAction.NAME);
      sort = true;
    }
    
    if(sort)
      Collections.sort(list);
    
    return list;
  }

  /**
   * Reset completions.
   */
  private void resetCompletions() {
    if (completions != null)
      completions = null;
  }
  
  /**
   * Adds the to history.
   *
   * @param input the input
   */
  private void addToHistory(String input) {
    if (history == null)
      history = new ArrayList<String>();
    history.add(input);
    index_history = history.size();
  }

  /**
   * Reset history.
   */
  private void resetHistory() {
    if (history != null)
      history = null;
  }
  
  /**
   * Previous history.
   */
  private void previousHistory() {
    if (history == null) {
      GUIUtilities.beep();
      return;
    }
    final String text = textField.getText();
    while (true) {
      String s = getPrevious();
      if (s == null)
        break;
      if (!s.equals(text)) {
        textField.setText(s);
        //textField.selectAll();
        break;
      }
    }
  }
    
  /**
   * Gets the previous.
   *
   * @return the previous
   */
  private String getPrevious() {
    if (history != null && index_history > 0 && index_history <= history.size())
      return history.get(--index_history);
    GUIUtilities.beep();
    return null;
  }

  /**
   * Next history.
   */
  private void nextHistory() {
    if (history == null) {
      GUIUtilities.beep();
      return;
    }
    final String text = textField.getText();
    while (true) {
      String s = getNext();
      if (s == null)
        break;
      if (!s.equals(text)) {
        textField.setText(s);
        //textField.selectAll();
        break;
      }
    }
  }

  /**
   * Gets the next.
   *
   * @return the next
   */
  private String getNext() {
    if (history != null && index_history < history.size() - 1 && index_history >= 0)
      return history.get(++index_history);
    GUIUtilities.beep();
    return null;
  }

}
////////////////////////////////////////////////////////////////////////////////
