/*
 * (c) Copyright IBM Corp. 2000, 2001.
 * All Rights Reserved.
 */
package net.sourceforge.phpdt.internal.corext.textmanipulation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.sourceforge.phpdt.internal.corext.textmanipulation.TextEditNode.RootNode;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;

// import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
// import net.sourceforge.phpdt.core.JavaModelException;
//
// import net.sourceforge.phpdt.internal.corext.Assert;

/**
 * A <code>TextBufferEditor</code> manages a set of <code>TextEdit</code>s
 * and applies them as a whole to a <code>TextBuffer</code>. Added
 * <code>TextEdit</code>s must not overlap. The only exception from this rule
 * are insertion point. There can be more than one insert point at the same text
 * position. Clients should use the method <code>
 * canPerformEdits</code> to
 * validate if all added text edits follow these rules.
 * <p>
 * Clients can attach more than one <code>TextBufferEditor</code> to a single
 * <code>
 * TextBuffer</code>. If so <code>canPerformEdits</code> validates
 * all text edits from all text buffer editors working on the same text buffer.
 */
public class TextBufferEditor {

	private TextBuffer fBuffer;

	private List fEdits;

	private RootNode fRootNode;

	private int fNumberOfNodes;

	private int fConnectCount;

	private int fMode;

	/* package */static final int UNDEFINED = 0;

	/* package */static final int REDO = 1;

	/* package */static final int UNDO = 2;

	/**
	 * Creates a new <code>TextBufferEditor</code> for the given
	 * <code>TextBuffer</code>.
	 * 
	 * @param the
	 *            text buffer this editor is working on.
	 */
	public TextBufferEditor(TextBuffer buffer) {
		fBuffer = buffer;
		// Assert.isNotNull(fBuffer);
		fEdits = new ArrayList();
	}

	/**
	 * Returns the text buffer this editor is working on.
	 * 
	 * @return the text buffer this editor is working on
	 */
	public TextBuffer getTextBuffer() {
		return fBuffer;
	}

	/**
	 * Adds a <code>TextEdit</code> to this text editor. Adding a
	 * <code>TextEdit</code> to a <code>TextBufferEditor</code> transfers
	 * ownership of the edit to the editor. So after a edit has been added to a
	 * editor the creator of that edit <b>must</b> not continue modifing it.
	 * 
	 * @param edit
	 *            the text edit to be added
	 * @exception CoreException
	 *                if the text edit can not be added to this text buffer
	 *                editor
	 */
	public void add(TextEdit edit) throws CoreException {
		// Assert.isTrue(fMode == UNDEFINED || fMode == REDO);
		internalAdd(edit);
		fMode = REDO;
	}

	/**
	 * Adds a <code>MultiTextEdit</code> to this text editor. Adding a
	 * <code>MultiTextEdit</code> to a <code>TextBufferEditor</code>
	 * transfers ownership of the edit to the editor. So after a edit has been
	 * added to a editor the creator of that edit <b>must</b> not continue
	 * modifing it.
	 * 
	 * @param edit
	 *            the multi text edit to be added
	 * @exception CoreException
	 *                if the multi text edit can not be added to this text
	 *                buffer editor
	 */
	public void add(MultiTextEdit edit) throws CoreException {
		// Assert.isTrue(fMode == UNDEFINED || fMode == REDO);
		edit.connect(this);
		fMode = REDO;
	}

	/**
	 * Adds a <code>UndoMemento</code> to this text editor. Adding a
	 * <code>UndoMemento</code> to a <code>TextBufferEditor</code> transfers
	 * ownership of the memento to the editor. So after a memento has been added
	 * to a editor the creator of that memento <b>must</b> not continue
	 * modifing it.
	 * 
	 * @param undo
	 *            the undo memento to be added
	 * @exception CoreException
	 *                if the undo memento can not be added to this text buffer
	 *                editor
	 */
	public void add(UndoMemento undo) throws CoreException {
		// Assert.isTrue(fMode == UNDEFINED);
		List list = undo.fEdits;
		// Add them reverse since we are adding undos.
		for (int i = list.size() - 1; i >= 0; i--) {
			internalAdd((TextEdit) list.get(i));
		}
		fMode = undo.fMode;
	}

	/**
	 * Checks if the <code>TextEdit</code> added to this text editor can be
	 * executed.
	 * 
	 * @return <code>true</code> if the edits can be executed. Return
	 *         <code>false
	 * 	</code>otherwise. One major reason why text edits
	 *         cannot be executed is a wrong offset or length value of a
	 *         <code>TextEdit</code>.
	 */
	public boolean canPerformEdits() {
		if (fRootNode != null)
			return true;
		fRootNode = buildTree();
		if (fRootNode == null)
			return false;
		if (fRootNode.validate(fBuffer.getLength()))
			return true;

		fRootNode = null;
		return false;
	}

	/**
	 * Clears the text buffer editor.
	 */
	public void clear() {
		fRootNode = null;
		fMode = UNDEFINED;
		fEdits.clear();
	}

	/**
	 * Executes the text edits added to this text buffer editor and clears all
	 * added text edits.
	 * 
	 * @param pm
	 *            a progress monitor to report progress or <code>null</code>
	 *            if no progress is desired.
	 * @return an object representing the undo of the executed
	 *         <code>TextEdit</code>s
	 * @exception CoreException
	 *                if the edits cannot be executed
	 */
	public UndoMemento performEdits(IProgressMonitor pm) throws CoreException {
		if (pm == null)
			pm = new NullProgressMonitor();

		int size = fEdits.size();
		if (size == 0)
			return new UndoMemento(fMode == UNDO ? REDO : UNDO);

		if (fRootNode == null) {
			fRootNode = buildTree();
			if (fRootNode == null || !fRootNode.validate(fBuffer.getLength())) {
				// throw new JavaModelException(null,
				// IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
			}
		}
		try {
			pm.beginTask("", fNumberOfNodes + 10); //$NON-NLS-1$
			UndoMemento undo = null;
			if (fMode == REDO) {
				undo = fRootNode.performDo(fBuffer, pm);
				fRootNode.performedDo();
			} else {
				undo = fRootNode.performUndo(fBuffer, pm);
				fRootNode.performedUndo();
			}
			pm.worked(10);
			return undo;
		} finally {
			pm.done();
			clear();
		}
	}

	// ---- Helper methods
	// ------------------------------------------------------------

	private RootNode buildTree() {
		TextEditNode[] nodes = new TextEditNode[fEdits.size()];
		for (int i = fEdits.size() - 1; i >= 0; i--) {
			nodes[i] = TextEditNode.create((TextEdit) fEdits.get(i));
		}
		fNumberOfNodes = nodes.length;
		Arrays.sort(nodes, new TextEditNodeComparator());
		RootNode root = new RootNode(fBuffer.getLength());
		for (int i = 0; i < nodes.length; i++) {
			root.add(nodes[i]);
		}
		return root;
	}

	private void internalAdd(TextEdit edit) throws CoreException {
		edit.index = fEdits.size();
		edit.isSynthetic = fConnectCount > 0;
		try {
			fConnectCount++;
			edit.connect(this);
		} finally {
			fConnectCount--;
		}
		fEdits.add(edit);
	}
}
