/******************************************************************************
 * 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.figures.activities;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.stp.bpmn.ActivityType;
import org.eclipse.swt.SWT;

/**
 * Utility class for activity figures painting.
 * 
 * @author hmalphettes
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 */
public class ActivityPainter {
    
    /**
     * Paints the specified activity figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure to be painted.
     */
    public static void paint(Graphics graphics, ActivityFigure fig) {
        PrecisionRectangle innerRect = null;
        switch (fig.getActivityType()) {
        case ActivityType.EVENT_START_EMPTY:
        case ActivityType.EVENT_START_MESSAGE:
        case ActivityType.EVENT_START_RULE:
            innerRect = paintEventStart(graphics, fig);
            break;
        case ActivityType.EVENT_INTERMEDIATE_COMPENSATION:
        case ActivityType.EVENT_INTERMEDIATE_EMPTY:
        case ActivityType.EVENT_INTERMEDIATE_ERROR:
        case ActivityType.EVENT_INTERMEDIATE_MESSAGE:
        case ActivityType.EVENT_INTERMEDIATE_RULE:
        case ActivityType.EVENT_INTERMEDIATE_TIMER:
            innerRect = paintEventIntermediate(graphics, fig);
            break;
        case ActivityType.EVENT_END_COMPENSATION:
        case ActivityType.EVENT_END_EMPTY:
        case ActivityType.EVENT_END_ERROR:
        case ActivityType.EVENT_END_MESSAGE:
        case ActivityType.EVENT_END_TERMINATE:
            innerRect = paintEventEnd(graphics, fig);
            break;
        case ActivityType.GATEWAY_DATA_BASED_EXCLUSIVE:
        case ActivityType.GATEWAY_DATA_BASED_INCLUSIVE:
        case ActivityType.GATEWAY_EVENT_BASED_EXCLUSIVE:
        case ActivityType.GATEWAY_PARALLEL:
            innerRect = paintGateway(graphics, fig);
            break;
        }
        switch (fig.getActivityType()) {
        case ActivityType.EVENT_START_EMPTY:
            break;
        case ActivityType.EVENT_START_MESSAGE:
            paintMessage(graphics, fig, innerRect);
            break;
        case ActivityType.EVENT_START_RULE:
            paintRule(graphics, fig);
            break;
        case ActivityType.EVENT_INTERMEDIATE_COMPENSATION:
            paintCompensation(graphics, fig);
            break;
        case ActivityType.EVENT_INTERMEDIATE_EMPTY:
            break;
        case ActivityType.EVENT_INTERMEDIATE_ERROR:
            paintError(graphics, fig);
            break;
        case ActivityType.EVENT_INTERMEDIATE_MESSAGE:
            paintMessage(graphics, fig, innerRect);
            break;
        case ActivityType.EVENT_INTERMEDIATE_RULE:
            paintRule(graphics, fig);
            break;
        case ActivityType.EVENT_INTERMEDIATE_TIMER:
            paintTimer(graphics, fig, innerRect);
            break;
        case ActivityType.EVENT_END_COMPENSATION:
            paintCompensation(graphics, fig);
            break;
        case ActivityType.EVENT_END_EMPTY:
            break;
        case ActivityType.EVENT_END_ERROR:
            paintError(graphics, fig);
            break;
        case ActivityType.EVENT_END_MESSAGE:
            paintMessage(graphics, fig, innerRect);
            break;
        case ActivityType.EVENT_END_TERMINATE:
            paintTerminate(graphics, fig, innerRect);
            break;
        case ActivityType.GATEWAY_DATA_BASED_EXCLUSIVE:
            paintX(graphics, fig, innerRect);
            break;
        case ActivityType.GATEWAY_DATA_BASED_INCLUSIVE:
            // TODO: what is it again?
            paintBoldOval(graphics, fig, innerRect);
            break;
        case ActivityType.GATEWAY_EVENT_BASED_EXCLUSIVE:
            paintStar(graphics, fig, innerRect);
            break;
        case ActivityType.GATEWAY_PARALLEL:
            paintPlus(graphics, fig, innerRect);
            break;
        }
        if (fig.isLooping()) {
            paintLoopInsideFigure(graphics, fig.getBounds(), fig);
        }
    }

    /**
     * Paints gateway
     * 
     * @param graphics
     *            The Graphics object used for painting.
     * @param fig
     *            figure to be painted.
     */
    public static PrecisionRectangle paintGateway(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.darkGray);
        int lineWidth = MapModeUtil.getMapMode(fig).LPtoDP(2);
        graphics.setLineWidth(lineWidth);
        PrecisionRectangle r = calcInnerRectangle(
              fig.getBounds().getCopy(), lineWidth);
        PointList pointList = new PointList();

        pointList.addPoint(r.x + r.width / 2, r.y);
        pointList.addPoint(r.x + r.width, r.y + r.height / 2);
        pointList.addPoint(r.x + r.width / 2, r.y + r.height);
        pointList.addPoint(r.x, r.y + r.height / 2);
        graphics.fillPolygon(pointList);
        graphics.drawPolygon(pointList);

        graphics.popState();
        return r;
    }

    /**
     * Paints event start figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure to be painted.
     * @return The last rectangle in which something was painted.
     */
    public static PrecisionRectangle paintEventStart(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.black);

        // first fill the biggest oval with white:
        Rectangle rect = fig.getBounds().getCopy();
        graphics.fillOval(rect);

        graphics.setLineWidth(MapModeUtil.getMapMode(fig).LPtoDP(1));
        PrecisionRectangle outerCircle = calcInnerRectangle(rect, graphics.getLineWidth());
        
        //we compute the inner circle as if this was an intermediary event shape
        //so that we return the same size of inner shape to paint the markers
        //inside.
        PrecisionRectangle precRect = outerCircle.getPreciseCopy();
        shrink(precRect,
                outerCircle.preciseWidth/12, outerCircle.preciseHeight/12);
        outerCircle = precRect.getPreciseCopy();
        shrink(outerCircle,
                -outerCircle.preciseWidth/12, -outerCircle.preciseHeight/12);
        
        graphics.drawOval(outerCircle);

        graphics.popState();
        return precRect;
    }

    /**
     * Calculates rectangle inside the specified rectangle for the specified
     * line width.
     * 
     * @param rect
     *            outer rectangle
     * @param lineWidth
     *            the line width
     * @return inner rectangle which width and height is less by the specified
     *         line width from the outer rectangle width and height and location
     *         is shifted by half line width both in horizontal and vertical
     *         direction.
     */
    private static PrecisionRectangle calcInnerRectangle(Rectangle rect, int lineWidth) {
        PrecisionRectangle newRect = new PrecisionRectangle(rect);
        double halfLineWidth = lineWidth / 2.0;
        newRect.setX(newRect.preciseX + halfLineWidth);
        newRect.setY(newRect.preciseY + halfLineWidth);
        newRect.setWidth(newRect.preciseWidth - lineWidth);
        newRect.setHeight(newRect.preciseHeight - lineWidth);
        return newRect;
    }

    /**
     * Paints intermediate event figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure to be painted.
     * @return The inner most rectangle in which something was painted.
     */
    public static PrecisionRectangle paintEventIntermediate(Graphics graphics,
            ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.black);

        //do fill te biggest oval with white.
        Rectangle rect = fig.getBounds().getCopy();
        graphics.fillOval(rect);

        graphics.setLineWidth(1);//MapModeUtil.getMapMode(fig).LPtoDP(1));
        
        PrecisionRectangle outerCircle = calcInnerRectangle(rect, graphics.getLineWidth());
        
        PrecisionRectangle precRect = outerCircle.getPreciseCopy();
        
        double ddW = outerCircle.preciseWidth/10;
        double ddH = outerCircle.preciseHeight/10;
//        if (ddW<3/graphics.getAbsoluteScale()) {
//            ddW = Math.min(3/graphics.getAbsoluteScale(),outerCircle.preciseWidth/4);
//        }
//        if (ddH<3/graphics.getAbsoluteScale()) {
//            ddH = Math.min(3/graphics.getAbsoluteScale(),outerCircle.preciseHeight/4);
//        }
        
        shrink(precRect, ddW, ddH);
        outerCircle = precRect.getPreciseCopy();
        shrink(outerCircle, -ddW, -ddH);
        
        graphics.drawOval(precRect);
        graphics.drawOval(outerCircle);

        graphics.popState();
        return precRect;
    }

    /**
     * Paints intermediate event figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure to be painted.
     * @return The inner most rectangle in which something was painted.
     */
    public static PrecisionRectangle paintEventIntermediateOld(Graphics graphics,
            ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.black);

        //do fill te biggest oval with white.
        Rectangle rect = fig.getBounds().getCopy();
        graphics.fillOval(rect);

        graphics.setLineWidth(MapModeUtil.getMapMode(fig).LPtoDP(1));
        
        PrecisionRectangle outerCircle = calcInnerRectangle(rect, graphics.getLineWidth());
        
        PrecisionRectangle precRect = outerCircle.getPreciseCopy();
        shrink(precRect,
                outerCircle.preciseWidth/12, outerCircle.preciseHeight/12);
        outerCircle = precRect.getPreciseCopy();
        shrink(outerCircle,
                -outerCircle.preciseWidth/12, -outerCircle.preciseHeight/12);
        
        graphics.drawOval(precRect);
        graphics.drawOval(outerCircle);

        graphics.popState();
        return precRect;
    }

    /**
     * Paints event end figure.
     * 
     * @param graphics The Graphics object used for painting
     * @param fig event figure to be painted.
     * @return The last rectangle in wich something was actually painted
     */
    public static PrecisionRectangle paintEventEnd(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.darkGray);
        graphics.setBackgroundColor(ColorConstants.white);

        int inn = 2;//MapModeUtil.getMapMode(fig).LPtoDP(2);
        double lineWidth = fig.getBounds().width * graphics.getAbsoluteScale()/10;
        graphics.setLineWidth((int)Math.floor(lineWidth));
        
        PrecisionRectangle outerCircle = new PrecisionRectangle(fig.getBounds());
        shrink(outerCircle, inn, inn);
        graphics.fillOval(outerCircle);
        graphics.drawOval(outerCircle);
        
        graphics.popState();
        shrink(outerCircle, outerCircle.preciseWidth/10,
                outerCircle.preciseHeight/10);
        return outerCircle;
    }

    /**
     * Paints event end figure.
     * 
     * @param graphics The Graphics object used for painting
     * @param fig event figure to be painted.
     * @return The last rectangle in wich something was actually painted
     */
    public static PrecisionRectangle paintEventEndOld(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.black);

        // first fill the biggest oval with white:
        Rectangle rect = fig.getBounds().getCopy();
        //graphics.fillOval(rect);

        PrecisionRectangle outerCircle = calcInnerRectangle(rect, graphics.getLineWidth());
        
        //we compute the inner circle as if this was an intermediary event shape
        //so that we return the same size of inner shape to paint the markers.
        PrecisionRectangle precRect = outerCircle.getPreciseCopy();
        shrink(precRect,
                outerCircle.preciseWidth/10, outerCircle.preciseHeight/10);
        outerCircle = precRect.getPreciseCopy();
        shrink(outerCircle,
                -outerCircle.preciseWidth/10, -outerCircle.preciseHeight/10);
        graphics.setBackgroundColor(ColorConstants.darkGray);
        graphics.fillOval(outerCircle);
        graphics.setBackgroundColor(ColorConstants.white);
        graphics.fillOval(precRect);

        graphics.popState();
        return precRect;
    }

    /**
     * Paints terminate figure (filled black circle) inside end activity fidure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure
     */
    public static void paintTerminate(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle innerRect) {
        graphics.pushState();
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
//        graphics.setForegroundColor(ColorConstants.black);
        graphics.setBackgroundColor(ColorConstants.darkGray);

        shrink(innerRect, innerRect.preciseWidth / 5.0, innerRect.preciseHeight / 5.0);
        innerRect.x++;
        innerRect.y++;
        graphics.fillOval(innerRect);

        graphics.popState();
    }

    /**
     * Pains message figure (envelope) inside event figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure.
     */
    public static void paintMessage(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle innerRect) {
        graphics.pushState();
        
        int lineWidth = MapModeUtil.getMapMode(fig).LPtoDP(1);

        graphics.setForegroundColor(ColorConstants.black);
        graphics.setBackgroundColor(ColorConstants.black);
        graphics.setLineWidth(lineWidth);

        //make sure we are painting _inside_
        shrink(innerRect, innerRect.preciseWidth/6.0,
                innerRect.preciseHeight/4.5);
        
        graphics.drawRectangle(innerRect);

        // System.err.println("After: " + rect.width);
        // ok. now just need to compute 2 point around the center:
        // basically it is the center -1 on the y and + 1 on the x
        // then -1 and +1
        graphics.drawPolyline(new int[] { innerRect.getTopLeft().x,
                innerRect.getTopLeft().y, innerRect.getCenter().x, innerRect.getCenter().y,
                innerRect.getTopRight().x, innerRect.getTopRight().y });

        graphics.popState();
    }

    /**
     * Paints conpensation figure (backwards arrows) inside event figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            event figure.
     */
    public static void paintCompensation(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        Rectangle rect = fig.getBounds().getCopy();
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
        graphics.setBackgroundColor(ColorConstants.darkGray);
        graphics.setForegroundColor(ColorConstants.darkGray);

        // shrink to the same size of rectangle than the message
        rect.shrink(rect.width / 4,// *12/22,
                rect.height * (22 - 10) / (22 * 2));// *12/22;
        rect.translate(-rect.width / 16, 0);
        PointList pl = new PointList(3);
        pl.addPoint(rect.getLeft());
        pl.addPoint(rect.getTop());
        pl.addPoint(rect.getBottom());
        graphics.fillPolygon(pl);
        pl = new PointList(3);
        pl.addPoint(rect.getCenter());
        pl.addPoint(rect.getTopRight());
        pl.addPoint(rect.getBottomRight());
        graphics.fillPolygon(pl);

        graphics.popState();
    }

    /**
     * Paints timer inside activity figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure.
     * @param innerRect The precision rectangle inside the figure in which 
     * another paint already took place. Might be null
     */
    public static void paintTimer(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle innerRect) {
        graphics.pushState();

        int lineWidth = MapModeUtil.getMapMode(fig).LPtoDP(1);

        PrecisionRectangle rect = innerRect;
        //make sure we are painting _inside_
        shrink(rect, lineWidth / 2.0, lineWidth / 2.0);

        graphics.setForegroundColor(ColorConstants.black);
        graphics.setBackgroundColor(ColorConstants.black);

        graphics.setLineWidth(lineWidth);
        
        //let's use something more precise
        shrink(rect, rect.preciseWidth / 6.0, rect.preciseHeight / 6.0);
        
        graphics.drawOval(rect);
        
        shrink(rect, lineWidth / 2.0, lineWidth / 2.0);

        // now draw ticks
        double a = rect.preciseWidth / 2;
        double b = rect.preciseHeight / 2;
        double x0 = rect.preciseX + a;
        double y0 = rect.preciseY + b;

        final double RATIO = 0.8;
        for (int i = 0; i < 12; i++) {
            int angleGrad = i * 30;

            double x = a * Math.cos(Math.toRadians(angleGrad));
            double y = b * Math.sin(Math.toRadians(angleGrad));

            PrecisionPoint pp1 = new PrecisionPoint(x0 + x * RATIO, y0 + y * RATIO);
            PrecisionPoint pp2 = new PrecisionPoint(x0 + x, y0 + y);

            graphics.drawLine(pp1, pp2);
        }

        // draw hands
        PrecisionPoint center = new PrecisionPoint(x0, y0);
        PrecisionPoint bigHand = new PrecisionPoint(rect.preciseX, rect.preciseY);
        bigHand.preciseX += 11.0 * rect.preciseWidth / 18.0;
        bigHand.preciseY += 3.0 * rect.preciseHeight / 24.0;
        bigHand.updateInts();
        graphics.drawLine(center, bigHand);
        PrecisionPoint littleHand = new PrecisionPoint(x0, y0);
        littleHand.preciseX += 3.0 * rect.preciseWidth / 10.0;
        littleHand.preciseY -= 1.0 * rect.preciseHeight / 24.0;
        littleHand.updateInts();
        graphics.drawLine(center, littleHand);

        graphics.popState();
    }
    
    private static final void shrink(PrecisionRectangle rect, 
            double w, double h) {
        rect.setX(rect.preciseX + w);
        rect.setY(rect.preciseY + h);
        rect.setWidth(rect.preciseWidth - 2.0 * w);
        rect.setHeight(rect.preciseHeight - 2.0 * h);
        rect.updateInts();
    }

    /**
     * Paonts error figure (flash arrow) inside secified activity figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure.
     */
    public static void paintError(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        Rectangle rect = fig.getBounds().getCopy();
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
        graphics.setForegroundColor(ColorConstants.black);
        graphics.setBackgroundColor(ColorConstants.black);

        graphics.setLineWidth((int) Math.floor(2 * Math.max(rect.width,
                rect.height) / 22));
        // graphics.setLineCap(SWT.CAP_ROUND);
        rect.shrink(rect.width / 4, rect.height / 4);

        // graphics.drawRoundRectangle(rect, 6, 6);

        Point one = rect.getTopLeft().translate(1 * rect.width / 3,
                1 * rect.height / 3);
        Point onebis = one.getCopy().translate(1 * rect.width / 10,
                1 * rect.height / 10);
        Point two = rect.getTopLeft().translate(2 * rect.width / 3,
                2 * rect.height / 3);
        Point twobis = two.getCopy().translate(-1 * rect.width / 10,
                -1 * rect.height / 10);

        graphics.drawLine(one, two);

        // graphics.setLineCap(SWT.CAP_FLAT);
        PointList pl = new PointList(4);
        pl.addPoint(rect.getBottomLeft());
        pl.addPoint(one);
        pl.addPoint(onebis);
        graphics.fillPolygon(pl);

        pl = new PointList(4);
        pl.addPoint(rect.getTopRight());
        pl.addPoint(two);
        pl.addPoint(twobis);
        graphics.fillPolygon(pl);

        // PointList pl = new PointList(4);
        // pl.addPoint(rect.getBottomLeft());
        // pl.addPoint(one);
        // pl.addPoint(two);
        // pl.addPoint(rect.getTopRight());
        // graphics.drawPolyline(pl);
        graphics.popState();
    }

    /**
     * Paint rule figure inside specified activity figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure.
     */
    public static void paintRule(Graphics graphics, ActivityFigure fig) {
        graphics.pushState();
        Rectangle rect = fig.getBounds().getCopy();
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
        graphics.setForegroundColor(ColorConstants.black);
        graphics.setBackgroundColor(ColorConstants.black);

        // external rectangle is twice thicker
        graphics.setLineWidth((int) Math.floor(2 * Math.max(rect.width,
                rect.height) / 22));

        // graphics.drawRoundedRectangle(rect.shrink(10, 10), 8, 8);
        // System.err.println("Before: " + rect.width + " line width " +
        // graphics.getLineWidth()/2);
        graphics.drawRectangle(rect.shrink(graphics.getLineWidth() / 2
                + rect.width * (22 - 10) / (22 * 2), graphics.getLineWidth()
                / 2 + rect.height / 4));

        // internal lines are thin
        graphics.setLineWidth((int) Math.floor(Math
                .max(rect.width, rect.height) / 22));

        rect.shrink(graphics.getLineWidth(), graphics.getLineWidth() * 4);

        for (int i = 0; i < 5; i++) {
            graphics.drawLine(rect.getTopLeft()
                    .translate(0, i * rect.width / 5), rect.getTopRight()
                    .translate(0, i * rect.width / 5));
        }

        graphics.popState();
    }

    /**
     * Paints star figure for event-based exclusive gateway figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure.
     */
    public static void paintStar(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle innerRect) {
        graphics.pushState();
        Rectangle rect = fig.getBounds().getCopy();
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
        graphics.setBackgroundColor(ColorConstants.darkGray);
        graphics.setForegroundColor(ColorConstants.darkGray);

        // if it is a gateway, paint the 2 ovals
        if (fig.getActivityType() == ActivityType.GATEWAY_EVENT_BASED_EXCLUSIVE) {
            //same strategy than the intermediate event:
            //go into PrecisionRectangle, compute the smallest one, expand it
            //draw.
            //this will only work for diamond shapes.
            int lineWidth = 1;MapModeUtil.getMapMode(fig).LPtoDP(1);
            PrecisionRectangle inner = new PrecisionRectangle(innerRect);//calcInnerRectangle(innerRect, lineWidth);
//            graphics.drawRectangle(inner);
            inner.preciseWidth = 1.414 * rect.width / 2.0;
            inner.preciseHeight = 1.414 * rect.height / 2.0;
            inner.preciseX = rect.x + rect.width * (2-1.41421356) / 4.0;
            inner.preciseY = rect.y + rect.height * (2-1.41421356) / 4.0;
            inner.updateInts();
            inner = calcInnerRectangle(inner, lineWidth);
//            graphics.drawRectangle(inner);
            
            PrecisionRectangle precRect = inner.getPreciseCopy();
            shrink(precRect,
                    2*inner.preciseWidth/12, 2*inner.preciseHeight/12);
            inner = precRect.getPreciseCopy();
            shrink(inner,
                    -3*inner.preciseWidth/24, -3*inner.preciseHeight/24);
            
            graphics.drawOval(precRect);
            graphics.drawOval(inner);
            
            precRect = calcInnerRectangle(precRect, lineWidth);
            shrink(precRect, precRect.preciseWidth / 6, precRect.preciseHeight / 5);
            rect = precRect.getCopy();
        } else {
            // 2 triangles, one north one, one south
            // the only difficulty is to compute the center.

            // first shrink to the same size than the message width (about)
            rect.shrink(rect.width / 5,// *12/22,
                    rect.height / 4);// *12/22;
        }

        // not a true hexagram but good enough for now

        // now translate to
        // graphics.fillPolygon(getStarPolygon(Math.PI/4, Math.PI / 6));
        rect.translate(0, -1 * rect.height / 5);
        
        PointList pl = new PointList(3);
        pl.addPoint(rect.getTop());
        pl.addPoint(rect.getBottomLeft());
        pl.addPoint(rect.getBottomRight());
        graphics.fillPolygon(pl);

        rect.translate(0, 2 * rect.height / 5);
        pl = new PointList(3);
        pl.addPoint(rect.getBottom());
        pl.addPoint(rect.getTopRight());
        pl.addPoint(rect.getTopLeft());
        graphics.fillPolygon(pl);

        graphics.popState();
    }

    /**
     * Calculates star polygon for event-based exclusive gateway.
     * 
     * @param radius
     *            the radius of the circle that should contain the star.
     * @param rotation
     *            rotation angle
     * @return
     */
    static PointList getStarPolygon(double radius, double rotation) {
        PointList pointList = new PointList(10);
        /*
         * radius2: the distance between the inner tip and the center
         */
        double radius2 = radius * Math.sin(Math.PI / 10)
                / Math.cos(Math.PI / 5);
        for (int i = 0; i < 5; i++) {
            // outer tip
            double xAngle = Math.PI * 2 * i / 5 + rotation - Math.PI / 2;
            float x1 = (float) (radius * Math.cos(xAngle));
            float y1 = (float) (radius * Math.sin(xAngle));
            Point point1 = new Point(Math.round(x1), Math.round(y1));
            pointList.addPoint(point1);
            // inner tip
            double xAngle2 = xAngle + Math.PI / 5;
            float x2 = (float) (radius2 * Math.cos(xAngle2));
            float y2 = (float) (radius2 * Math.sin(xAngle2));
            Point point2 = new Point(Math.round(x2), Math.round(y2));
            pointList.addPoint(point2);
        }
        return pointList;
    }

    /**
     * Paints X-cross figure for data-based exclusive gateway figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure.
     */
    public static void paintX(Graphics graphics, ActivityFigure fig, PrecisionRectangle rect) {
        graphics.pushState();

        graphics.setForegroundColor(ColorConstants.darkGray);

        double lineWidth = rect.preciseWidth * graphics.getAbsoluteScale()/6;
        shrink(rect, 3.0* rect.preciseWidth / 8,
                     3.0* rect.preciseHeight / 8);
        
        graphics.setLineCap(SWT.CAP_SQUARE);
        graphics.setLineWidth((int)Math.floor(lineWidth*0.95));
        
        graphics.drawLine(rect.getTopLeft(), rect.getBottomRight());
        graphics.drawLine(rect.getTopRight(), rect.getBottomLeft());
        
        graphics.popState();
    }

    /**
     * Paints + cross figure for parallel gateway figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            activity figure where cross should be painted.
     */
    public static void paintPlus(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle rect) {
        graphics.pushState();
        graphics.setForegroundColor(ColorConstants.darkGray);

        double lineWidth = rect.preciseWidth * graphics.getAbsoluteScale()/6;
        shrink(rect, 5*rect.preciseWidth / 16,
                     5*rect.preciseHeight / 16);
        
        graphics.setLineCap(SWT.CAP_SQUARE);
        graphics.setLineWidth((int)Math.floor(lineWidth));
        
        graphics.drawLine(rect.getTop(), rect.getBottom());
        graphics.drawLine(rect.getLeft(), rect.getRight());
        

        graphics.popState();
    }

    /**
     * Paints oval figure for data-based inclusive figure.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param fig
     *            data-based inclusive figure.
     */
    public static void paintBoldOval(Graphics graphics, ActivityFigure fig,
            PrecisionRectangle innerRect) {
        graphics.pushState();
        Rectangle rect = innerRect;
        // linewidth is 1/22 of the figure. rectangle width is 12 for 22:
        // and height is 10 for 22
        graphics.setForegroundColor(ColorConstants.darkGray);

        int orih = rect.height;
        int oriw = rect.width;

        Rectangle rr = rect.getCopy().shrink(11 * oriw / 44, 11 * orih / 44);
        graphics.setLineWidth((int)Math.round(graphics.getAbsoluteScale()*rr.width/8));
        graphics.drawOval(rr);

        graphics.popState();

    }

    /**
     * Calculated loop marker bounds for the figure with the specified bounds
     * 
     * @param figureRect
     *            figure bounds
     * @return calculated loop markler bounds
     */
    public static PrecisionRectangle getLoopMarkerBounds(Rectangle figureRect) {
        final double RATIO = 5.0;
        double loopHeight = figureRect.height / RATIO;
        double loopWidth = Math.min(loopHeight, figureRect.width);

        double loopX = figureRect.x + (figureRect.width - loopWidth) / 2.0;
        double loopY = figureRect.y + figureRect.height - loopHeight;

        PrecisionRectangle bounds = new PrecisionRectangle();
        bounds.setX(loopX);
        bounds.setY(loopY);
        bounds.setWidth(loopWidth);
        bounds.setHeight(loopHeight);

        return bounds;
    }

    /**
     * Paints loop marker insice figure wwith the specified bounds.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param figureRect
     *            figure bounds
     */
    public static void paintLoopInsideFigure(Graphics graphics,
            Rectangle figureRect, IFigure fig) {
        PrecisionRectangle loopRect = getLoopMarkerBounds(figureRect);

        paintLoop(graphics, loopRect, fig);
    }

    /**
     * Paints loop marker with the specified bounds.
     * 
     * @param graphics
     *            The Graphics object used for painting
     * @param loopRect
     *            loop marker bounds.
     */
    public static void paintLoop(Graphics graphics, PrecisionRectangle loopRect,
            IFigure fig) {
        graphics.pushState();
        graphics.setLineWidth(
                MapModeUtil.getMapMode(fig).LPtoDP(2));

        int angleGrad = 30;// between 0 and 90 - angle between vertical axis
        // and start of arc
        graphics.drawArc(loopRect, -(90 - angleGrad), 360 - 2 * angleGrad);

        // now calculate end of arc coordinates
        double dx = loopRect.preciseWidth / 2
                * Math.cos(Math.toRadians(90 - angleGrad));
        double dy = loopRect.preciseHeight / 2
                * Math.sin(Math.toRadians(90 - angleGrad));

        double endX = loopRect.preciseX + loopRect.preciseWidth / 2 - dx;
        double endY = loopRect.preciseY + loopRect.preciseHeight / 2 + dy;
        double length = endX - loopRect.preciseX;

        // and draw arrow
        PrecisionPoint pp1 = new PrecisionPoint(loopRect.preciseX, endY);
        PrecisionPoint pp2 = new PrecisionPoint(endX, endY);
        graphics.drawLine(pp1, pp2);
        PrecisionPoint pp3 = new PrecisionPoint(endX, endY - length);
        graphics.drawLine(pp2, pp3);

        graphics.popState();
    }
}
