/* LanguageTool, a natural language style checker 
 * Copyright (C) 2005 Daniel Naber (http://www.danielnaber.de)
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */
package org.languagetool.gui;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.JTextComponent;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import org.languagetool.JLanguageTool;

/**
 * Provides Undo/Redo support and actions for JTextComponent
 *
 * @author Panagiotis Minos
 */
class UndoRedoSupport {

  final UndoAction undoAction;
  final RedoAction redoAction;
  
  private final UndoManager undoManager;
  private final ResourceBundle messages;
  
  private boolean compoundMode = false;
  private CompoundEdit ce = null;

  UndoRedoSupport(JTextComponent textComponent, ResourceBundle messages) {
    this.messages = messages;
    undoManager = new UndoManager();
    undoAction = new UndoAction();
    redoAction = new RedoAction();
    textComponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
      @Override
      public void undoableEditHappened(UndoableEditEvent e) {
        if(compoundMode) {
          ce.addEdit(e.getEdit());
        } else {
          undoManager.addEdit(e.getEdit());
        }
        undoAction.updateUndoState();
        redoAction.updateRedoState();
      }
    });
    InputMap inputMap = textComponent.getInputMap();
    KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    inputMap.put(key, "undo");
    textComponent.getActionMap().put("undo", undoAction);
    key = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | java.awt.event.InputEvent.SHIFT_DOWN_MASK);
    inputMap.put(key, "redo");
    textComponent.getActionMap().put("redo", redoAction);
  }

  /**
   * Notify manager to start merging undoable edits.
   * 
   * Calling startCompoundEdit when already in compound mode is an error
   * and will throw a RuntimeException.
   *
   * @since 2.7
   */
  void startCompoundEdit()
  {
      if(compoundMode) {
        throw new RuntimeException("already in compound mode");
      }
      ce = new CompoundEdit();
      compoundMode = true;
  }

  /**
   * Notify manager to stop merging undoable edits.
   * 
   * Calling endCompoundEdit when not in compound mode is an error
   * and will throw a RuntimeException.
   *
   * @since 2.7
   */  
  void endCompoundEdit()
  {
      if(!compoundMode) {
        throw new RuntimeException("not in compound mode");
      }      
      ce.end();
      undoManager.addEdit(ce);
      ce = null;
      compoundMode = false;
  }

  class UndoAction extends AbstractAction {

    private UndoAction() {
      super(messages.getString("guiUndo"));
      Image img;
      img = Toolkit.getDefaultToolkit().getImage(
              JLanguageTool.getDataBroker().getFromResourceDirAsUrl("sc_undo.png"));
      putValue(Action.SMALL_ICON, new ImageIcon(img));
      img = Toolkit.getDefaultToolkit().getImage(
              JLanguageTool.getDataBroker().getFromResourceDirAsUrl("lc_undo.png"));
      putValue(Action.LARGE_ICON_KEY, new ImageIcon(img));
      KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
      putValue(Action.ACCELERATOR_KEY, key);
      putValue(Action.MNEMONIC_KEY, KeyEvent.VK_U);
      setEnabled(false);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      try {
        undoManager.undo();
      } catch (CannotUndoException ex) {
        // ignore
      }
      updateUndoState();
      redoAction.updateRedoState();
    }

    private void updateUndoState() {
      if (undoManager.canUndo()) {
        setEnabled(true);
      } else {
        setEnabled(false);
      }
    }
  }

  class RedoAction extends AbstractAction {

    private RedoAction() {
      super(messages.getString("guiRedo"));
      Image img;
      img = Toolkit.getDefaultToolkit().getImage(
              JLanguageTool.getDataBroker().getFromResourceDirAsUrl("sc_redo.png"));
      putValue(Action.SMALL_ICON, new ImageIcon(img));
      img = Toolkit.getDefaultToolkit().getImage(
              JLanguageTool.getDataBroker().getFromResourceDirAsUrl("lc_redo.png"));
      putValue(Action.LARGE_ICON_KEY, new ImageIcon(img));
      KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
              Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | java.awt.event.InputEvent.SHIFT_DOWN_MASK);
      putValue(Action.ACCELERATOR_KEY, key);
      putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R);
      setEnabled(false);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      try {
        undoManager.redo();
      } catch (CannotRedoException ex) {
        // ignore
      }
      updateRedoState();
      undoAction.updateUndoState();
    }

    private void updateRedoState() {
      setEnabled(undoManager.canRedo());
    }
  }
}
