/******************************************************************************
 * 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.diagram.edit.parts;

import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gef.CompoundSnapToHelper;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.SnapToGeometry;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.SnapToGuides;
import org.eclipse.gef.SnapToHelper;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.rulers.RulerProvider;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.diagram.ui.commands.SetBoundsCommand;
import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeCompartmentEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.CreationEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.DragDropEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.EditPolicyRoles;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.ResizableCompartmentEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.figures.ResizableCompartmentFigure;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateUnspecifiedTypeConnectionRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor;
import org.eclipse.gmf.runtime.draw2d.ui.figures.ConstrainedToolbarLayout;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.stp.bpmn.Activity;
import org.eclipse.stp.bpmn.diagram.edit.policies.SubProcessCreationEditPolicy;
import org.eclipse.stp.bpmn.diagram.edit.policies.SubProcessSubProcessBodyCompartmentCanonicalEditPolicy;
import org.eclipse.stp.bpmn.diagram.edit.policies.SubProcessSubProcessBodyCompartmentItemSemanticEditPolicy;
import org.eclipse.stp.bpmn.policies.GenericEAnnotationDropPolicy;
import org.eclipse.stp.bpmn.policies.PopupBarEditPolicyEx;
import org.eclipse.stp.bpmn.policies.ResizableCompartmentEditPolicyEx;
import org.eclipse.stp.bpmn.tools.TaskDragHelper;

/**
 * @generated
 */
public class SubProcessSubProcessBodyCompartmentEditPart extends
        ShapeCompartmentEditPart {

    /**
     * @generated
     */
    public static final int VISUAL_ID = 5002;

    /**
     * @notgenerated
     */
    private static final int BORDER_INSET = 5;

    /**
     * @generated
     */
    public SubProcessSubProcessBodyCompartmentEditPart(View view) {
        super(view);
    }

    /**
     * @generated
     */
    public String getCompartmentNameGen() {
        return "SubProcessBodyCompartment";
    }

    /**
     * @notgenerated
     */
    public String getCompartmentName() {
        EObject bpmnObj = getPrimaryView().getElement();
        if (bpmnObj instanceof Activity) {
            Activity a = (Activity) bpmnObj;
            if (a.getName() != null) {
                return "SubProcess " + a.getName();
            }
        }
        return "SubProcess";
    }

    /**
     * @generated
     */
    public IFigure createFigureGen() {
        ResizableCompartmentFigure result = (ResizableCompartmentFigure) super
                .createFigure();
        result.setTitleVisibility(false);
        return result;
    }

    /**
     * @notgenerated
     */
    public IFigure createFigure() {
        ResizableCompartmentFigure result = (ResizableCompartmentFigure) this
                .createFigureGen();
        result.setBorder(null);
        return result;
    }

    /**
     * @generated
     */
    protected void createDefaultEditPoliciesGen() {
        super.createDefaultEditPolicies();
        installEditPolicy(EditPolicy.PRIMARY_DRAG_ROLE,
                new ResizableCompartmentEditPolicy());
        installEditPolicy(EditPolicyRoles.SEMANTIC_ROLE,
                new SubProcessSubProcessBodyCompartmentItemSemanticEditPolicy());
        installEditPolicy(EditPolicyRoles.CREATION_ROLE,
                new CreationEditPolicy());
        installEditPolicy(EditPolicyRoles.DRAG_DROP_ROLE,
                new DragDropEditPolicy());
        installEditPolicy(EditPolicyRoles.CANONICAL_ROLE,
                new SubProcessSubProcessBodyCompartmentCanonicalEditPolicy());
    }

    /**
     * @notgenerated
     */
    protected void createDefaultEditPolicies() {
        createDefaultEditPoliciesGen();
        // the following is added:
        installEditPolicy(EditPolicyRoles.POPUPBAR_ROLE,
                new PopupBarEditPolicyEx());
        removeEditPolicy(EditPolicy.PRIMARY_DRAG_ROLE);
        installEditPolicy(EditPolicy.PRIMARY_DRAG_ROLE,
                new ResizableCompartmentEditPolicyEx());
        removeEditPolicy(EditPolicyRoles.CANONICAL_ROLE);
        installEditPolicy(EditPolicyRoles.CANONICAL_ROLE,
            new SubProcessSubProcessBodyCompartmentCanonicalEditPolicy() {

                /**
                 * Center generated task figure within subprocesscompartment
                 * figure
                 */
                @Override
                protected CreateViewRequest getCreateViewRequest(
                        List descriptors) {
                    CreateViewRequest createViewRequest = new CreateViewRequest(
                            descriptors);

                    List viewDescriptors = createViewRequest
                            .getViewDescriptors();
                    for (Object o : viewDescriptors) {
                        ViewDescriptor vd = (ViewDescriptor) o;
                        if (vd.getSemanticHint() == null)//
                        {
                            Point point = new Point(0, 0);
                            getFigure().translateToAbsolute(point);
                            // point = new Point(point.x, point.y);
                            int x = (SubProcessEditPart.EXPANDED_SIZE.width
                                    - ActivityEditPart.ACTIVITY_FIGURE_SIZE.width - BORDER_INSET * 2) / 2;
                            int y = 10;
                            createViewRequest.setLocation(new Point(point.x
                                    + x, point.y + y));
                        }
                    }

                    return createViewRequest;
                }
            });
        removeEditPolicy(EditPolicyRoles.CREATION_ROLE);
        installEditPolicy(EditPolicyRoles.CREATION_ROLE,
                new SubProcessCreationEditPolicy());

        // adding default drag and drop edit policy
        installEditPolicy(EditPolicyRoles.DRAG_DROP_ROLE,
                new GenericEAnnotationDropPolicy(this));
    }

    /**
     * @generated
     */
    protected void setRatio(Double ratio) {
        if (getFigure().getParent().getLayoutManager() instanceof ConstrainedToolbarLayout) {
            super.setRatio(ratio);
        }
    }

    /**
     * @notgenerated
     */
    protected void setCollapsed(boolean collapsed, boolean animate) {
        processConnections(this);
        super.setCollapsed(collapsed, animate);
    }

    /**
     * Calls refresh() on the specified edit part's connections. This method is
     * called recursively also for all children of the specified edit part.
     * 
     * @param editPart
     *            the edit part who's children connections should be refreshed.
     */
    private static void processConnections(GraphicalEditPart editPart) {
        List children = editPart.getChildren();
        for (Object obj : children) {
            GraphicalEditPart childEditPart = (GraphicalEditPart) obj;
            processConnections(childEditPart);
        }
        List connections = editPart.getSourceConnections();
        for (Object object : connections) {
            ConnectionEditPart connection = (ConnectionEditPart) object;
            connection.refresh();
        }
        connections = editPart.getTargetConnections();
        for (Object object : connections) {
            ConnectionEditPart connection = (ConnectionEditPart) object;
            connection.refresh();
        }
    }

    /**
     * @notgenerated
     */
    @Override
    protected void handleNotificationEvent(Notification event) {
        Object feature = event.getFeature();
        if (NotationPackage.eINSTANCE.getDrawerStyle_Collapsed()
                .equals(feature)) {
            handleCollapseExpand();
        }

        super.handleNotificationEvent(event);
    }

    /**
     * Handles collapse/expands events. Changes the size of the subprocess and
     * layouts sibling figure.
     * 
     */
    private void handleCollapseExpand() {
        final String SUBPROCESS_ANNOTATION_SOURCE = "SubProcess_Annotation";
        final String WIDTH = "width";
        final String HEIGHT = "height";
        boolean isCollapsed = ((Boolean) getStructuralFeatureValue(NotationPackage.eINSTANCE
                .getDrawerStyle_Collapsed())).booleanValue();

        // Retrieve current bounds from the model
        SubProcessEditPart subProcessEditPart = (SubProcessEditPart) getParent();
        int oldX = ((Integer) subProcessEditPart
                .getStructuralFeatureValue(NotationPackage.eINSTANCE
                        .getLocation_X())).intValue();
        int oldY = ((Integer) subProcessEditPart
                .getStructuralFeatureValue(NotationPackage.eINSTANCE
                        .getLocation_Y())).intValue();
        int oldWidth = ((Integer) subProcessEditPart
                .getStructuralFeatureValue(NotationPackage.eINSTANCE
                        .getSize_Width())).intValue();
        int oldHeight = ((Integer) subProcessEditPart
                .getStructuralFeatureValue(NotationPackage.eINSTANCE
                        .getSize_Height())).intValue();
        if (oldWidth == -1) {
            oldWidth = SubProcessEditPart.EXPANDED_SIZE.width;
        }
        if (oldHeight == -1) {
            oldHeight = SubProcessEditPart.EXPANDED_SIZE.height;
        }

        // Retrieve previously saved bounds from the diagram
        View view = subProcessEditPart.getPrimaryView();
        EAnnotation annotation = view
                .getEAnnotation(SUBPROCESS_ANNOTATION_SOURCE);

        if (annotation == null) {
            annotation = EcoreFactory.eINSTANCE.createEAnnotation();
            annotation.setSource(SUBPROCESS_ANNOTATION_SOURCE);
            view.getEAnnotations().add(annotation);
        }
        EMap details = annotation.getDetails();
        // obtain prevoius width and height from annotation
        String width = (String) details.get(WIDTH);
        String height = (String) details.get(HEIGHT);

        // Calculate new bounds
        Dimension minimalDim = subProcessEditPart.calcMinSize();

        int newWidth;
        int newHeight;

        if (width == null || height == null) {
            // no previous record in annotation
            if (isCollapsed) {
                newWidth = Math.max(SubProcessEditPart.COLLAPSED_SIZE.width,
                        minimalDim.width);
                newHeight = Math.max(SubProcessEditPart.COLLAPSED_SIZE.height,
                        minimalDim.height);
            } else {
                newWidth = Math.max(SubProcessEditPart.EXPANDED_SIZE.width,
                        minimalDim.width);
                newHeight = Math.max(SubProcessEditPart.EXPANDED_SIZE.height,
                        minimalDim.height);
            }
        } else {
            newWidth = Math.max(minimalDim.width, Integer.parseInt(width));
            newHeight = Math.max(minimalDim.height, Integer.parseInt(height));
        }

        // Save new bounds in the diagram
        details.put(WIDTH, Integer.toString(oldWidth));
        details.put(HEIGHT, Integer.toString(oldHeight));

        // Apply new bounds if something changed
        if (newWidth != oldWidth || newHeight != oldHeight) {
            Rectangle subprocessBounds = new Rectangle(oldX, oldY, newWidth,
                    newHeight);
            setNewBounds(subprocessBounds, new Dimension(oldWidth, oldHeight),
                    isCollapsed);
            if (!isCollapsed) {
                // subProcessEditPart.setChildAdded(true);
                subProcessEditPart.refresh();
            }
        }
    }

    /**
     * Sets new bounds on parent subprocess and layouts sibling figures.
     * Sibling figures that lay to the right and bottom from parent subprocess
     * are shifted to the right and to the bottom on expand and to the left and
     * to the top on collapse.
     * 
     * @param newBounds
     *            the new bounds of subprocess.
     * @param oldSize
     *            old size of subprocess.
     */
    private void setNewBounds(Rectangle newBounds, Dimension oldSize,
            boolean isCollapsed) {
        SubProcessEditPart subProcessEditPart = (SubProcessEditPart) getParent();
        CompositeCommand compoudCommand = new CompositeCommand(
                "Set new bounds on collapse/expand");
        TransactionalEditingDomain editingDomain = getEditingDomain();
        List siblings = subProcessEditPart.getParent().getChildren();
        int dx = newBounds.width - oldSize.width;
        int dy = newBounds.height - oldSize.height;
        int prevLeft = newBounds.x;
        int prevTop = newBounds.y;

        Rectangle newTotalBounds = new Rectangle(newBounds);
        for (Object sibling : siblings) {
            GraphicalEditPart siblingEditPart = (GraphicalEditPart) sibling;
            if (siblingEditPart != subProcessEditPart) {
                Rectangle currSiblingBounds = siblingEditPart.getFigure()
                        .getBounds().getCopy();
                Rectangle bounds = currSiblingBounds.getCopy();
                boolean changed = false;

                if (dx != 0 && currSiblingBounds.x > prevLeft
                // //if it is above the current shape don't move anyways
                // && currSiblingBounds.y + currSiblingBounds.height > prevTop +
                // 4
                // && currSiblingBounds.y + currSiblingBounds.height >=
                // newBounds.y
                ) {
                    int xchanged = bounds.x + dx;
                    bounds.x = xchanged > prevLeft ? xchanged : prevLeft + 1;
                    // avoid going back further than the left of
                    // the shape that is collapsed.
                    // otherwise when expanded again, it will not be moved
                    // anymore.
                    changed = true;
                }
                if (dy != 0 && currSiblingBounds.y > prevTop
                // //if it is on the left, don't move anyways.
                // && currSiblingBounds.x < prevLeft
                // && currSiblingBounds.x + currSiblingBounds.width >=
                // newBounds.x
                ) {
                    int ychanged = bounds.y + dy;
                    // avoid going up further than the top of
                    // the shape that is collapsed.
                    // otherwise when expanded again, it will not be moved
                    // anymore.
                    bounds.y = ychanged > prevTop ? ychanged : prevTop + 1;
                    changed = true;
                }
                if (changed) {
                    SetBoundsCommand setBoundsCommand = new SetBoundsCommand(
                            editingDomain, "Set bounds", siblingEditPart,
                            bounds);
                    compoudCommand.add(setBoundsCommand);
                    newTotalBounds.union(bounds);
                }
            } else {
                SetBoundsCommand setBoundsCommand = new SetBoundsCommand(
                        editingDomain, "Set bounds", subProcessEditPart,
                        newBounds);
                compoudCommand.add(setBoundsCommand);
            }
        }
        try {
            compoudCommand.execute(new NullProgressMonitor(), null);
            if (!isCollapsed) {
                GraphicalEditPart parentContainer = (GraphicalEditPart) getParent()
                        .getParent().getParent();
                Rectangle prevBounds = parentContainer.getFigure().getBounds();
                TaskDragHelper.updateContainerBounds((SubProcessEditPart) this
                        .getParent(), newTotalBounds, false);
                // now do this recursively for all parent subprocesses
                if (parentContainer instanceof SubProcessEditPart) {
                    int newX = ((Integer) parentContainer
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getLocation_X())).intValue();
                    int newY = ((Integer) parentContainer
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getLocation_Y())).intValue();
                    int newWidth = ((Integer) parentContainer
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getSize_Width())).intValue();
                    int newHeight = ((Integer) parentContainer
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getSize_Height())).intValue();

                    Rectangle changedBounds = new Rectangle(newX, newY,
                            newWidth, newHeight);
                    if (!changedBounds.equals(prevBounds)) {
                        SubProcessSubProcessBodyCompartmentEditPart body =
                            (SubProcessSubProcessBodyCompartmentEditPart) parentContainer
                            .getChildBySemanticHint(Integer.toString(
                                 SubProcessSubProcessBodyCompartmentEditPart.VISUAL_ID));
                        body.setNewBounds(changedBounds, prevBounds.getSize(), false);
                    }
                }
            }
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    /**
     * @notgenerated
     */
    protected void handlePropertyChangeEvent(PropertyChangeEvent event) {
        super.handlePropertyChangeEvent(event);
        TaskDragHelper.handlePropertyChangeEvent(event, this);
    }

    /**
     * @notgenerated
     */
    @Override
    protected void addChild(EditPart child, int index) {
        super.addChild(child, index);
        if (child instanceof SubProcessEditPart) {
            ((SubProcessEditPart) child).setNeedToUpdateContainer(true);
        } else if (child instanceof ActivityEditPart) {
            ((ActivityEditPart) child).setChildAdded(true);
        }
    }

    /**
     * @notgenerated this makes sure that visually the primary shape is the one
     *               that has the focus. It is the same code than for all the
     *               container compartments (mpeleshchyshyn)
     */
    @Override
    public void setSelected(int value) {
        if (value == SELECTED_PRIMARY) {
            getViewer().select(getParent());
            return;
        }
        super.setSelected(value);
    }

    @Override
    /**
     * @notgenerated
     */
    public EditPart getTargetEditPart(Request request) {
        EditPart targetEditPart;
        if (request instanceof CreateUnspecifiedTypeConnectionRequest) {
            targetEditPart = getParent();
        } else {
            targetEditPart = super.getTargetEditPart(request);
        }
        return targetEditPart;
    }

    /**
     * Overrides the default: never selectable to always move it when it is
     * dragged. (EDGE-1024)
     */
    public boolean isSelectable() {
        // DrawerStyle style = (DrawerStyle)
        // ((View)getModel()).getStyle(NotationPackage.eINSTANCE.getDrawerStyle());
        // if (style.isCollapsed()) {
        // return super.isSelectable();
        // }
        return false;
    }

    /**
     * @notgenerated Taken from logic example adn comments on bug 78462
     * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class adapter) {
        if (adapter == SnapToHelper.class) {
            List<SnapToHelper> snapStrategies = new ArrayList<SnapToHelper>();
            Boolean val = (Boolean) getViewer().getProperty(
                    RulerProvider.PROPERTY_RULER_VISIBILITY);
            if (val != null && val.booleanValue())
                snapStrategies.add(new SnapToGuides(this));
            val = (Boolean) getViewer().getProperty(
                    SnapToGeometry.PROPERTY_SNAP_ENABLED);
            if (val != null && val.booleanValue())
                snapStrategies.add(new SnapToGeometry(this));
            val = (Boolean) getViewer().getProperty(
                    SnapToGrid.PROPERTY_GRID_ENABLED);
            if (val != null && val.booleanValue())
                snapStrategies.add(new SnapToGrid(this));

            if (snapStrategies.size() == 0)
                return null;
            if (snapStrategies.size() == 1)
                return snapStrategies.get(0);

            SnapToHelper ss[] = new SnapToHelper[snapStrategies.size()];
            for (int i = 0; i < snapStrategies.size(); i++)
                ss[i] = snapStrategies.get(i);
            return new CompoundSnapToHelper(ss);
        }
        return super.getAdapter(adapter);
    }

    @Override
    public Command getCommand(Request _request) {
        // TODO Auto-generated method stub
        return super.getCommand(_request);
    }
}
