/******************************************************************************
 * Copyright (c) 2006, Intalio Inc.
 * 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:
 *     Intalio Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.stp.bpmn.policies;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.GraphicalEditPolicy;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.EditPolicyRoles;
import org.eclipse.gmf.runtime.diagram.ui.requests.DropObjectsRequest;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.stp.bpmn.diagram.part.BpmnDiagramEditorPlugin;
import org.eclipse.stp.bpmn.diagram.ui.PopupBalloon;
import org.eclipse.stp.bpmn.dnd.IGenericEAnnotationDndHandler;

/**
 * Policy to handle the drop of EAnnotations on a EModelElement.
 * 
 * @author atoulme
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 */
public class GenericEAnnotationDropPolicy extends GraphicalEditPolicy {

	private EditPolicy previousEditPolicy;
	
	public GenericEAnnotationDropPolicy(IGraphicalEditPart part) {
		EditPolicy policy = part.getEditPolicy(EditPolicyRoles.DRAG_DROP_ROLE);
		if (policy != null) {
			previousEditPolicy = policy;
		}
	}
	/**
	 * HashMap holding the annotations for the request,
	 * so that they are not computed several times.
	 * Note that this is a WeakHashMap, 
	 * so the requests are disposed by the grabage collector.
	 */
	private Map<Request,List<EAnnotation>> requestAnnotations = 
		new WeakHashMap<Request, List<EAnnotation>>(); 
	/**
	 * The balloon used for the feedback.
	 */
	private static PopupBalloon _balloon;
	
	/**
	 * the messages carried as popup descriptors.
	 */
	private List<IStatus> _messages = 
		new LinkedList<IStatus>();
	/* (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.AbstractEditPolicy#getTargetEditPart(org.eclipse.gef.Request)
	 */
	@Override
	public EditPart getTargetEditPart(Request request) {
		if (understandsRequest(request)) {
			return getHost();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.AbstractEditPolicy#understandsRequest(org.eclipse.gef.Request)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean understandsRequest(Request req) {
		if (req instanceof DropObjectsRequest) {
			List objects = ((DropObjectsRequest) req).getObjects();
			if (objects == null||objects.isEmpty()) {
				return delegateUnderstandsRequest(req);
			}
			List<EAnnotation> annotations = retrieveAnnotations(objects);
			if (annotations == null) {
				return delegateUnderstandsRequest(req);
			}
			boolean accepted = accept(annotations,
					(DropObjectsRequest) req);
			if (accepted) {
				requestAnnotations.put(req,annotations);
				return true;
			}
		}
		return delegateUnderstandsRequest(req);
	}

	private boolean delegateUnderstandsRequest(Request req) {
		if (previousEditPolicy != null) {
			return previousEditPolicy.understandsRequest(req);
		}
		return false;
	}
	
	private Command delegateGetCommand(Request req) {
		if (previousEditPolicy != null) {
			return previousEditPolicy.getCommand(req);
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	private List<EAnnotation> retrieveAnnotations(List objects) {
		List<EAnnotation> annotations = new LinkedList<EAnnotation>();
		for (Object dropped : objects) {
			Object ann =Platform.getAdapterManager().
				getAdapter(dropped, EAnnotation.class);
			if (dropped instanceof EAnnotation) {
				ann = dropped;
			}
			if (ann == null || (!(ann instanceof EAnnotation))) {
				return null;
			}
			annotations.add((EAnnotation) ann);
		}
		return annotations;
	}

	@SuppressWarnings("unchecked")
	private boolean accept(List<EAnnotation> annotations,
			DropObjectsRequest request) {
		_messages.clear();
		boolean doit = true;
		for (EAnnotation annotation:annotations) {
			IGenericEAnnotationDndHandler handler =BpmnDiagramEditorPlugin.
			getInstance().
			getEAnnotationDragAndDropHandler(annotation.getSource());
			if (handler == null) {
				throw new IllegalArgumentException("No extension point is " +
						"defined for the annotation source " + 
						annotation.getSource());
			}
			IStatus status = handler.accept(annotation, 
					(IGraphicalEditPart) getHost());

			if (status.getSeverity() != IStatus.OK) {
				doit=false;

			} 

			if (status.getMessage()!= null&&status.getMessage().length() != 0) {
				_messages.add(status);
			}
		}
		if (!_messages.isEmpty()) {
			if (_balloon == null) {
				_balloon = new PopupBalloon(
						(IGraphicalEditPart) getHost());
			} 
			if (_balloon.isShowing() &&
					!(_balloon.showsOnThisEditPart((IGraphicalEditPart) 
							getHost()))) {
				_balloon.hide();
			}
			if (!_balloon.isShowing()) {
				for (IStatus descriptor:_messages){

					_balloon.addPopupBarDescriptor(descriptor.getMessage(),
							descriptor.getSeverity());
				}
				_balloon.showBalloon(request.getLocation());
			}
		}
		return doit;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.AbstractEditPolicy#getCommand(org.eclipse.gef.Request)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Command getCommand(Request request) {
		if (!understandsRequest(request) || 
				requestAnnotations.get(request) == null) {
			return delegateGetCommand(request);
		}
		List<EAnnotation> annotations = requestAnnotations.get(request);
		if (getHost().getModel() instanceof View &&
				((View)getHost().getModel()).getElement()
				instanceof EModelElement) {
			EModelElement modelElement =
				(EModelElement) ((View)getHost().getModel()).getElement();

			final ICommand gmfco = new AddAnnotationCommand(
					modelElement,annotations);

			Command co = new Command() {

				/* (non-Javadoc)
				 * @see org.eclipse.gef.commands.Command#execute()
				 */
				@Override
				public void execute() {

					try {
						gmfco.execute(null, null);
					} catch (ExecutionException e) {
						BpmnDiagramEditorPlugin.getInstance().getLog().
							log(new Status(IStatus.ERROR,
									BpmnDiagramEditorPlugin.ID,IStatus.ERROR,
									"Error dropping the annotation",e));
					}

				}
				public void undo() {

					try {
						gmfco.undo(null, null);
					} catch (ExecutionException e) {
						BpmnDiagramEditorPlugin.getInstance().getLog().
						log(new Status(IStatus.ERROR,
								BpmnDiagramEditorPlugin.ID,IStatus.ERROR,
								"Error dropping the annotation",e));
					}

				}
				public void redo() {

					try {
						gmfco.redo(null, null);
					} catch (ExecutionException e) {
						BpmnDiagramEditorPlugin.getInstance().getLog().
						log(new Status(IStatus.ERROR,
								BpmnDiagramEditorPlugin.ID,IStatus.ERROR,
								"Error dropping the annotation",e));
					}

				}};
				co.setLabel("DnD annotation");
				
				return co;
		} else {
			// the view is associated with something that is not an EModelElement.
			// does nothing.
		}
		return delegateGetCommand(request);
	}

	/**
	 * Command to wrap the EMF command, with redo and undo ability.
	 * @author <a href="mailto:atoulme@intalio.com">Antoine Toulmé</a>
	 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
	 */
	private class AddAnnotationCommand extends AbstractTransactionalCommand {

		private EModelElement element;
		private List<EAnnotation> annotationsToAdd;
		private CompoundCommand emfCommand;
		
		public AddAnnotationCommand(
				EModelElement elt,List<EAnnotation> annotationsToAdd) {
			super((TransactionalEditingDomain) AdapterFactoryEditingDomain.
					getEditingDomainFor(elt),
					"DnD",
					getWorkspaceFiles(elt));
			element = elt;
			this.annotationsToAdd = annotationsToAdd;
			initCommand();
		}
		
		private void initCommand() {
			emfCommand = new CompoundCommand();
			EditingDomain domain = AdapterFactoryEditingDomain.
			getEditingDomainFor(element);
			for (EAnnotation annotation : annotationsToAdd) {
				IGenericEAnnotationDndHandler handler =
					BpmnDiagramEditorPlugin.getInstance().
					getEAnnotationDragAndDropHandler(
							((EAnnotation) annotation).getSource());
				if (handler == null) {
					throw new IllegalArgumentException(
							"No extension point is defined for the " +
							"annotation source " + 
							((EAnnotation) annotation).getSource());
				}
				if (handler.doPerformDrop(
								emfCommand,(EAnnotation) annotation, element)) {

					emfCommand.append(
							SetCommand.create(domain, annotation, 
									EcorePackage.
									eINSTANCE.getEAnnotation_EModelElement(),
									element));
				}
			}
		}
		protected CommandResult doExecuteWithResult(
				IProgressMonitor monitor, IAdaptable info)
		throws ExecutionException {

			emfCommand.execute();
			if (_balloon != null && _balloon.isShowing()) {
				_balloon.hide();
			}
			return CommandResult.newOKCommandResult();
		}

		/* (non-Javadoc)
		 * @see org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand#doUndo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable)
		 */
		@Override
		protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info) 
		throws ExecutionException {
			emfCommand.undo();
			return super.doUndo(monitor, info);
		}

		/* (non-Javadoc)
		 * @see org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand#doRedo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable)
		 */
		@Override
		protected IStatus doRedo(IProgressMonitor arg0, IAdaptable arg1) 
		throws ExecutionException {
			emfCommand.redo();
			return super.doRedo(arg0, arg1);
		}
	}
}
