/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.properties;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.RSSOwlMenu;
import net.sourceforge.rssowl.util.i18n.KeyCodeParser;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.HotkeyShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;

import java.util.Enumeration;
import java.util.Vector;

/**
 * Class displays a PropertyPage to assign hotkeys to common actions in RSSOwl
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class HotkeysProperties extends PropertyPage {
  Button assignKeyButton;
  Text charSequence;
  int countFullKey;
  boolean firstKeyPress;
  boolean fullKeyWasPressed;
  Vector keys;
  int keyValue = 0;
  Combo selectCategoryCombo;
  Combo selectCommandCombo;

  /**
   * Instatiate a new HotkeysProperties PropertyPage
   * 
   * @param parent Composite's parent
   * @param rssOwlGui The MainController
   */
  public HotkeysProperties(Composite parent, GUI rssOwlGui) {
    super(parent, rssOwlGui);
    keys = new Vector();
    countFullKey = 0;
    firstKeyPress = false;
    fullKeyWasPressed = false;
  }

  /**
   * @see net.sourceforge.rssowl.controller.properties.PropertyPage#applyButtonPressed()
   */
  public void applyButtonPressed() {
    HotkeyShop.hotKeys = propertyChangeManager.getHotkeys();
    rssOwlGui.getRSSOwlMenu().updateAccelerators();
    rssOwlGui.getRSSOwlMenu().initMnemonics();
    rssOwlGui.getRSSOwlQuickview().updateI18N();
    rssOwlGui.getRSSOwlNewsText().updateI18N();
  }

  /**
   * No action is done here. The definition of hotkeys need to be applyd
   * pressing the "Assign" button.
   */
  public void updatePropertiesChangeManager() {
  // Not necessary
  }

  /** Init all components */
  protected void initComponents() {

    /** Group to hold the command controls */
    Group commandGroup = new Group(composite, SWT.NONE);
    commandGroup.setText(GUI.i18n.getTranslation("GROUP_COMMAND"));
    commandGroup.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    commandGroup.setLayout(new GridLayout(2, false));
    commandGroup.setFont(FontShop.dialogFont);

    /** Label Category */
    Label labelCategory = new Label(commandGroup, SWT.NONE);
    labelCategory.setText(GUI.i18n.getTranslation("LABEL_CATEGORY") + ": ");
    labelCategory.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
    labelCategory.setFont(dialogFont);

    /** Combo to select a category */
    selectCategoryCombo = new Combo(commandGroup, SWT.READ_ONLY);
    selectCategoryCombo.setVisibleItemCount(RSSOwlMenu.menuStructure.length);
    selectCategoryCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    selectCategoryCombo.setFont(dialogFont);
    selectCategoryCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** Clear the select command combo */
        selectCommandCombo.removeAll();

        /** Show all Elements */
        selectCommandCombo.setVisibleItemCount(RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()].length);

        /** Fill the select command combo with the commands from the category */
        int selectionIndex = selectCategoryCombo.getSelectionIndex();
        for (int a = 0; a < RSSOwlMenu.subMenuStructure[selectionIndex].length; a++)
          selectCommandCombo.add(GUI.i18n.getTranslation(RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][a]));

        /** Preselect first element */
        selectCommandCombo.select(0);

        /**
         * Preset the charSequence from the first command of the select command
         * combo
         */
        String type = RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][selectCommandCombo.getSelectionIndex()];
        charSequence.setText(((String[]) propertyChangeManager.getHotkeys().get(type))[0]);

        /** Clear any warnings */
        setWarningMessage(null);
      }
    });

    /** Fill the select category combo with all categorys */
    for (int a = 0; a < RSSOwlMenu.menuStructure.length; a++) {
      selectCategoryCombo.add(GUI.i18n.getTranslation(RSSOwlMenu.menuStructure[a]));
    }

    /** Preselect first item */
    selectCategoryCombo.select(0);

    /** Label Name */
    Label labelName = new Label(commandGroup, SWT.NONE);
    labelName.setText(GUI.i18n.getTranslation("LABEL_NAME") + ": ");
    labelName.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING));
    labelName.setFont(dialogFont);

    /** Combo to select a command */
    selectCommandCombo = new Combo(commandGroup, SWT.READ_ONLY);
    selectCommandCombo.setVisibleItemCount(RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()].length);
    selectCommandCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    selectCommandCombo.setFont(dialogFont);
    selectCommandCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /**
         * Set the keySequence of the selected command to the charSequence input
         * field
         */
        String type = RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][selectCommandCombo.getSelectionIndex()];
        charSequence.setText(((String[]) propertyChangeManager.getHotkeys().get(type))[0]);

        /** Clear any warnings */
        setWarningMessage(null);
      }
    });

    /** Fill commands from first category */
    int selectionIndex = selectCategoryCombo.getSelectionIndex();
    for (int a = 0; a < RSSOwlMenu.subMenuStructure[selectionIndex].length; a++)
      selectCommandCombo.add(GUI.i18n.getTranslation(RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][a]));

    /** Preselect first item */
    selectCommandCombo.select(0);

    /** Group that holds all keySequence controls */
    Group keySequenceGroup = new Group(composite, SWT.NONE);
    keySequenceGroup.setText(GUI.i18n.getTranslation("LABEL_KEY_SEQUENCE"));
    keySequenceGroup.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    keySequenceGroup.setLayout(new GridLayout(2, false));
    keySequenceGroup.setFont(FontShop.dialogFont);

    /** Label to enter the key sequence */
    Label labelEnterKeySequence = new Label(keySequenceGroup, SWT.WRAP);
    labelEnterKeySequence.setText(GUI.i18n.getTranslation("DIALOG_MESSAGE_KEYSEQUENCE") + ": ");
    labelEnterKeySequence.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));
    labelEnterKeySequence.setFont(dialogFont);

    /** Holds the input field and the assign button */
    Composite charSequenceInputComposite = new Composite(keySequenceGroup, SWT.NONE);
    charSequenceInputComposite.setLayout(new GridLayout(2, false));
    charSequenceInputComposite.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 2));

    /** Input for the key-sequence */
    charSequence = new Text(charSequenceInputComposite, SWT.SINGLE | SWT.BORDER);
    charSequence.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.HORIZONTAL_ALIGN_BEGINNING));
    charSequence.setFont(dialogFont);

    /** Allow Esc, Enter, Shift+Tab and Mod+Tab as Hotkeys */
    charSequence.addTraverseListener(new TraverseListener() {
      public void keyTraversed(TraverseEvent e) {
        switch (e.detail) {

          /** Only allow Tab if in combination with another Key */
          case SWT.TRAVERSE_TAB_NEXT:
            e.doit = (e.stateMask == SWT.NONE);
            break;

          /** Allow Shift+Tab, Esc and Enter */
          case SWT.TRAVERSE_ESCAPE:
          case SWT.TRAVERSE_RETURN:
          case SWT.TRAVERSE_TAB_PREVIOUS:
            e.doit = false;
        }
      }
    });

    /** Disable context menu */
    charSequence.setMenu(new Menu(charSequenceInputComposite));

    /** Listen for key pressed and released */
    charSequence.addKeyListener(new KeyListener() {

      /** The user has pressed a key */
      public void keyPressed(KeyEvent e) {
        onKeyPressed(e);

        /** Do not write char into input field */
        e.doit = false;
      }

      /** The user has released a key */
      public void keyReleased(KeyEvent e) {
        onKeyReleased();
      }
    });

    /** Preselect with key sequence from first command */
    String type = RSSOwlMenu.subMenuStructure[0][0];
    charSequence.setText(((String[]) propertyChangeManager.getHotkeys().get(type))[0]);

    assignKeyButton = new Button(charSequenceInputComposite, SWT.PUSH);
    assignKeyButton.setText(GUI.i18n.getTranslation("BUTTON_ASSIGN"));
    assignKeyButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    assignKeyButton.setFont(dialogFont);
    assignKeyButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {

        /** Update the key sequence of the selected command */
        String type = RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][selectCommandCombo.getSelectionIndex()];
        propertyChangeManager.getHotkeys().put(type, new String[] { charSequence.getText(), String.valueOf(keyValue) });

        /** See if hotkey was occupied */
        String isOccupiedBy = isKeyOccupied(keyValue, false);

        /** Clear old assignment */
        if (isOccupiedBy != null)
          propertyChangeManager.getHotkeys().put(isOccupiedBy, new String[] { "", String.valueOf(HotkeyShop.NULL_ACCELERATOR_VALUE) });

        /** Reset */
        keyValue = 0;
        countFullKey = 0;

        /** Clear any warnings */
        setWarningMessage(null);

        /** Disable button again */
        assignKeyButton.setEnabled(false);
      }
    });

    /** Disabled at first */
    assignKeyButton.setEnabled(false);

    /** Error Label */
    errorMessageLabel = new CLabel(keySequenceGroup, SWT.NONE);
    errorMessageLabel.setFont(dialogFont);

    /** Fill with some spacer */
    LayoutShop.setDialogSpacer(composite, 2, 3);
  }

  /**
   * @see net.sourceforge.rssowl.controller.properties.PropertyPage#restoreButtonPressed()
   */
  protected void restoreButtonPressed() {
    HotkeyShop.initDefaultAccelerators();
    propertyChangeManager.setHotkeys(HotkeyShop.hotKeys);

    /** Preselect with key sequence from selected command */
    String type = RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][selectCommandCombo.getSelectionIndex()];
    charSequence.setText(((String[]) propertyChangeManager.getHotkeys().get(type))[0]);

    /** Remove warnings */
    setWarningMessage(null);
  }

  /**
   * A full key is any key, that is not Ctrl, Shift or Alt. These keys need a
   * combination with a full key to work as accelerator (e.g. Ctrl+B)
   * 
   * @param keyCode Selected key
   * @return TRUE if key is a full key
   */
  boolean isFullKey(int keyCode) {
    switch (keyCode) {
      case SWT.CTRL:
        return false;
      case SWT.SHIFT:
        return false;
      case SWT.ALT:
        return false;
      case SWT.COMMAND:
        return false;
    }
    return true;
  }

  /**
   * Check if a given key sequence is occupied by a command
   * 
   * @param keySequence Selected key sequence
   * @param matchSelf TRUE if selected command should be checked too
   * @return NULL if key sequence was not occupied, and the translation key of
   * the assigend command if occupied. Returns "_SELF" if the key sequence is
   * occupied by the selected command
   */
  String isKeyOccupied(int keySequence, boolean matchSelf) {
    Enumeration commands = propertyChangeManager.getHotkeys().keys();

    /** Dont look at invalid keysequence */
    if (keySequence < 1)
      return null;

    /** For all commands */
    while (commands.hasMoreElements()) {
      String type = (String) commands.nextElement();

      /** Key sequence of the commands */
      int selectedKeySequence = Integer.parseInt(((String[]) propertyChangeManager.getHotkeys().get(type))[1]);

      /** Compare key sequence to selected key sequence */
      if (selectedKeySequence == keySequence && !type.equals(RSSOwlMenu.subMenuStructure[selectCategoryCombo.getSelectionIndex()][selectCommandCombo.getSelectionIndex()]))
        return type;
      else if (selectedKeySequence == keySequence && matchSelf)
        return "_SELF";
    }
    return null;
  }

  /**
   * A key was pressed inside the hotkey input field.
   * 
   * @param e The occuring KeyEvent
   */
  void onKeyPressed(KeyEvent e) {

    /** Reset the key value because this is the first key press */
    if (!firstKeyPress) {
      keyValue = 0;
      countFullKey = 0;
      firstKeyPress = true;
    }

    /** Get the formated key */
    String key = KeyCodeParser.format(e.keyCode);

    /** Key is not printable, dont write to input field */
    if (key.equals("")) {
      charSequence.setSelection(charSequence.getText().length(), charSequence.getText().length());
    }

    /** User has pressed the backspace key, delete input field */
    else if (key.equals("DEL") && charSequence.getText().length() > 0) {
      charSequence.setText("");
    }

    /** User has pressed a printable key */
    else if (!key.equals("DEL")) {

      /** Add to list of pressed keys */
      keys.add(KeyCodeParser.format(e.keyCode));

      /** Key is a full key */
      if (isFullKey(e.keyCode)) {
        fullKeyWasPressed = true;
        countFullKey++;
      }

      /** Key is not a full key */
      else
        fullKeyWasPressed = false;

      /** This is the first pressed key */
      if (keyValue == 0)
        keyValue = e.keyCode;

      /** Perform bitwise OR on keyValue */
      else
        keyValue |= e.keyCode;

      /** Set the entered key sequence formatted as text */
      setKeySequenceText();
    }

    /** Set state of assign button */
    String isKeyOccupied = isKeyOccupied(keyValue, true);
    if (isKeyOccupied != null && isKeyOccupied.equals("_SELF"))
      assignKeyButton.setEnabled(false);
    else
      assignKeyButton.setEnabled(true);
  }

  /**
   * A key was released inside the hotkey input field.
   */
  void onKeyReleased() {

    /** Clear the keys vector */
    keys.clear();

    /** Reset */
    firstKeyPress = false;

    /** Remove key sequence if no full key was pressed */
    if (!fullKeyWasPressed) {
      charSequence.setText("");
      keyValue = 0;
      countFullKey = 0;
      setWarningMessage(null);
    }

    /** See if the key sequence is occupied or invalid */
    else {

      String isOccupiedBy = isKeyOccupied(keyValue, false);

      /** Key sequence is invalid */
      if (countFullKey > 1)
        setWarningMessage(GUI.i18n.getTranslation("LABEL_INVALID_KEYSEQUENCE"));

      /** Key sequence is occupied */
      else if (isOccupiedBy != null)
        setWarningMessage(GUI.i18n.getTranslation("LABEL_USED_BY") + ": " + GUI.i18n.getTranslation(isOccupiedBy));

      /** Key sequence is not occupied */
      else
        setWarningMessage(null);
    }
  }

  /** Set the entered key sequence formatted as text */
  void setKeySequenceText() {
    String keySequence = "";
    int a;

    /** Build the key sequence as String */
    for (a = 0; a < keys.size() - 1; a++)
      keySequence += keys.get(a) + "+";
    keySequence += keys.get(a);

    /** Add '+' if last key is not a full key */
    if (!fullKeyWasPressed)
      keySequence += "+";

    /** Set Text and selection */
    charSequence.setText(keySequence);
    charSequence.setSelection(charSequence.getText().length(), charSequence.getText().length());
  }
}