/*******************************************************************************
 * Copyright (c) 2003, 2005 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.jst.j2ee.internal.webservice.command;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jst.j2ee.webservice.wsclient.WebServicesClient;


public class CommandMoveServiceRefs extends AbstractCommand {
	private EObject source_;
	private EObject target_;
	private EStructuralFeature childFeature_;
	private Object[] children_;
	private boolean isCopy_;
	private Object[] clonedChildren_;
	private EList serviceRefs;

	public CommandMoveServiceRefs(String label, String description, EObject source, EObject target, EStructuralFeature childFeature, Object[] children, boolean isCopy, EList serviceRefs) {
		super(label, description);
		source_ = source;
		target_ = target;
		childFeature_ = childFeature;
		children_ = children;
		isCopy_ = isCopy;
		clonedChildren_ = null;
		this.serviceRefs = serviceRefs;
	}

	private Object cloneObject(Object object) {
		if (object instanceof EList) {
			EList clone = new BasicEList();
			for (Iterator it = ((EList) object).iterator(); it.hasNext();) {
				clone.add(cloneObject(it.next()));
			}
			return clone;
		}
		if (object instanceof EObject) {
			EObject source = (EObject) object;
			EClass eClass = ((EObject) object).eClass();
			EPackage ePackage = eClass.getEPackage();
			EFactory eFactory = ePackage.getEFactoryInstance();
			EObject clone = eFactory.create(eClass);
			EList features = eClass.getEAllStructuralFeatures();
			for (Iterator it = features.iterator(); it.hasNext();) {
				EStructuralFeature feature = (EStructuralFeature) it.next();
				if (source.eIsSet(feature)) {
					Object clonedFeature = cloneObject(source.eGet(feature));
					if (feature.isMany())
						((List) clone.eGet(feature)).addAll((List) clonedFeature);
					else
						clone.eSet(feature, clonedFeature);
				} else
					clone.eUnset(feature);
			}
			return clone;
		}
		if (object != null)
			return new String(object.toString());
		return null;
	}

	/**
	 * Called at most once in {@link #canExecute}to give the command an opportunity to ready itself
	 * for execution. The returned value is stored in {@link #canExecute}. In other words, you can
	 * override this method to initialize and to yield a cached value for the all subsequent calls
	 * to canExecute.
	 * 
	 * @return whether the command is executable.
	 */
	protected boolean prepare() {
		return true;
	}

	/**
	 * Returns whether the comamad is valid to <code>execute</code>. The
	 * {@link UnexecutableCommand#INSTANCE}.<code>canExecute()</code> always returns
	 * <code>false</code>. This <b>must </b> be called before calling <code>execute</code>.
	 * 
	 * @return whether the comamad is valid to <code>execute</code>.
	 */
	public boolean canExecute() {
		return super.canExecute();
	}

	/**
	 * Performs the command activity required for the effect. The effect of calling
	 * <code>execute</code> when <code>canExecute</code> returns <code>false</code>, or when
	 * <code>canExecute</code> hasn't been called, is undefined.
	 */
	public void execute() {
		if (childFeature_.isMany())
			executeMany();
		else
			executeSingle();
	}

	private void executeSingle() {
		Object child = source_.eGet(childFeature_);
		if (isCopy_)
			target_.eSet(childFeature_, cloneObject(child));
		else {
			source_.eUnset(childFeature_);
			target_.eSet(childFeature_, child);
		}
	}

	private void executeMany() {
		EList sourceList;
		if (source_ instanceof WebServicesClient)
			sourceList = serviceRefs;
		else
			sourceList = (EList) source_.eGet(childFeature_);
		EList targetList;
		if (target_ instanceof WebServicesClient)
			targetList = serviceRefs;
		else
			targetList = (EList) target_.eGet(childFeature_);
		if (isCopy_)
			clonedChildren_ = new Object[children_.length];
		for (int i = 0; i < children_.length; i++) {
			int index = sourceList.indexOf(children_[i]);
			if (index != -1) {
				if (isCopy_) {
					clonedChildren_[i] = cloneObject(children_[i]);
					targetList.add(clonedChildren_[i]);
				} else {
					sourceList.remove(index);
					targetList.add(children_[i]);
				}
			}
		}
	}

	/**
	 * Returns <code>true</code> because most command should be undoable.
	 * 
	 * @return <code>true</code>.
	 */
	public boolean canUndo() {
		return true;
	}

	/**
	 * Performs the command activity required to <code>undo</code> the effects of a preceding
	 * <code>execute</code> (or <code>redo</code>). The effect, if any, of calling
	 * <code>undo</code> before <code>execute</code> or <code>redo</code> have been called, or
	 * when canUndo returns <code>false</code>, is undefined.
	 */
	public void undo() {
		if (childFeature_.isMany())
			undoSingle();
		else
			undoMany();
	}

	private void undoSingle() {
		Object child = target_.eGet(childFeature_);
		target_.eUnset(childFeature_);
		if (!isCopy_)
			source_.eSet(childFeature_, child);
	}

	private void undoMany() {
		EList sourceList;
		if (source_ instanceof WebServicesClient)
			sourceList = serviceRefs;
		else
			sourceList = (EList) source_.eGet(childFeature_);
		EList targetList;
		if (target_ instanceof WebServicesClient)
			targetList = serviceRefs;
		else
			targetList = (EList) target_.eGet(childFeature_);
		if (isCopy_) {
			if (clonedChildren_ != null) {
				for (int i = 0; i < clonedChildren_.length; i++) {
					targetList.remove(clonedChildren_[i]);
				}
			}
		} else {
			for (int i = 0; i < children_.length; i++) {
				int index = targetList.indexOf(children_[i]);
				if (index != -1) {
					targetList.remove(index);
					sourceList.add(children_[i]);
				}
			}
		}
	}

	/**
	 * Performs the command activity required to <code>redo</code> the effect after undoing the
	 * effect. The effect, if any, of calling <code>redo</code> before <code>undo</code> is
	 * called is undefined. Note that if you implement <code>redo</code> to call
	 * <code>execute</code> then any derived class will be restricted by that decision also.
	 */
	public void redo() {
		execute();
	}

	/**
	 * Returns a collection of things which this command wishes to present as it's result. The
	 * result of calling this before an <code>execute</code> or <code>redo</code>, or after an
	 * <code>undo</code>, is undefined.
	 * 
	 * @return a collection of things which this command wishes to present as it's result.
	 */
	public Collection getResult() {
		return super.getResult();
	}

	/**
	 * Returns the collection of things which this command wishes to present as the objects affected
	 * by the command. Typically should could be used as the selection that should be highlighted to
	 * best illustrate the effect of the command. The result of calling this before an
	 * <code>execute</code>,<code>redo</code>, or <code>undo</code> is undefined. The
	 * result may be different after an <code>undo</code> than it is after an <code>execute</code>
	 * or <code>redo</code>, but the result should be the same (equivalent) after either an
	 * <code>execute</code> or <code>redo</code>.
	 * 
	 * @return the collection of things which this command wishes to present as the objects affected
	 *         by the command.
	 */
	public Collection getAffectedObjects() {
		return super.getAffectedObjects();
	}

	/**
	 * Called to indicate that the command will never be used again. Calling any other method after
	 * this one has undefined results.
	 */
	public void dispose() {
		//Do nothing
	}

	/**
	 * Returns a command that represents the composition of this command with the given command. The
	 * resulting command may just be this, if this command is capabable of composition. Otherwise,
	 * it will be a new command created to compose the two.
	 * <p>
	 * Instead of the following pattern of usage
	 * 
	 * <pre>
	 * Command result = x;
	 * if (condition)
	 * 	result = result.chain(y);
	 * </pre>
	 * 
	 * you should consider using a {@link org.eclipse.emf.common.command.CompoundCommand}and using
	 * {@link org.eclipse.emf.common.command.CompoundCommand#unwrap()}to optimize the result:
	 * 
	 * <pre>
	 * CompoundCommand subcommands = new CompoundCommand();
	 * subcommands.append(x);
	 * if (condition)
	 * 	subcommands.append(y);
	 * Command result = subcommands.unwrap();
	 * </pre>
	 * 
	 * This gives you more control over how the compound command composes it's result and affected
	 * objects.
	 * 
	 * @param command
	 *            the command to chain.
	 * @return a command that represents the composition of this command with the given command.
	 */
	public Command chain(Command command) {
		return super.chain(command);
	}
}
