/**
 * File:    JWTDeleteCommand.java
 * Created: 18.01.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * 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:
 *    Christian Saad, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.meta.commands;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jwt.meta.PluginProperties;
import org.eclipse.jwt.meta.commands.helper.CommandHelper;
import org.eclipse.jwt.meta.commands.interfaces.IInterruptibleCommand;
import org.eclipse.jwt.meta.model.core.Package;
import org.eclipse.jwt.meta.model.processes.Activity;

/**
 * The command checks if the collection of elements that should be deleted
 * contains elements which could be referenced to somewhere else in the model
 * (ReferenceableElement could be referenced by References, Activities can be
 * referenced by ActivityLinkNodes).
 * 
 * On trying to delete an element which is still referenced the user has to
 * decide if he wants to delete all references to the given elements as well.
 * The cause for this is, that if there are still references and the referenced
 * element is deleted, the model file is a mess and not loadable any more!
 * 
 * @version $Id: JWTDeleteCommand.java,v 1.5 2009-12-10 13:31:50 chsaad Exp $
 * @author Christian Saad (csaad@gmx.net), Programming distributed Systems Lab,
 *         University of Augsburg, Germany, www.ds-lab.org
 */
public class JWTDeleteCommand extends DeleteCommand implements IInterruptibleCommand
{

	/**
	 * If the selected elements which should be deleted have references to them
	 * somewhere in the model, these references are stored here. Otherwise is
	 * stays null. Depending on the value the user must decide if the references
	 * should also be deleted.
	 */
	private Set referencedElements = null;


	/**
	 * The constructor.
	 * 
	 * @param domain
	 * @param collection
	 */
	public JWTDeleteCommand(EditingDomain domain, Collection<?> collection)
	{
		super(domain, collection);
	}


	/**
	 * This is executed just before the command is executed. If this function
	 * returns false, the command will not be executed. It checks whether the
	 * elements that should be deleted have active references. If not, it can be
	 * executed. If yes, a dialog is presented and if the user chooses to delete
	 * the references it is executed, if not it is aborted.
	 * 
	 * @return true if the command should be executed
	 */
	public boolean checkIfCommandCanBeExecuted()
	{
		// for each element that should be deleted, collect all
		// ReferenceableElements and
		// Activities of this element (recursively if necessary).
		Set relevantElements = new HashSet();
		for (Iterator iterator = collection.iterator(); iterator.hasNext();)
		{
			relevantElements.addAll(CommandHelper
					.collectRelevantElements((EObject) iterator.next()));
		}

		// collect all elements which are still referenced somewhere in the
		// model starting
		// with the root model object
		Package rootPackage = (Package) CommandHelper.getRoot((EObject) collection
				.iterator().next());
		Set referencedElements = CommandHelper.collectReferencedElements(
				relevantElements, rootPackage, false);

		// collect activities
		Set activities = new HashSet<Activity>();
		for (Object object : relevantElements)
		{
			if (object instanceof Activity)
			{
				activities.add((Activity) object);
			}
		}
		for (Object object : referencedElements)
		{
			if (object instanceof Activity)
			{
				activities.add((Activity) object);
			}
		}

		// if activities are to be deleted, ask user
		if (activities.size() > 0)
		{
			if (!dialogDeleteActivities(activities))
			{
				// if the activities should not be deleted -> nothing to do
				return false;
			}
		}

		// if no elements are referenced -> command can be executed
		if (referencedElements.size() == 0)
		{
			return true;
		}

		// if elements are still referenced -> show dialog
		if (!dialogDeleteAllReferences(referencedElements))
		{
			// if the references should not be deleted -> nothing to do
			return false;
		}
		else
		{
			// if the references should be deleted -> command can be executed
			this.referencedElements = referencedElements;
			return true;
		}
	}


	/**
	 * Displays a dialog and a confirmation dialog and returns true if the user
	 * wants to delete all references.
	 * 
	 * @param referencedElements
	 * @return true if references should be deleted, false if not
	 */
	private boolean dialogDeleteAllReferences(Set referencedElements)
	{
		// collect names of elements which are still referenced
		String refNameString = "\n\n" + CommandHelper.collectNames(referencedElements) //$NON-NLS-1$
				+ "\n\n"; //$NON-NLS-1$

		// show message
		boolean deleteReferences = MessageDialog.openQuestion(null,
				PluginProperties.editor_StillReferenced_title, PluginProperties.bind(
						PluginProperties.editor_StillReferencedDelete_message,
						refNameString));

		// show confirmation message
		if (deleteReferences)
		{
			deleteReferences = MessageDialog.openQuestion(null,
					PluginProperties.editor_StillReferenced_title,
					PluginProperties.editor_StillReferencedConfirmation_message);
		}
		return deleteReferences;
	}


	/**
	 * Displays a dialog and a confirmation dialog and returns true if the user
	 * wants to delete all activities.
	 * 
	 * @param referencedElements
	 * @return true if references should be deleted, false if not
	 */
	private boolean dialogDeleteActivities(Set activities)
	{
		// collect names of elements which are still referenced
		String actNameString = "\n\n" + CommandHelper.collectNames(activities) //$NON-NLS-1$
				+ "\n\n"; //$NON-NLS-1$

		// show message
		return MessageDialog.openQuestion(null,
				PluginProperties.editor_DeleteActivities_title, PluginProperties.bind(
						PluginProperties.editor_DeleteActivities_message, actNameString));
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DeleteCommand#execute()
	 */
	@Override
	public void execute()
	{
		// if no additional references have to be deleted, we can execute the
		// command.
		// if there are additional references, we have to delete them to.
		if (referencedElements == null)
		{
			super.execute();
		}
		else
		{
			// get elements (References, ActivityLinkNodes) which have a
			// reference to the
			// elements which should be deleted (Activities,
			// ReferenceableElements)
			Package rootPackage = (Package) CommandHelper.getRoot((EObject) collection
					.iterator().next());
			Set referenceElements = CommandHelper.collectReferencedElements(
					referencedElements, rootPackage, true);

			// delete references and referenced elements
			Command dC = DeleteCommand.create(domain, referenceElements);
			if (dC.canExecute())
			{
				commandList.add(0, dC);
				compoundExecute();
			}
		}
	}


	/**
	 * Copied from CompoundCommand to avoid the cross referencing detection of
	 * DeleteCommand.
	 */
	private void compoundExecute()
	{
		for (ListIterator<Command> commands = commandList.listIterator(); commands
				.hasNext();)
		{
			try
			{
				Command command = commands.next();
				command.execute();
			}
			catch (RuntimeException exception)
			{
				// Skip over the command that threw the exception.
				//
				commands.previous();

				try
				{
					// Iterate back over the executed commands to undo them.
					//
					while (commands.hasPrevious())
					{
						Command command = commands.previous();
						if (command.canUndo())
						{
							command.undo();
						}
						else
						{
							break;
						}
					}
				}
				catch (RuntimeException nestedException)
				{
					CommonPlugin.INSTANCE.log(new WrappedException(CommonPlugin.INSTANCE
							.getString("_UI_IgnoreException_exception"), nestedException)
							.fillInStackTrace());
				}

				throw exception;
			}
		}

		// remove other crossreferences
		Collection<EObject> eObjects = new LinkedHashSet<EObject>();
		for (Object wrappedObject : collection)
		{
			Object object = AdapterFactoryEditingDomain.unwrap(wrappedObject);
			if (object instanceof EObject)
			{
				eObjects.add((EObject) object);
				for (Iterator<EObject> j = ((EObject) object).eAllContents(); j.hasNext();)
				{
					eObjects.add(j.next());
				}
			}
			else if (object instanceof Resource)
			{
				for (Iterator<EObject> j = ((Resource) object).getAllContents(); j
						.hasNext();)
				{
					eObjects.add(j.next());
				}
			}
		}

		Map<EObject, Collection<EStructuralFeature.Setting>> usages = EcoreUtil.UsageCrossReferencer
				.findAll(eObjects, domain.getResourceSet());
		for (Map.Entry<EObject, Collection<EStructuralFeature.Setting>> entry : usages
				.entrySet())
		{
			EObject eObject = entry.getKey();
			Collection<EStructuralFeature.Setting> settings = entry.getValue();
			for (EStructuralFeature.Setting setting : settings)
			{
				EObject referencingEObject = setting.getEObject();
				if (!eObjects.contains(referencingEObject))
				{
					EStructuralFeature eStructuralFeature = setting
							.getEStructuralFeature();
					if (eStructuralFeature.isChangeable())
					{
						if (eStructuralFeature.isMany())
						{
							appendAndExecute(RemoveCommand.create(domain,
									referencingEObject, eStructuralFeature, eObject));
						}
						else
						{
							appendAndExecute(SetCommand.create(domain,
									referencingEObject, eStructuralFeature,
									SetCommand.UNSET_VALUE));
						}
					}
				}
			}
		}

	}


	@Override
	public void dispose()
	{
		super.dispose();

		if (referencedElements != null)
		{
			referencedElements.clear();
		}
	}

}
