/******************************************************************************
 * 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 
 * 04 Dec 2006   	MPeleshchyshyn  	Created 
 */
package org.eclipse.stp.bpmn.policies;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gmf.runtime.diagram.ui.commands.CommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.commands.SetBoundsCommand;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest;
import org.eclipse.gmf.runtime.diagram.ui.requests.CreateViewRequest.ViewDescriptor;
import org.eclipse.gmf.runtime.emf.commands.core.command.CompositeTransactionalCommand;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.stp.bpmn.diagram.edit.parts.PoolEditPart;
import org.eclipse.stp.bpmn.diagram.edit.parts.PoolPoolCompartmentEditPart;
import org.eclipse.stp.bpmn.diagram.part.BpmnVisualIDRegistry;

/**
 * This policy is used to lay out all pools inside diagram.
 * 
 * @author MPeleshchyshyn
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 */
public class BpmnDiagramXYLayoutEditPolicy extends XYLayoutEditPolicy {

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#getResizeChildrenCommand(org.eclipse.gef.requests.ChangeBoundsRequest)
     */
    protected Command getResizeChildrenCommand(ChangeBoundsRequest request) {
        
        if (request.getMoveDelta() != null) {
            request.setMoveDelta(new Point(0, request.getMoveDelta().y));
        }
        CompoundCommand resize = new CompoundCommand();

        Map<IGraphicalEditPart, Rectangle> map = new HashMap<IGraphicalEditPart, Rectangle>();
        SortedSet<Rectangle> set = new TreeSet<Rectangle>(
                new RectanglesComparator());
        int maxWidth = fillMapAndSetCalcMaxWidth(map, set, request, resize);

        doLayout(map, set, maxWidth, resize, request);

        return resize.unwrap();
    }

    /**
     * Lays out pools - resolves overlappings, sets pools' width, creates
     * <code>SetBoundsCommand</code>s and appends them to the specified
     * compound command
     * 
     * @param map
     *            edit part - bounds map
     * @param set
     *            sorted bounds set - holds all pools' bounds ordered by y
     *            coordinate
     * @param maxWidth
     *            maximal width of the pools in the current diagram
     * @param cc
     *            compound command to append create set bounds commands
     */
    private void doLayout(Map<IGraphicalEditPart, Rectangle> map,
            SortedSet<Rectangle> set, int maxWidth, CompoundCommand cc,
            ChangeBoundsRequest requestOrNull) {
        Iterator<Rectangle> iterat = set.iterator();
        // move figures in a top if above them there is free space
        Rectangle firstBounds = iterat.next();
        int yDelta = 0 - firstBounds.y;
        firstBounds.setLocation(firstBounds.x, 0);
        firstBounds.width = maxWidth;

        while (iterat.hasNext()) {
            Rectangle element = iterat.next();
            element.setLocation(element.x, element.y + yDelta);
            element.width = maxWidth;
        }

        // prevent pools overlapping
        List<Rectangle> list = new ArrayList<Rectangle>(set);
        firstBounds = list.get(0);
        for (int i = 1; i < list.size(); i++) {
            Rectangle secondBounds = list.get(i);
            if ((firstBounds.y + firstBounds.height + 5) > secondBounds.y) {
                yDelta = (firstBounds.y + firstBounds.height + 5)
                        - secondBounds.y;
                secondBounds.setLocation(firstBounds.x, firstBounds.y
                        + firstBounds.height + 5);
                for (int j = i + 1; j < list.size(); j++) {
                    Rectangle element = list.get(j);
                    element.setLocation(element.x, element.y + yDelta);
                }
            }
            firstBounds = secondBounds;
        }

        Set<Map.Entry<IGraphicalEditPart, Rectangle>> entries = map.entrySet();
        Iterator<Map.Entry<IGraphicalEditPart, Rectangle>> outerIterator = entries
                .iterator();
        while (outerIterator.hasNext()) {
            Map.Entry<IGraphicalEditPart, Rectangle> outerEntry = outerIterator
                    .next();
            IGraphicalEditPart pep = outerEntry.getKey();
            Rectangle rect = outerEntry.getValue();

            Rectangle prevBounds = pep.getFigure().getBounds();
            if (!prevBounds.equals(rect)) {
                Command c = createChangeConstraintCommand(pep, rect);
                cc.add(c);
                if (requestOrNull != null && pep instanceof PoolEditPart) {
                    PoolPoolCompartmentEditPart poolCompartment =
                        (PoolPoolCompartmentEditPart)
                        ((PoolEditPart)pep)
                            .getChildBySemanticHint(BpmnVisualIDRegistry
                                .getType(PoolPoolCompartmentEditPart.VISUAL_ID));
                    if (poolCompartment != null) {
                        EditPolicy compartmentRefresh = poolCompartment
                            .getEditPolicy(EditPolicy.LAYOUT_ROLE);
                        c = compartmentRefresh.getCommand(requestOrNull);
                        if (c != null && c != UnexecutableCommand.INSTANCE) {
                            cc.add(c);
                        }
                    }
                }
                
            }
        }
    }

    /**
     * Fills specified edit part - bounds map and specified sorted set,
     * calculates maximal pool width.
     * 
     * @param map
     *            edit part - bounds map
     * @param set
     *            sorted bouds set (sorts rectangles by their y coordinate)
     * @param request
     *            change bouds request (can be <code>null</code> for create
     *            command)
     * @param cc
     *            compound command to append set bounds command for annotations.
     *            Can be <code>null</code> for create request.
     * @return maximal pool width inside diagram.
     */
    private int fillMapAndSetCalcMaxWidth(
            Map<IGraphicalEditPart, Rectangle> map, SortedSet<Rectangle> set,
            ChangeBoundsRequest request, CompoundCommand cc) {
        List children = request != null ? request.getEditParts()
                : Collections.EMPTY_LIST;
        List allChildren = getHost().getChildren();

        Iterator iter = allChildren.iterator();
        int maxWidth = -1;
        while (iter.hasNext()) {
            IGraphicalEditPart child = (IGraphicalEditPart) iter.next();
            int x = 10;
            if (child instanceof PoolEditPart) {
                Rectangle bounds;
                if (children.contains(child)) {
                    bounds = (Rectangle) translateToModelConstraint(getConstraintFor(
                            request, child));
                    bounds.x = x;
                } else {
                    int y = ((Integer) child
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getLocation_Y())).intValue();
                    int width = ((Integer) child
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getSize_Width())).intValue();
                    int height = ((Integer) child
                            .getStructuralFeatureValue(NotationPackage.eINSTANCE
                                    .getSize_Height())).intValue();
                    bounds = new Rectangle(x, y, width, height);
                }

                // find max pool width
                if (bounds.width > maxWidth) {
                    maxWidth = bounds.width;
                }
                map.put(child, bounds);
                set.add(bounds);
            } else if (children.contains(child)) {
                Command c = createChangeConstraintCommand(request, child,
                        translateToModelConstraint(getConstraintFor(request,
                                child)));
                cc.add(c);
            }
        }
        return maxWidth;
    }

    /**
     * Simple comparator class for Rectangles comparison
     * 
     * @author MPeleshchyshyn
     * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
     */
    private class RectanglesComparator implements Comparator<Rectangle> {

        /**
         * Rectangular A is bigger then rectangular B if Y coordinate of A is
         * bigger then Y coordinate of b
         */
        public int compare(Rectangle r1, Rectangle r2) {
            if (r1.y > r2.y) {
                return 1;
            } else if (r1.y < r2.y) {
                return -1;
            }
            return 0;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.XYLayoutEditPolicy#getCreateCommand(org.eclipse.gef.requests.CreateRequest)
     */
    protected Command getCreateCommand(CreateRequest request) {
        CreateViewRequest req = (CreateViewRequest) request;

        TransactionalEditingDomain editingDomain = ((IGraphicalEditPart) getHost())
                .getEditingDomain();

        CompositeTransactionalCommand cc = new CompositeTransactionalCommand(
                editingDomain, DiagramUIMessages.AddCommand_Label);
        Iterator iter = req.getViewDescriptors().iterator();

        final Rectangle BOUNDS = (Rectangle) getConstraintFor(request);

        Map<IGraphicalEditPart, Rectangle> map = new HashMap<IGraphicalEditPart, Rectangle>();
        SortedSet<Rectangle> set = new TreeSet<Rectangle>(
                new RectanglesComparator());
        int maxWidth = fillMapAndSetCalcMaxWidth(map, set, null, null);
        Map<ViewDescriptor, Rectangle> descriptorsMap = new HashMap<ViewDescriptor, Rectangle>();
        while (iter.hasNext()) {
            CreateViewRequest.ViewDescriptor viewDescriptor = (CreateViewRequest.ViewDescriptor) iter
                    .next();
            Rectangle rect = getBoundsOffest(req, BOUNDS, viewDescriptor);
            rect.x = 16;
            if (rect.height < PoolEditPart.POOL_HEIGHT) {
                rect.height = PoolEditPart.POOL_HEIGHT;
            }
            if (rect.width > maxWidth) {
                maxWidth = rect.width;
            }
            descriptorsMap.put(viewDescriptor, rect);
            set.add(rect);
        }

        if (map.size() == 0 && maxWidth == -1) {
            maxWidth = getHostFigure().getBounds().width - 25;
        } else if (maxWidth < PoolEditPart.POOL_WIDTH) {
            maxWidth = PoolEditPart.POOL_WIDTH;
        }

        CompoundCommand compoundCommand = new CompoundCommand();
        doLayout(map, set, maxWidth, compoundCommand, null);
        if (compoundCommand.canExecute()) {
            cc.compose(new CommandProxy(compoundCommand));
        }
        Iterator<Map.Entry<ViewDescriptor, Rectangle>> iterator = descriptorsMap
                .entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<ViewDescriptor, Rectangle> entry = iterator.next();
            cc.compose(new SetBoundsCommand(editingDomain,
                    DiagramUIMessages.SetLocationCommand_Label_Resize, entry
                            .getKey(), entry.getValue()));
        }

        if (cc.reduce() == null)
            return null;

        return chainGuideAttachmentCommands(request, new ICommandProxy(cc
                .reduce()));
    }

    
}
