/*******************************************************************************
 * Copyright (c) 2000, 2008 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.internal.core.model;

import org.eclipse.cdt.core.CConventions;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.IBuffer;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICModelStatus;
import org.eclipse.cdt.core.model.ICModelStatusConstants;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;

/**
 * <p>This abstract class implements behavior common to <code>CreateElementInTUOperations</code>.
 * To create a compilation unit, or an element contained in a compilation unit, the
 * source code for the entire compilation unit is updated and saved.
 *
 * <p>The element being created can be positioned relative to an existing
 * element in the compilation unit via the methods <code>#createAfter</code>
 * and <code>#createBefore</code>. By default, the new element is positioned
 * as the last child of its parent element.
 *
 */
public abstract class CreateElementInTUOperation extends CModelOperation {

	/**
	 * A constant meaning to position the new element
	 * as the last child of its parent element.
	 */
	protected static final int INSERT_LAST = 1;

	/**
	 * A constant meaning to position the new element
	 * after the element defined by <code>fAnchorElement</code>.
	 */
	protected static final int INSERT_AFTER = 2;

	/**
	 * A constant meaning to position the new element
	 * before the element defined by <code>fAnchorElement</code>.
	 */
	protected static final int INSERT_BEFORE = 3;

	/**
	 * One of the position constants, describing where
	 * to position the newly created element.
	 */
	protected int fInsertionPolicy = INSERT_LAST;

	/**
	 * The element that is being created.
	 */
	protected String fCreatedElement = null;

	/**
	 * The element that the newly created element is
	 * positioned relative to, as described by
	 * <code>fInsertPosition</code>, or <code>null</code>
	 * if the newly created element will be positioned
	 * last.
	 */
	protected ICElement fAnchorElement = null;

	/**
	 * A flag indicating whether creation of a new element occurred.
	 * A request for creating a duplicate element would request in this
	 * flag being set to <code>false</code>. Ensures that no deltas are generated
	 * when creation does not occur.
	 */
	protected boolean fCreationOccurred = true;

	/**
	 * The position of the element that is being created.
	 */
	protected int fInsertionPosition = -1;

	/**
	 * The number of characters the new element replaces,
	 * or 0 if the new element is inserted,
	 * or -1 if the new element is append to the end of the CU.
	 */
	protected int fReplacementLength = -1;

	/**
	 * Constructs an operation that creates a C Language Element with
	 * the specified parent, contained within a translation unit.
	 */
	public CreateElementInTUOperation(ICElement parentElement) {
		super(null, new ICElement[]{parentElement});
		initializeDefaultPosition();
	}

	/**
	 * Only allow cancelling if this operation is not nested.
	 */
	@Override
	protected void checkCanceled() {
		if (!fNested) {
			super.checkCanceled();
		}
	}

	/**
	 * Instructs this operation to position the new element after
	 * the given sibling, or to add the new element as the last child
	 * of its parent if <code>null</code>.
	 */
	public void createAfter(ICElement sibling) {
		setRelativePosition(sibling, INSERT_AFTER);
	}

	/**
	 * Instructs this operation to position the new element before
	 * the given sibling, or to add the new element as the last child
	 * of its parent if <code>null</code>.
	 */
	public void createBefore(ICElement sibling) {
		setRelativePosition(sibling, INSERT_BEFORE);
	}

	protected abstract String generateElement(ITranslationUnit unit) throws CModelException;
	
	/**
	 * Execute the operation - generate new source for the compilation unit
	 * and save the results.
	 *
	 * @exception CModelException if the operation is unable to complete
	 */
	@Override
	protected void executeOperation() throws CModelException {
		beginTask(getMainTaskName(), getMainAmountOfWork());
		CElementDelta delta = newCElementDelta();
		ITranslationUnit unit = getTranslationUnit();
		fCreatedElement = generateElement(unit);
		insertElement();
		if (fCreationOccurred) {
			//a change has really occurred
			IBuffer buffer = unit.getBuffer();
			if (buffer == null) return;
			char[] bufferContents = buffer.getCharacters();
			if (bufferContents == null) return;
			char[] elementContents = Util.normalizeCRs(getCreatedElementCharacters(), bufferContents);
			switch (fReplacementLength) {
				case -1 : 
					// element is append at the end
					buffer.append(elementContents);
					break;

				case 0 :
					// element is inserted
					buffer.replace(fInsertionPosition, 0, elementContents);
					break;

				default :
					// element is replacing the previous one
					buffer.replace(fInsertionPosition, fReplacementLength, elementContents);
			}
			unit.save(null, false);
			boolean isWorkingCopy = unit.isWorkingCopy();
			//if (isWorkingCopy) {
			//	this.setAttributes(...);
			//}
			worked(1);
			fResultElements = generateResultHandles();
			if (!isWorkingCopy) { // if unit is working copy, then save will have already fired the delta
				if (unit.getParent().exists()) {
					for (ICElement resultElement : fResultElements) {
						delta.added(resultElement);
					}
					addDelta(delta);
				} // else unit is created outside classpath
				  // non-java resource delta will be notified by delta processor
			}
		}
		done();
	}

	private char[] getCreatedElementCharacters() {
		return fCreatedElement.toCharArray();
	}

	/**
	 * Creates and returns the handle for the element this operation created.
	 */
	protected abstract ICElement generateResultHandle();

	/**
	 * Creates and returns the handles for the elements this operation created.
	 */
	protected ICElement[] generateResultHandles() throws CModelException {
		return new ICElement[]{generateResultHandle()};
	}

	/**
	 * Returns the compilation unit in which the new element is being created.
	 */
	protected ITranslationUnit getTranslationUnit() {
		return ((ISourceReference)getParentElement()).getTranslationUnit();
	}

	/**
	 * Returns the amount of work for the main task of this operation for
	 * progress reporting.
	 * @see #executeOperation()
	 */
	protected int getMainAmountOfWork(){
		return 2;
	}

	/**
	 * Returns the name of the main task of this operation for
	 * progress reporting.
	 * @see #executeOperation()
	 */
	protected abstract String getMainTaskName();

	/**
	 * Returns the elements created by this operation.
	 */
	@Override
	public ICElement[] getResultElements() {
		return fResultElements;
	}

	/**
	 * Sets the default position in which to create the new type
	 * member. By default, the new element is positioned as the
	 * last child of the parent element in which it is created.
	 * Operations that require a different default position must
	 * override this method.
	 */
	protected void initializeDefaultPosition() {
	}

	/**
	 * Inserts the given child into the given JDOM, 
	 * based on the position settings of this operation.
	 *
	 * @see #createAfter(ICElement)
	 * @see #createBefore(ICElement)
	 */
	protected void insertElement() throws CModelException {
		if (fInsertionPolicy != INSERT_LAST) {
			ISourceRange range = ((ISourceReference)fAnchorElement).getSourceRange();
			switch (fInsertionPolicy) {
				case INSERT_AFTER:
					fReplacementLength = 0;
					fInsertionPosition = range.getStartPos() + range.getLength();
				break;

				case INSERT_BEFORE:
					fReplacementLength = 0;
					fInsertionPosition = range.getStartPos();
				break;

				default:
					fReplacementLength = range.getStartPos() + range.getLength();
					fInsertionPosition = range.getStartPos();
			}
			return;
		}
		//add as the last element of the parent
		fReplacementLength = -1;
	}

	/**
	 * Sets the name of the <code>DOMNode</code> that will be used to
	 * create this new element.
	 * Used by the <code>CopyElementsOperation</code> for renaming.
	 * Only used for <code>CreateTypeMemberOperation</code>
	 */
	protected void setAlteredName(String newName) {
	}

	/**
	 * Instructs this operation to position the new element relative
	 * to the given sibling, or to add the new element as the last child
	 * of its parent if <code>null</code>. The <code>position</code>
	 * must be one of the position constants.
	 */
	protected void setRelativePosition(ICElement sibling, int policy) throws IllegalArgumentException {
		if (sibling == null) {
			fAnchorElement = null;
			fInsertionPolicy = INSERT_LAST;
		} else {
			fAnchorElement = sibling;
			fInsertionPolicy = policy;
		}
	}

	/**
	 * Possible failures: <ul>
	 *  <li>NO_ELEMENTS_TO_PROCESS - the compilation unit supplied to the operation is
	 * 		<code>null</code>.
	 *  <li>INVALID_NAME - no name, a name was null or not a valid
	 * 		import declaration name.
	 *  <li>INVALID_SIBLING - the sibling provided for positioning is not valid.
	 * </ul>
	 * @see ICModelStatus
	 * @see CConventions
	 */
	@Override
	public ICModelStatus verify() {
		if (getParentElement() == null) {
			return new CModelStatus(ICModelStatusConstants.NO_ELEMENTS_TO_PROCESS);
		}
		if (fAnchorElement != null) {
			ICElement domPresentParent = fAnchorElement.getParent();
			if (!domPresentParent.equals(getParentElement())) {
				return new CModelStatus(ICModelStatusConstants.INVALID_SIBLING, fAnchorElement);
			}
		}
		return CModelStatus.VERIFIED_OK;
	}

}
