package org.eclipse.jwt.we.misc.providers;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.CopyToClipboardCommand;
import org.eclipse.emf.edit.command.CutToClipboardCommand;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.DragAndDropCommand;
import org.eclipse.emf.edit.command.PasteFromClipboardCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jwt.meta.commands.processes.RemoveActivityEdgeCommand;
import org.eclipse.jwt.meta.model.core.ReferenceableElement;
import org.eclipse.jwt.meta.model.processes.Action;
import org.eclipse.jwt.meta.model.processes.ProcessesPackage;
import org.eclipse.jwt.meta.model.processes.Scope;
import org.eclipse.jwt.meta.providers.interfaces.ICommandProvider;
import org.eclipse.jwt.we.commands.WEDeleteCommand;
import org.eclipse.jwt.we.commands.WEDragAndDropCommand;
import org.eclipse.jwt.we.commands.clipboard.WECopyToClipboardCommand;
import org.eclipse.jwt.we.commands.clipboard.WECutToClipboardCommand;
import org.eclipse.jwt.we.commands.clipboard.WEPasteFromClipboardCommand;
import org.eclipse.jwt.we.commands.core.WERemovePackageElementsCommand;
import org.eclipse.jwt.we.commands.core.WERemovePackageSubpackagesCommand;
import org.eclipse.jwt.we.commands.processes.WERemoveActivityNodeCommand;
import org.eclipse.jwt.we.commands.view.CreateReferenceEdgeCommand;
import org.eclipse.jwt.we.editors.WEEditor;
import org.eclipse.jwt.we.misc.factories.CreateReferenceFactory;
import org.eclipse.jwt.we.misc.util.EMFHelper;
import org.eclipse.jwt.we.misc.views.LayoutDataManager;
import org.eclipse.jwt.we.model.view.EdgeDirection;
import org.eclipse.jwt.we.model.view.Reference;
import org.eclipse.jwt.we.model.view.ReferenceEdge;
import org.eclipse.jwt.we.model.view.ViewPackage;

public class MetaModelCommandProvider implements ICommandProvider
{

	/**
	 * The workflow editor.
	 */
	private WEEditor weeditor;


	/**
	 * The constructor.
	 * 
	 * @param weeditor
	 */
	public MetaModelCommandProvider(WEEditor weeditor)
	{
		super();
		this.weeditor = weeditor;
	}


	/**
	 * Creates a command that sets the ExecutedBy/PerformedBy Feature of the
	 * corresponding action.
	 * 
	 * @param domain
	 * @param action
	 * @param referFeature
	 * @param referElem
	 * @return
	 */
	private Command createActionExecutedByPerformedBySetCommand(EditingDomain domain,
			Action action, EStructuralFeature referFeature, Object referElem)
	{
		// if set to -null-
		if (referElem == null)
		{
			// find the refEdge for the RefElem, which should be removed from
			// the action
			ReferenceEdge refEdge = EMFHelper.getReferenceEdgeForAction(weeditor, action,
					(ReferenceableElement) action.eGet(referFeature));

			// return delete command
			return new DeleteCommand(domain, Collections.singleton(refEdge));
		}
		// if set to a value
		else
		{
			CompoundCommand createCommand = new CompoundCommand();

			// search for matching reference in the current scope
			Reference reference = EMFHelper.getReferenceForScope(weeditor, (Scope) action
					.eContainer(), (ReferenceableElement) referElem);

			// if no reference already exists, create a new one and append the
			// add command
			if (reference == null)
			{
				reference = (Reference) new CreateReferenceFactory(
						(ReferenceableElement) referElem).getNewObject();

				reference.setContainedIn((Scope) action.eContainer());

				LayoutDataManager.setX(weeditor, reference, LayoutDataManager.getX(
						weeditor, action));
				LayoutDataManager.setY(weeditor, reference, LayoutDataManager.getY(
						weeditor, action) - 80);

				createCommand.append(AddCommand.create(domain, weeditor.getDiagramData(),
						ViewPackage.Literals.DIAGRAM__REFERENCES, Collections
								.singleton(reference)));
			}

			// append the command to create the reference edge from action to
			// reference
			CreateReferenceEdgeCommand createRefEdgeCommand = new CreateReferenceEdgeCommand(
					weeditor, domain);
			createRefEdgeCommand.setSource(action);
			createRefEdgeCommand.setTarget(reference);
			createRefEdgeCommand.setDirection(0);

			createCommand.append(createRefEdgeCommand);

			// return the (add-reference +) create-referenceedge command
			return createCommand;
		}
	}


	/**
	 * Creates a command that adds Input/Output Features of the corresponding
	 * action.
	 * 
	 * @param domain
	 * @param owner
	 * @param referFeature
	 * @param refElements
	 * @return
	 */
	private Command createActionInputsOutputsAddCommand(EditingDomain domain,
			Action action, EStructuralFeature referFeature, Collection<?> refElements)
	{
		CompoundCommand addEdgesCommand = new CompoundCommand();

		// for every refElem to be added, create corresponding commands
		for (Iterator iterator = refElements.iterator(); iterator.hasNext();)
		{
			ReferenceableElement refElem = (ReferenceableElement) iterator.next();

			// we need a reference, to connect the action to
			Reference reference = null;

			// if an refEdge already exists (IN or OUT), use its reference as
			// target
			ReferenceEdge refEdge = EMFHelper.getReferenceEdgeForAction(weeditor, action,
					refElem);
			if (refEdge != null)
			{
				reference = refEdge.getReference();
			}

			// if no refEdge existed, locate matching reference in scope
			if (reference == null)
			{
				reference = EMFHelper.getReferenceForScope(weeditor, (Scope) action
						.eContainer(), (ReferenceableElement) refElem);
			}

			// if no matching reference exists, create one and append the add
			// command
			if (reference == null)
			{
				reference = (Reference) new CreateReferenceFactory(
						(ReferenceableElement) refElem).getNewObject();

				reference.setContainedIn((Scope) action.eContainer());

				LayoutDataManager.setX(weeditor, reference, LayoutDataManager.getX(
						weeditor, action));
				LayoutDataManager.setY(weeditor, reference, LayoutDataManager.getY(
						weeditor, action) - 80);

				addEdgesCommand.append(AddCommand.create(domain, weeditor
						.getDiagramData(), ViewPackage.Literals.DIAGRAM__REFERENCES,
						Collections.singleton(reference)));
			}

			// append the command to create the reference edge itself
			CreateReferenceEdgeCommand createRefEdgeCommand = new CreateReferenceEdgeCommand(
					weeditor, domain);
			createRefEdgeCommand.setSource(action);
			createRefEdgeCommand.setTarget(reference);

			if (referFeature == ProcessesPackage.Literals.ACTION__INPUTS)
			{
				createRefEdgeCommand.setDirection(0);
			}
			else
			{
				createRefEdgeCommand.setDirection(1);
			}

			addEdgesCommand.append(createRefEdgeCommand);
		}

		// return commands to add reference edges
		return addEdgesCommand;
	}


	/**
	 * Creates a command that removes Input/Output Features of the corresponding
	 * action.
	 * 
	 * @param domain
	 * @param owner
	 * @param referFeature
	 * @param refElements
	 * @return
	 */
	private Command createActionInputsOutputsRemoveCommand(EditingDomain domain,
			Action action, EStructuralFeature referFeature, Collection<?> refElements)
	{
		CompoundCommand removeEdgesCommand = new CompoundCommand();

		// for every refElem to be removed
		for (Iterator iterator = refElements.iterator(); iterator.hasNext();)
		{
			ReferenceableElement refElem = (ReferenceableElement) iterator.next();

			// find the refEdge, which should be removed from the action...
			ReferenceEdge refEdge = EMFHelper.getReferenceEdgeForAction(weeditor, action,
					refElem);

			// ...and append remove edge command
			removeEdgesCommand.append(new DeleteCommand(domain, Collections
					.singleton(refEdge)));

			// if refEdge direction was INOUT, restore the OPPOSITE direction
			// (IN or OUT)
			if (refEdge.getDirection() == EdgeDirection.INOUT)
			{
				// CreateReferenceEdgeCommand
				CreateReferenceEdgeCommand createRefEdgeCommand = new CreateReferenceEdgeCommand(
						weeditor, domain);
				createRefEdgeCommand.setSource(refEdge.getAction());
				createRefEdgeCommand.setTarget(refEdge.getReference());

				// skip the canExecute check which would return false
				createRefEdgeCommand.setSkipCanExecute(true);

				// set the direction to the opposite of the removed feature
				if (referFeature == ProcessesPackage.Literals.ACTION__INPUTS)
				{
					createRefEdgeCommand.setDirection(1);
				}
				else
				{
					createRefEdgeCommand.setDirection(0);
				}

				// append the create command
				removeEdgesCommand.append(createRefEdgeCommand);
			}
		}

		// return commands to remove edges
		return removeEdgesCommand;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @seeorg.eclipse.jwt.meta.providers.interfaces.ICommandProvider#
	 * createProviderCommand (java.lang.String,
	 * org.eclipse.emf.edit.domain.EditingDomain, java.lang.Object,
	 * org.eclipse.emf.ecore.EStructuralFeature, java.lang.Object)
	 */
	public Command createProviderCommand(String commandType, EditingDomain domain,
			Object owner, EStructuralFeature feature, Object data)
	{
		if (commandType.equals(ICommandProvider.CT_SCOPE_ACTIVITYNODE_REMOVE))
		{
			return new WERemoveActivityNodeCommand(weeditor, domain, (EObject) owner,
					feature, (Collection) data);
		}
		else if (commandType.equals(ICommandProvider.CT_SCOPE_ACTIVITYEDGE_REMOVE))
		{
			return new RemoveActivityEdgeCommand(domain, (EObject) owner, feature,
					(Collection) data);
		}
		else if (commandType.equals(ICommandProvider.CT_PACKAGE_ELEMENTS_REMOVE))
		{
			return new WERemovePackageElementsCommand(weeditor, domain, (EObject) owner, feature,
					(Collection) data);
		}
		else if (commandType.equals(ICommandProvider.CT_PACKAGE_SUBPACKAGES_REMOVE))
		{
			return new WERemovePackageSubpackagesCommand(weeditor, domain, (EObject) owner, feature,
					(Collection) data);
		}
		else if (commandType.equals(ICommandProvider.CT_ACTION_EXECUTEDBYPERFORMEDBY_SET))
		{
			return createActionExecutedByPerformedBySetCommand(domain, (Action) owner,
					feature, data);
		}
		else if (commandType.equals(ICommandProvider.CT_ACTION_INPUTSOUTPUTS_ADD))
		{
			return createActionInputsOutputsAddCommand(domain, (Action) owner, feature,
					(Collection) data);
		}
		else if (commandType.equals(ICommandProvider.CT_ACTION_INPUTSOUTPUTS_REMOVE))
		{
			return createActionInputsOutputsRemoveCommand(domain, (Action) owner,
					feature, (Collection) data);
		}

		return null;
	}


	public Command createCommand(AdapterFactoryEditingDomain domain, Class commandClass,
			CommandParameter commandParameter)
	{
		// delegate to custom CutToClipboardCommand (cuts and copies also the
		// edges)
		if (commandClass == CutToClipboardCommand.class)
		{
			return new WECutToClipboardCommand(weeditor, domain, RemoveCommand.create(
					domain, commandParameter.getOwner(), commandParameter.getFeature(),
					commandParameter.getCollection()));
		}
		// delegate to custom CopyToClipboardCommand (copies also the edges)
		if (commandClass == CopyToClipboardCommand.class)
		{
			return new WECopyToClipboardCommand(weeditor, domain, commandParameter
					.getCollection());
		}
		// delegate to custom PasteFromClipboardCommand (moves objects before
		// pasting)
		if (commandClass == PasteFromClipboardCommand.class)
		{
			return new WEPasteFromClipboardCommand(weeditor, domain, commandParameter
					.getOwner(), commandParameter.getFeature(), commandParameter
					.getIndex(), domain.getOptimizeCopy());
		}
		// delegate to custom DeleteCommand (checks if elements are still
		// referenced)
		else if (commandClass == DeleteCommand.class)
		{
			return new WEDeleteCommand(weeditor, domain, commandParameter.getCollection());
		}
		// delegate to custom DragAndDropCommand (no d'n'd if refedges are
		// involved)
		else if (commandClass == DragAndDropCommand.class)
		{
			DragAndDropCommand.Detail detail = (DragAndDropCommand.Detail) commandParameter
					.getFeature();
			return new WEDragAndDropCommand(weeditor, domain,
					commandParameter.getOwner(), detail.location, detail.operations,
					detail.operation, commandParameter.getCollection());
		}

		return null;
	}

}
