/*******************************************************************************
 * Copyright (c) 2000, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.text.edits;

import java.util.List;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;

/**
 * A multi-text edit can be used to aggregate several edits into
 * one edit. The edit itself doesn't modify a document.
 * <p>
 * Clients are allowed to implement subclasses of a multi-text
 * edit.Subclasses must implement <code>doCopy()</code> to ensure
 * the a copy of the right type is created. Not implementing
 * <code>doCopy()</code> in subclasses will result in an assertion
 * failure during copying.
 *
 * @since 3.0
 */
public class MultiTextEdit extends TextEdit {

	private boolean fDefined;

	/**
	 * Creates a new <code>MultiTextEdit</code>. The range
	 * of the edit is determined by the range of its children.
	 *
	 * Adding this edit to a parent edit sets its range to the
	 * range covered by its children. If the edit doesn't have
	 * any children its offset is set to the parent's offset
	 * and its length is set to 0.
	 */
	public MultiTextEdit() {
		super(0, Integer.MAX_VALUE);
		fDefined= false;
	}

	/**
	 * Creates a new </code>MultiTextEdit</code> for the given
	 * range. Adding a child to this edit which isn't covered
	 * by the given range will result in an exception.
	 *
	 * @param offset the edit's offset
	 * @param length the edit's length.
	 * @see TextEdit#addChild(TextEdit)
	 * @see TextEdit#addChildren(TextEdit[])
	 */
	public MultiTextEdit(int offset, int length) {
		super(offset, length);
		fDefined= true;
	}

	/*
	 * Copy constructor.
	 */
	protected MultiTextEdit(MultiTextEdit other) {
		super(other);
	}

	/**
	 * Checks the edit's integrity.
	 * <p>
	 * Note that this method <b>should only be called</b> by the edit
	 * framework and not by normal clients.</p>
	 *<p>
	 * This default implementation does nothing. Subclasses may override
	 * if needed.</p>
	 *
	 * @exception MalformedTreeException if the edit isn't in a valid state
	 *  and can therefore not be executed
	 */
	protected void checkIntegrity() throws MalformedTreeException {
		// does nothing
	}

	@Override
	final boolean isDefined() {
		if (fDefined)
			return true;
		return hasChildren();
	}

	@Override
	public final int getOffset() {
		if (fDefined)
			return super.getOffset();

		List<TextEdit> children= internalGetChildren();
		if (children == null || children.size() == 0)
			return 0;
		// the children are already sorted
		return children.get(0).getOffset();
	}

	@Override
	public final int getLength() {
		if (fDefined)
			return super.getLength();

		List<TextEdit> children= internalGetChildren();
		if (children == null || children.size() == 0)
			return 0;
		// the children are already sorted
		TextEdit first= children.get(0);
		TextEdit last= children.get(children.size() - 1);
		return last.getOffset() - first.getOffset() + last.getLength();
	}

	@Override
	public final boolean covers(TextEdit other) {
		if (fDefined)
			return super.covers(other);
		// an undefined multiple text edit covers everything
		return true;
	}

	@Override
	protected boolean canZeroLengthCover() {
		return true;
	}

	/*
	 * @see TextEdit#copy
	 */
	@Override
	protected TextEdit doCopy() {
		Assert.isTrue(MultiTextEdit.class == getClass(), "Subclasses must reimplement copy0"); //$NON-NLS-1$
		return new MultiTextEdit(this);
	}

	/*
	 * @see TextEdit#accept0
	 */
	@Override
	protected void accept0(TextEditVisitor visitor) {
		boolean visitChildren= visitor.visit(this);
		if (visitChildren) {
			acceptChildren(visitor);
		}
	}

	@Override
	void adjustOffset(int delta) {
		if (fDefined)
			super.adjustOffset(delta);
	}

	@Override
	void adjustLength(int delta) {
		if (fDefined)
			super.adjustLength(delta);
	}

	/*
	 * @see TextEdit#performConsistencyCheck
	 */
	@Override
	void performConsistencyCheck(TextEditProcessor processor, IDocument document) throws MalformedTreeException {
		checkIntegrity();
	}

	/*
	 * @see TextEdit#performDocumentUpdating
	 */
	@Override
	int performDocumentUpdating(IDocument document) throws BadLocationException {
		fDelta= 0;
		return fDelta;
	}

	/*
	 * @see TextEdit#deleteChildren
	 */
	@Override
	boolean deleteChildren() {
		return false;
	}

	@Override
	void aboutToBeAdded(TextEdit parent) {
		defineRegion(parent.getOffset());
	}

	void defineRegion(int parentOffset) {
		if (fDefined)
			return;
		if (hasChildren()) {
			IRegion region= getCoverage(getChildren());
			internalSetOffset(region.getOffset());
			internalSetLength(region.getLength());
		} else {
			internalSetOffset(parentOffset);
			internalSetLength(0);
		}
		fDefined= true;
	}

	@Override
	void internalToString(StringBuffer buffer, int indent) {
		super.internalToString(buffer, indent);
		if (! fDefined)
			buffer.append(" [undefined]"); //$NON-NLS-1$
	}
}
