/*******************************************************************************
 * Copyright (c) 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.bpel.ui.commands;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.bpel.model.EventHandler;
import org.eclipse.bpel.model.FaultHandler;
import org.eclipse.bpel.model.Flow;
import org.eclipse.bpel.model.Link;
import org.eclipse.bpel.model.PartnerLink;
import org.eclipse.bpel.model.Process;
import org.eclipse.bpel.model.partnerlinktype.PartnerLinkType;
import org.eclipse.bpel.model.util.BPELUtils;
import org.eclipse.bpel.ui.BPELEditor;
import org.eclipse.bpel.ui.Messages;
import org.eclipse.bpel.ui.adapters.IContainer;
import org.eclipse.bpel.ui.adapters.ILabeledElement;
import org.eclipse.bpel.ui.commands.util.AutoUndoCommand;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.FlowLinkUtil;
import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.wsdl.Definition;

/**
 * Deletes a model object from an IContainer, and removes any references to it
 * from other model objects.
 * 
 * This is more or less the opposite of InsertInContainerCommand.
 * 
 * TODO: simplify this command now that it's an AutoUndoCommand!
 */
public class DeleteChildCommand extends AutoUndoCommand {

	private EObject fChild;
	private EObject fParent;

	IContainer fContainer;

	Resource[] resourcesToModify = null;

	CompoundCommand fDeleteLinksCmd;
	CompoundCommand fDeletePLTsCmd;

	public DeleteChildCommand(EObject child) {
		super(new ArrayList(2));

		fParent = child.eContainer();

		if (fParent instanceof FaultHandler || fParent instanceof EventHandler) {
			// If the child of the FH/EH is the *only* child, then we want to
			// delete the
			// entire FH/EH, since it has no meaningful attributes of its own.
			IContainer<EObject> container = BPELUtil.adapt(fParent,
					IContainer.class);
			List<EObject> children = container.getChildren(fParent);
			if (children.size() == 1 && children.contains(child)) {
				// delete the FH instead
				child = fParent;
				fParent = child.eContainer();
			}
		}
		this.fChild = child;

		if (fParent != null) {
			addModelRoot(fParent);
		}

		fContainer = BPELUtil.adapt(fParent, IContainer.class);

		String childType = null;

		ILabeledElement labeledElement = BPELUtil.adapt(child,
				ILabeledElement.class);
		if (labeledElement != null) {
			childType = labeledElement.getTypeLabel(child);
		}
		if (childType == null) {
			childType = Messages.DeleteChildCommand_Item_1;
		}
		setLabel(NLS.bind(Messages.DeleteChildCommand_Delete_2,
				(new Object[] { childType })));
	}

	/**
	 * @see org.eclipse.bpel.ui.commands.util.AutoUndoCommand#canDoExecute()
	 */
	@Override
	public boolean canDoExecute() {
		if (fChild == null || fParent == null || fContainer == null) {
			return false;
		}
		return fContainer.canRemoveChild(fParent, fChild);
	}

	// TODO: this is a hack.
	@Override
	public Resource[] getResources() {
		if (resourcesToModify == null) {
			Process process = BPELUtils.getProcess(fParent);
			if (process == null)
				return EMPTY_RESOURCE_ARRAY;
			BPELEditor bpelEditor = ModelHelper.getBPELEditor(process);

			Set<Resource> resultSet = new HashSet<Resource>();
			resultSet.add(fParent.eResource());

			// Figure out which model objects are being deleted.
			HashSet<Object> deletingSet = new HashSet<Object>();
			ModelHelper.addSubtreeToCollection(fChild, deletingSet);

			// If we are deleting any PartnerLinks which reference PLTs in the
			// Artifacts WSDL
			// file, also delete the referenced PLTs.
			Set<PartnerLinkType> partnerLinkTypes = null;
			Definition artifactsDefinition = bpelEditor
					.getArtifactsDefinition();

			for (Iterator it = deletingSet.iterator(); it.hasNext();) {
				Object object = it.next();
				if (object instanceof PartnerLink) {
					PartnerLinkType plt = ((PartnerLink) object)
							.getPartnerLinkType();
					if ((plt != null)
							&& (plt.getEnclosingDefinition() == artifactsDefinition)) {
						if (partnerLinkTypes == null)
							partnerLinkTypes = new HashSet<PartnerLinkType>();
						if (partnerLinkTypes.add(plt)) {
							resultSet.add(plt.eResource());
						}
					}
				}
			}
			resourcesToModify = resultSet
					.toArray(new Resource[resultSet.size()]);
		}
		return resourcesToModify;
	}

	/**
	 * @see org.eclipse.bpel.ui.commands.util.AutoUndoCommand#doExecute()
	 */
	@Override
	public void doExecute() {

		if (!canExecute()) {
			throw new IllegalStateException();
		}

		Process process = BPELUtils.getProcess(fParent);
		if (process == null)
			return;
		BPELEditor bpelEditor = ModelHelper.getBPELEditor(process);

		EObject topModelObject = process;

		// Multi-delete safety: if the child does not have a resource, assume it
		// has
		// already been deleted, and do nothing.
		if (fChild.eResource() == null) {
			return;
		}

		// Figure out which model objects are being deleted.
		HashSet deletingSet = new HashSet();
		ModelHelper.addSubtreeToCollection(fChild, deletingSet);

		// If we are deleting any PartnerLinks which reference PLTs in the
		// Artifacts WSDL
		// file, also delete the referenced PLTs.
		Set<PartnerLinkType> partnerLinkTypes = null;
		Definition artifactsDefinition = bpelEditor.getArtifactsDefinition();

		for (Iterator it = deletingSet.iterator(); it.hasNext();) {
			Object object = it.next();
			if (object instanceof PartnerLink) {
				PartnerLinkType plt = ((PartnerLink) object)
						.getPartnerLinkType();
				if ((plt != null)
						&& (plt.getEnclosingDefinition() == artifactsDefinition)) {
					// We should ask the user if delete the partner link type
					if (MessageDialog
							.openQuestion(
									PlatformUI.getWorkbench()
											.getActiveWorkbenchWindow()
											.getShell(),
									Messages.DeletePartnerLinkTypeWarningDialogTitle,
									NLS.bind(Messages.DeletePartnerLinkTypeWarningMessage,
											 (new Object[] {((PartnerLink) object).getName(), plt.getName() })))) {
						if (partnerLinkTypes == null)
							partnerLinkTypes = new HashSet<PartnerLinkType>();
						if (partnerLinkTypes.add(plt)) {
							if (fDeletePLTsCmd == null)
								fDeletePLTsCmd = new CompoundCommand();
							fDeletePLTsCmd
									.add(new DeletePartnerLinkTypeCommand(plt));
						}
					}
				}
			}
		}

		// TODO: Build the set of "all model objects" and subtract...
		HashSet notDeletingSet = new HashSet();
		ModelHelper.addSubtreeToCollection(topModelObject, notDeletingSet);
		notDeletingSet.removeAll(deletingSet);

		// We also need to find any flow links which involve a deleted object
		// and remove them.
		// This is a hack, but it could be worse..

		// step 1: find all the flows which contain deleted objects
		HashSet<Flow> flowSet = new HashSet<Flow>();
		for (Iterator<EObject> it = deletingSet.iterator(); it.hasNext();) {
			Flow[] flws = FlowLinkUtil.getParentFlows(it.next());
			flowSet.addAll(Arrays.asList(flws));
		}
		// step 2: if any of the flows is being deleted, we can ignore it
		// this is safe because the source, dest and link itself are all
		// children of the flow
		flowSet.removeAll(deletingSet);

		// step 3: check each link in each of the remaining flows to see if it
		// involves a
		// deleted object. Even if both source and target are being deleted, we
		// should still
		// delete the link, since it is a child of the Flow which is not being
		// deleted.
		fDeleteLinksCmd = new CompoundCommand();
		for (Iterator<Flow> flowIt = flowSet.iterator(); flowIt.hasNext();) {
			Flow flow = flowIt.next();
			for (Iterator<Link> it = FlowLinkUtil.getFlowLinks(flow).iterator(); it
					.hasNext();) {
				Link link = it.next();
				if (deletingSet.contains(FlowLinkUtil.getLinkSource(link))
						|| deletingSet.contains(FlowLinkUtil
								.getLinkTarget(link))) {
					// NOTE: this is safe even if the link is scheduled for
					// deletion by
					// a GEF DeleteAction, see comment in DeleteLinkCommand.
					DeleteLinkCommand child = new DeleteLinkCommand(link);
					if (child.canExecute())
						fDeleteLinksCmd.add(child);
				}
			}
		}

		fDeleteLinksCmd.doExecute();

		if (fDeletePLTsCmd != null) {
			fDeletePLTsCmd.doExecute();
		}

		// finally, we can remove the child.
		fContainer.removeChild(fParent, fChild);

		// IMPORTANT: since the parent is a not-deleted object and the child
		// will be
		// a deleted object, this step must be done *after* the IContainer
		// removal ;)
		for (Iterator it = notDeletingSet.iterator(); it.hasNext();) {
			BPELUtil.deleteNonContainmentRefs((EObject) it.next(), deletingSet);
		}
	}
}
