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

import java.util.LinkedList;

import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

import jnpad.GUIUtilities;
import jnpad.config.Config;
import jnpad.config.Updatable;

/**
 * The Class CompoundUndoManager.
 *
 * @version 0.3
 * @since jNPad 0.1
 */
public class CompoundUndoManager implements UndoableEditListener {
  MyUndoManager                    undoManager;
  private LinkedList<CompoundEdit> editStack;
  private CompoundEdit             currentEdit;
  private Buffer                   buffer;

  /** The last edit that was performed before the last save. */
  private volatile UndoableEdit    savePoint;

  /**
   * Instantiates a new compound undo manager.
   *
   * @param buffer the buffer
   */
  public CompoundUndoManager(Buffer buffer) {
    this.buffer = buffer;
    undoManager = new MyUndoManager();
    currentEdit = undoManager;
    editStack = new LinkedList<CompoundEdit>();
  }

  /**
   * Undoable edit happened.
   *
   * @param e UndoableEditEvent
   * @see javax.swing.event.UndoableEditListener#undoableEditHappened(javax.swing.event.UndoableEditEvent)
   */
  @Override
  public void undoableEditHappened(UndoableEditEvent e) {
    addUndoableEdit(e.getEdit());
  }

  /**
   * Adds the undoable edit.
   *
   * @param edit UndoableEdit
   */
  public void addUndoableEdit(UndoableEdit edit) {
    currentEdit.addEdit(edit);
    notifyUndoHappened();
  }

  /**
   * Begin compound edit.
   */
  public void beginCompoundEdit() {
    editStack.add(currentEdit);
    currentEdit = new CompoundEdit();
  }

  /**
   * End compound edit.
   */
  public void endCompoundEdit() {
    currentEdit.end();
    CompoundEdit lastEdit = editStack.removeLast();
    lastEdit.addEdit(currentEdit);
    currentEdit = lastEdit;
    notifyUndoHappened();
  }

  /**
   * Notify undo happened.
   */
  private void notifyUndoHappened() {
    if (currentEdit == undoManager) {
      GUIUtilities.runOrInvokeLater(new Runnable() {
        public void run() {
          buffer.updateControls(Updatable.CTRLS_UNDO);
        }
      });
    }
  }

  /**
   * Can undo.
   *
   * @return true, if successful
   */
  public boolean canUndo() {
    return undoManager.canUndo();
  }

  /**
   * Can redo.
   *
   * @return true, if successful
   */
  public boolean canRedo() {
    return undoManager.canRedo();
  }

  /**
   * Undo.
   *
   * @throws CannotUndoException the cannot undo exception
   */
  public void undo() throws CannotUndoException {
    undoManager.undo();
  }

  /**
   * Redo.
   *
   * @throws CannotUndoException the cannot undo exception
   */
  public void redo() throws CannotUndoException {
    undoManager.redo();
  }

  /**
   * Discard all edits.
   */
  public void discardAllEdits() {
    undoManager.discardAllEdits();
  }

  /**
   * Document saved.
   */
  public void documentSaved() {
    savePoint = undoManager.editToBeUndone();
  }

  /**
   * Determines if the document is in the same undo state as it was when it was
   * last saved.
   * 
   * @return true if all changes have been undone since the last save
   */
  public boolean isModified() {
    return undoManager.editToBeUndone() != savePoint;
  }

  /////////////////////////////////////////////////////////////////////////////
  /**
   * The Class MyUndoManager.
   */
  static class MyUndoManager extends UndoManager {
    /** UID */
    private static final long serialVersionUID = -3588486176872330816L;

    /**
     * Instantiates a new my undo manager.
     */
    MyUndoManager() {
      setLimit(Config.UNDO_LIMIT.getValue());
    }

    /**
     * Last edit.
     *
     * @return the undoable edit
     * @see javax.swing.undo.CompoundEdit#lastEdit()
     */
    @Override
    protected UndoableEdit lastEdit() {
      return super.lastEdit();
    }

    /**
     * Edits the to be undone.
     *
     * @return UndoableEdit
     * @see javax.swing.undo.UndoManager#editToBeUndone()
     */
    @Override
    protected UndoableEdit editToBeUndone() {
      return super.editToBeUndone();
    }
  }
  /////////////////////////////////////////////////////////////////////////////

}
