/******************************************************************************
 * 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
 *******************************************************************************/

/**
 * Date             Author              Changes
 * Jul 17, 2006     hmalphettes         Created
 **/

package org.eclipse.stp.bpmn.clipboard;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.gmf.runtime.emf.clipboard.core.ClipboardSupportUtil;
import org.eclipse.gmf.runtime.emf.clipboard.core.CopyOperation;
import org.eclipse.gmf.runtime.emf.clipboard.core.OverrideCopyOperation;
import org.eclipse.gmf.runtime.emf.clipboard.core.OverridePasteChildOperation;
import org.eclipse.gmf.runtime.emf.clipboard.core.PasteAction;
import org.eclipse.gmf.runtime.emf.clipboard.core.PasteChildOperation;
import org.eclipse.gmf.runtime.emf.clipboard.core.PasteOption;
import org.eclipse.gmf.runtime.emf.core.clipboard.AbstractClipboardSupport;
import org.eclipse.gmf.runtime.emf.type.core.commands.DestroyElementCommand;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.stp.bpmn.Identifiable;
import org.eclipse.stp.bpmn.Lane;
import org.eclipse.stp.bpmn.MessagingEdge;
import org.eclipse.stp.bpmn.Pool;
import org.eclipse.stp.bpmn.SequenceEdge;
import org.eclipse.stp.bpmn.SubProcess;

/**
 * Following the advice of the Q&amp;A on GMF wiki.
 * This is a fork of the NotationClipboardOperationHelper.
 * 
 * @author hmalphettes
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 */
public class BpmnClipboardSupport extends AbstractClipboardSupport {

    public BpmnClipboardSupport() {
    }

    public void destroy(EObject eObject) {
        DestroyElementCommand.destroy(eObject);
    }
    
    /**
     * By default, there are no collisions in pasting.
     * 
     * @return the {@link PasteAction#ADD}action, always
     */
    public PasteAction getPasteCollisionAction(EClass eClass) {
        return PasteAction.ADD;
    }

    /**
     * By default, the following paste options are supported:
     * <ul>
     * <li>{@link PasteOption#NORMAL}: always</li>
     * <li>{@link PasteOption#PARENT}: never</li>
     * <li>{@link PasteOption#DISTANT}: if and only only if the
     * <code>eStructuralFeature</code> is a
     * {@link org.eclipse.gmf.runtime.notation.View}'s reference to its semantic
     * {@linkplain org.eclipse.gmf.runtime.notation.View#getElement() element}</li>
     * </ul>
     */
    public boolean hasPasteOption(EObject contextEObject,
            EStructuralFeature eStructuralFeature, PasteOption pasteOption) {
//        System.err.println("HasPasteOption is called ctxt=" + contextEObject + 
//                "   eStructuralFeature=" + eStructuralFeature + "   option=" + pasteOption);
        if (pasteOption.equals(PasteOption.NORMAL)) {
            return true;
        } else if (pasteOption.equals(PasteOption.PARENT)) {
            //disable the copy-parent functionality completely.
            return false;
        } else if (pasteOption.equals(PasteOption.DISTANT)) {
            if (eStructuralFeature == null) {
                return false;
            } else {
                return NotationPackage.eINSTANCE.getView_Element().equals(
                    eStructuralFeature);
            }
        } else {
            return false;
        }
    }

    /**
     * By default, transient and derived references are never copied, and
     * containment references and the
     * {@linkplain org.eclipse.gmf.runtime.notation.View#getElement() element}reference
     * always are copied.
     */
    public boolean isCopyAlways(EObject context, EReference eReference,
            Object value) {
//        System.err.println("isCopyAlways " + eReference);
        if ((eReference.isTransient()) || (eReference.isDerived())) {
            return false;
        } else if (eReference.equals(NotationPackage.eINSTANCE
                        .getView_Element())) {
            //System.err.println(context + " isCopyAlways " + value + " true");
            return true;
        } else if (context instanceof Identifiable) {
            if (eReference.isContainment()) {
                //System.err.println(context + " isCopyAlways " + eReference.eContents() + " true (containment)");
                return true;
            }
            //probably some special things to do for
            //messaging edges and sequence edges
            System.err.println(context + " isCopyAlways " + eReference + " false (not containment)");
            return false;
        } else {
            return eReference.isContainment();
        }
    }

    /**
     * By default, don't provide any child paste override behaviour.
     */
    public boolean shouldOverrideChildPasteOperation(EObject parentElement,
            EObject childEObject) {
        System.err.println("shouldOverrideChildPasteOperation parent=" + parentElement +
                ", child=" + childEObject);
        return (childEObject.eClass().getEPackage() == NotationPackage.eINSTANCE);
    }

    /**
     * By default, don't provide any copy override behaviour.
     */
    public boolean shouldOverrideCopyOperation(Collection eObjects, Map hintMap) {
        return false;
    }

    private boolean shouldAllowPaste(
            PasteChildOperation overriddenChildPasteOperation) {
        EObject eObject = overriddenChildPasteOperation.getEObject();
        EObject parentEObject = overriddenChildPasteOperation
            .getParentEObject();
        
        //support for pasting into pool compartment and sub-process body compartment
        //and sub-process event handlers compartments.
        if (eObject instanceof View && parentEObject instanceof Node) {
            Node parentNode = (Node)parentEObject;
            EObject semanticChildElement = ((View) eObject).getElement();
            
            //TODO: somehing better if possible using pure EMF concepts (?):
            //in theory the BPMN schema describes well enough the containments constraints
            //so it should be possible.
            if (semanticChildElement instanceof Pool) {
                //a pool can only be pasted inside a Diagram
                return false;
            }
            if (semanticChildElement instanceof MessagingEdge
                    || semanticChildElement instanceof SequenceEdge) {
                //relationship cannot be copied and pasted without their source 
                //and target.
                return false;
            }
            EObject semanticParent = parentNode.getElement();
            if (semanticChildElement instanceof Lane && 
                    !(semanticParent instanceof Pool)) {
                //a lane can only be pasted inside a Pool
                return false;
            }
            if (!(semanticParent instanceof Pool 
                    || semanticParent instanceof SubProcess)) {
                //can only paste into a pool and into a sub-process
                return false;
            }
            System.err.println("return true for paste");
            return true;
        }
        if ((parentEObject instanceof Diagram) && (eObject instanceof View)) {
            EObject semanticChildElement = ((View) eObject).getElement();
            if (semanticChildElement == null) {
                return true;
            }
            if (semanticChildElement.eIsProxy()) {
                semanticChildElement = ClipboardSupportUtil.resolve(
                    semanticChildElement, overriddenChildPasteOperation
                        .getParentPasteProcess().getLoadedIDToEObjectMapCopy());
                if (semanticChildElement.eIsProxy()) {
                    semanticChildElement = EcoreUtil.resolve(
                        semanticChildElement, getResource(parentEObject));
                }
            }

            EPackage semanticChildEpackage = semanticChildElement.eClass()
                .getEPackage();
            EPackage diagramRootContainerEpackage = EcoreUtil.getRootContainer(
                parentEObject).eClass().getEPackage();
            EPackage sematicDiagramRootContainerEpackage = null;
            EObject sematicDiagramElement = ((View) parentEObject).getElement();
            if (sematicDiagramElement != null) {
                sematicDiagramRootContainerEpackage = EcoreUtil
                    .getRootContainer(sematicDiagramElement).eClass()
                    .getEPackage();
            }

            if (diagramRootContainerEpackage != NotationPackage.eINSTANCE) {
                if (semanticChildEpackage != diagramRootContainerEpackage) {
                    return false;
                }
            }

            if ((sematicDiagramRootContainerEpackage != null)
                && (sematicDiagramRootContainerEpackage != NotationPackage.eINSTANCE)) {
                if (semanticChildEpackage != sematicDiagramRootContainerEpackage) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * By default, don't provide any child paste override behaviour.
     * 
     * @return <code>null</code>
     */
    public OverridePasteChildOperation getOverrideChildPasteOperation(
            PasteChildOperation overriddenChildPasteOperation) {
        if (shouldAllowPaste(overriddenChildPasteOperation)) {
            EObject eObject = overriddenChildPasteOperation.getEObject();
            if (eObject instanceof org.eclipse.gmf.runtime.notation.Node) {
                org.eclipse.gmf.runtime.notation.Node node = (org.eclipse.gmf.runtime.notation.Node) eObject;
                EObject element = node.getElement();
                if ((element != null)) {
                    return new BpmnPositionalGeneralViewPasteOperation(
                        overriddenChildPasteOperation, true);
                } else {
                    return new BpmnPositionalGeneralViewPasteOperation(
                        overriddenChildPasteOperation, false);
                }
            } else if (eObject instanceof Edge) {
                return new BpmnConnectorViewPasteOperation(
                    overriddenChildPasteOperation);
            }
        }
        return null;
    }

    /**
     * By default, don't provide any copy override behaviour.
     * 
     * @return <code>null</code>, always
     */
    public OverrideCopyOperation getOverrideCopyOperation(
            CopyOperation overriddenCopyOperation) {
        return null;
    }

    /**
     * Exclude the connections that source and target are not included in 
     * this set: basically excludes the connections.
     * 
     * @return the set of edges found in here.
     */
    public Collection getExcludedCopyObjects(Set eObjects) {
//        Set<Object> toRemove = new HashSet<Object>();
//        for (Object e : eObjects) {
//            //if (e instanceof MessagingEdge || e instanceof SequenceEdge
//            if (e instanceof Edge) {
//                //TODO: more about this.
//                toRemove.add(e);
//            }
//        }
//        return toRemove;
        return Collections.EMPTY_SET;
    }

    /**
     * By default, just get the resource that contains the object.
     */
    public XMLResource getResource(EObject eObject) {
        XMLResource eResource = (XMLResource) eObject.eResource();
        if (eResource == null) {
            if (eObject instanceof View) {
                EObject element = ((View) eObject).getElement();
                if ((element != null)) {
                    return (XMLResource) element.eResource();
                }
            }
        }
        return eResource;
    }

    /**
     * By default, we always copy all contents of an object.
     * 
     * @return <code>true</code>
     */
    public boolean shouldSaveContainmentFeature(EObject eObj) {
        if (EcorePackage.eINSTANCE.getEClassifiers().contains(eObj.eClass())) {
            return false;
        }
        try {
            eObj.eResource().getURIFragment(eObj);
        } catch (Exception ex) {
            return false;
        }
        return true;
    }

    /**
     * Makes sure that the copied semantic objects are in the right container.
     */
    public void performPostPasteProcessing(Set pastedEObjects) {
        // nothing to do
        for (Object o : pastedEObjects) {
            if (o instanceof View) {
                System.err.println("pastedEObject=" + o);
                View copiedView = (View)o;
            }
        }
    }
    
    
//[hugues] the name for the clipboard is the uuid.
//by default it will consider that the uuid is the 'name' eattribute
    @Override
    public String getName(EObject eObject) {
        if (eObject instanceof Identifiable) {
            return ((Identifiable)eObject).getID();
        }
        return super.getName(eObject);
    }

    @Override
    public void setName(EObject eObject, String name) {
        if (eObject instanceof Identifiable) {
            ((Identifiable)eObject).setID(EcoreUtil.generateUUID());
        } else {
            super.setName(eObject, name);
        }
    }

    static Diagram getContainingDiagram(View view) {
        EObject current = view;
        while (current != null) {
            if (current instanceof Diagram) {
                return (Diagram) current;
            }
            current = current.eContainer();
        }
        return null;
    }

    static EObject getSemanticPasteTarget(View view) {
        View parent = (View) view.eContainer();
        System.err.println("pasting in " + parent);
        return parent.getElement();
    }

}
