/*
 *******************************************************************************
 ** 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 org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ConnectionNodeEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.EditPolicyRoles;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.IdentityAnchor;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.Routing;
import org.eclipse.gmf.runtime.notation.RoutingStyle;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.stp.bpmn.Activity;
import org.eclipse.stp.bpmn.MessagingEdge;
import org.eclipse.stp.bpmn.diagram.edit.policies.MessagingEdgeItemSemanticEditPolicy;
import org.eclipse.stp.bpmn.figures.ConnectionLayerExEx;
import org.eclipse.stp.bpmn.figures.ConnectionUtils;
import org.eclipse.stp.bpmn.figures.connectionanchors.IModelAwareAnchor;
import org.eclipse.stp.bpmn.policies.GenericEAnnotationDropPolicy;

/**
 * @generated
 */
public class MessagingEdgeEditPart extends ConnectionNodeEditPart {

    /**
     * @notgenerated exact observed dashes from previous modeler
     */
    private static final int[] DASHES = { 7, 5 };
    
    /**
     * @generated
     */
    public static final int VISUAL_ID = 3002;

    /**
     * @notgenerated
     */
    private ConnectionRouter rectilinearRouter = null;

    /**
     * Edit part used to create source anchor
     */
    private EditPart srcConnEditPart = null;

    /**
     * Edit part used to create target anchor
     */
    private EditPart trgConnEditPart = null;

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

    /**
     * @generated
     */
    protected void createDefaultEditPoliciesGen() {
        super.createDefaultEditPolicies();
        installEditPolicy(EditPolicyRoles.SEMANTIC_ROLE,
                new MessagingEdgeItemSemanticEditPolicy());
    }
    
    /**
     * @notgenerated
     */
    protected void createDefaultEditPolicies() {
        createDefaultEditPoliciesGen();
     // adding default drag and drop edit policy
        installEditPolicy(EditPolicyRoles.DRAG_DROP_ROLE, 
        		new GenericEAnnotationDropPolicy(this));
    }

    /**
     * Creates figure for this edit part.
     * 
     * Body of this method does not depend on settings in generation model
     * so you may safely remove <i>generated</i> tag and modify it.
     * 
     * @generated
     */
    protected Connection createConnectionFigure() {
        return new ConnectionMessageFigure();
    }

    /**
     * @notgenerated
     */
    public class ConnectionMessageFigure extends
            org.eclipse.gmf.runtime.draw2d.ui.figures.PolylineConnectionEx {

        /**
         * @notgenerated
         */
        private static final int DELTA_ANCHOR_SIZE = 10;

        /**
         * @notgenerated
         */
        private boolean routerIsRectilinear = true;

        /**
         * Added the DASHES and the routing constraint
         * 
         * @notgenerated
         */
        public ConnectionMessageFigure() {
            this.setLineStyle(org.eclipse.draw2d.Graphics.LINE_CUSTOM);
            this.setLineDash(DASHES);
            this.setForegroundColor(org.eclipse.draw2d.ColorConstants.black

            );
            setSourceDecoration(createSourceDecoration());
            setTargetDecoration(createTargetDecoration());
        }

        /**
         * @generated
         */
        private org.eclipse.stp.bpmn.figures.MessagePolylineSourceDecoration createSourceDecoration() {
            org.eclipse.stp.bpmn.figures.MessagePolylineSourceDecoration df = new org.eclipse.stp.bpmn.figures.MessagePolylineSourceDecoration();

            return df;
        }

        /**
         * @generated
         */
        private org.eclipse.stp.bpmn.figures.MessagePolylineTargetDecoration createTargetDecoration() {
            org.eclipse.stp.bpmn.figures.MessagePolylineTargetDecoration df = new org.eclipse.stp.bpmn.figures.MessagePolylineTargetDecoration();

            return df;
        }

        /**
         * @notgenerated
         * 
         * @see org.eclipse.gmf.runtime.draw2d.ui.figures.PolylineConnectionEx#getSmoothPoints()
         */
        @Override
        public PointList getSmoothPoints() {
            PointList smoothPoints;
            if (!routerIsRectilinear) {
                smoothPoints = super.getSmoothPoints();
            } else {
                smoothPoints =
                   ConnectionUtils.getRoundedRectilinearSmoothPoints(
                         true,
                         getPoints(), getSmoothness());
            }
            return smoothPoints;
        }

        @Override
        /**
         * @notgenerated
         */
        public void setVisible(boolean visible) {
            if (isReparented()) {
                visible = true;
            }
            super.setVisible(visible);
        }

    }
    /**
     * @notgenerated
     */
    @SuppressWarnings("unchecked")
    protected void installRouter() {
        ConnectionLayer cLayer = (ConnectionLayer) getLayer(LayerConstants.CONNECTION_LAYER);
        RoutingStyle style = (RoutingStyle) ((View) getModel())
                .getStyle(NotationPackage.eINSTANCE.getRoutingStyle());

        if (style != null && cLayer instanceof ConnectionLayerExEx) {
            ConnectionLayerExEx cLayerEx = (ConnectionLayerExEx) cLayer;
            if (Routing.RECTILINEAR_LITERAL == style.getRouting()) {
                if (rectilinearRouter == null) {
                    rectilinearRouter = cLayerEx.getBpmnMessagingEdgeRectilinearRouter();
                }
                getConnectionFigure().setConnectionRouter(rectilinearRouter);
                ((ConnectionMessageFigure) getFigure()).routerIsRectilinear = true;
                refreshRouterChange();
                return;
            }
        }
        ((ConnectionMessageFigure) getFigure()).routerIsRectilinear = false;
        super.installRouter();
    }
    
    
    /**
     * Returns top-most collapsed parent subprocess or specified editpart in
     * case if specified edit part has no parent subprocesses or all parent
     * subprocesses are expanded.
     * 
     * @notgenerated
     * @param editPart
     *            connection's source or target edit part
     * @return top-most collapsed parent subprocess.
     */
    private static EditPart getRealEditPart(EditPart editPart) {
        EditPart res = editPart;
        EditPart parent = editPart.getParent();
        while (!(parent instanceof PoolPoolCompartmentEditPart)) {
            if (parent instanceof SubProcessSubProcessBodyCompartmentEditPart) {
                boolean isCollapsed = ((Boolean) ((SubProcessSubProcessBodyCompartmentEditPart) parent)
                        .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                .getDrawerStyle_Collapsed())).booleanValue();
                if (isCollapsed) {
                    res = parent.getParent();
                }
            }
            parent = parent.getParent();
        }
        return res;
    }

    /**
     * Overrides default implementation. Returns source anchor on top-most
     * collapsed parent subprocess.
     * 
     * @notgenerated
     */
    @Override
    protected ConnectionAnchor getSourceConnectionAnchor() {
        EditPart editPart = getSource();
        if (editPart != null) {
            EditPart newSrcEditPart = getRealEditPart(editPart);
            if (newSrcEditPart != editPart) {
                // now check target
                EditPart targetEditPart = getTarget();
                if (targetEditPart != null) {
                    targetEditPart = getRealEditPart(targetEditPart);
                }
                if (newSrcEditPart != targetEditPart) {
                    srcConnEditPart = newSrcEditPart;
                    ConnectionAnchor ca = ((NodeEditPart) srcConnEditPart)
                            .getSourceConnectionAnchor(this);
                    updateConnectionAnchor(ca, true);
                    return ca;
                } else {
                    // don't apply changes becase it will cause self-connection
                    srcConnEditPart = editPart;
                }
            }
        }
        ConnectionAnchor ca = super.getSourceConnectionAnchor();
        updateConnectionAnchor(ca, true);
        return ca;
    }
    
    /**
     * Sets the connection anchor with extra parameters if it is an IModelAwareConnectionAnchor
     * @param ca The connection anchor to eventually update
     * @notgenerated
     */
    private void updateConnectionAnchor(
            final ConnectionAnchor ca, final boolean isSource) {
        if (ca instanceof IModelAwareAnchor) {
            IModelAwareAnchor modelAware = (IModelAwareAnchor)ca;
            MessagingEdge msgEdge = (MessagingEdge)getPrimaryView().getElement();
            EList messages = isSource ?
                    ((Activity)((GraphicalEditPart) super.getSource())
                            .getPrimaryView().getElement()).getOrderedMessages() :
                    ((Activity)((GraphicalEditPart) super.getTarget())
                            .getPrimaryView().getElement()).getOrderedMessages();
            int ind = messages.indexOf(msgEdge);
            int count = messages.size();
            modelAware.setConnectionType(isSource,
                    String.valueOf(VISUAL_ID), ind, count);
        }
    }

    @Override
    /**
     * Overrides default implementation. Returns target anchor on top-most
     * collapsed parent subprocess.
     * 
     * @notgenerated
     */
    protected ConnectionAnchor getTargetConnectionAnchor() {
        EditPart editPart = getTarget();
        if (editPart != null) {
            EditPart newTargetEditPart = getRealEditPart(editPart);
            if (newTargetEditPart != editPart) {
                // now check source
                EditPart srcEditPart = getSource();
                if (srcEditPart != null) {
                    srcEditPart = getRealEditPart(srcEditPart);
                }
                if (newTargetEditPart != srcEditPart) {
                    trgConnEditPart = newTargetEditPart;
                    ConnectionAnchor ca = ((NodeEditPart) trgConnEditPart)
                            .getTargetConnectionAnchor(this);
                    updateConnectionAnchor(ca, false);
                    return ca;
                } else {
                    // don't apply changes because it will cause self-connection
                    trgConnEditPart = editPart;
                }
            }
        }
        ConnectionAnchor ca = super.getTargetConnectionAnchor();
        updateConnectionAnchor(ca, false);
        return ca;
    }

    /**
     * Checkes whether this edge has reparented source or target.
     * 
     * @notgenerated
     * @return <code>true</code> in case if source or taget of this connecion
     *         were reparented, <code>false</code> otherwise.
     */
    private boolean isReparented() {
        return (srcConnEditPart != null && srcConnEditPart != this.getSource())
                || (trgConnEditPart != null && trgConnEditPart != getTarget());
    }

    @Override
    /**
     * mpeleshchyshyn: This handles the changes in the order of the messages
     * to be reflected in the UI.
     * @notgenerated
     */
    protected void handleNotificationEvent(Notification notification) {
        Object feature = notification.getFeature();
        if (feature instanceof EAttribute) {
            if ("anchor"
                    .equals(((EAttribute) feature).getDefaultValueLiteral())) {
                String newValue = notification.getNewStringValue();
                if (newValue != null) {
                    PrecisionPoint p = BaseSlidableAnchor
                            .parseTerminalString(newValue);
                    IdentityAnchor notifier = (IdentityAnchor) notification
                            .getNotifier();
                    Edge connection = (Edge) notifier.eContainer();
                    Activity activity;
                    boolean isSource;
                    if (connection.getSourceAnchor() == notifier) {
                        isSource = true;
                        activity = (Activity) connection.getSource()
                                .getElement();
                    } else {
                        isSource = false;
                        activity = (Activity) connection.getTarget()
                                .getElement();
                    }
                    EList messages = activity.getOrderedMessages();
                    int connCount = messages.size();
                    int newIdx = -1;
                    for (int i = 0; i < connCount; i++) {
                        if (p.preciseX < 1.0 / connCount * (i + 1)) {
                            newIdx = i;
                            break;
                        }
                    }
                    if (newIdx != -1) {
                        messages.move(newIdx, connection.getElement());
                        EditPart activityEditPart;
                        if (isSource) {
                            activityEditPart = getSource();
                        } else {
                            activityEditPart = getTarget();
                        }
                        activityEditPart.refresh();
                    }
                }
            }
        }
        super.handleNotificationEvent(notification);
    }
}
