package com.baselet.element.old.allinone;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.font.TextLayout;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.sourceforge.jlibeps.epsgraphics.EpsGraphics2D;

import com.baselet.control.HandlerElementMap;
import com.baselet.control.enums.AlignHorizontal;
import com.baselet.control.enums.Direction;
import com.baselet.control.enums.FormatLabels;
import com.baselet.control.enums.LineType;
import com.baselet.control.util.Utils;
import com.baselet.diagram.FontHandler;
import com.baselet.element.interfaces.GridElementDeprecatedAddons;
import com.baselet.element.old.OldGridElement;

// An interaction represents a synchronous/asynchronous message
// that is sent between two objects.
class Interaction {

	private final int srcObj;
	private final boolean srcObjHasControl;
	private final int arrowKind; // 1=SYNC, 2= ASYNC, 3=EDGE, 4=FILLED
	private final int lineKind; // 1=SOLID, 2=DOTTED
	private final int destObj;
	private final boolean destObjHasControl;
	private final String methodName;

	public Interaction(int srcObj, boolean srcObjHasControl, int arrowKind, int lineKind,
			int destObj, boolean destObjHasControl, String methodName) {
		this.srcObj = srcObj;
		this.srcObjHasControl = srcObjHasControl;
		this.arrowKind = arrowKind;
		this.lineKind = lineKind;
		this.destObj = destObj;
		this.destObjHasControl = destObjHasControl;
		this.methodName = methodName;
	}

	public boolean hasControl(int objNum) {
		return srcObjHasControl && srcObj == objNum ||
				destObjHasControl && destObj == objNum;
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Interaction)) {
			return false;
		}
		Interaction i = (Interaction) o;

		return srcObj == i.srcObj && srcObjHasControl == i.srcObjHasControl &&
				arrowKind == i.arrowKind && destObj == i.destObj &&
				destObjHasControl == i.destObjHasControl &&
				methodName == null || methodName.equals(i.methodName);
	}

	@Override
	public int hashCode() {
		return (methodName != null ? methodName.hashCode() : 1) +	srcObj +
				(srcObjHasControl ? 1 : 0) + arrowKind + destObj + (destObjHasControl ? 1 : 0);
	}

	public int getSrcObj() {
		return srcObj;
	}

	public boolean getSrcObjHasControl() {
		return srcObjHasControl;
	}

	public int getArrowKind() {
		return arrowKind;
	}

	public int getLineKind() {
		return lineKind;
	}

	public int getDestObj() {
		return destObj;
	}

	public boolean getDestObjHasControl() {
		return destObjHasControl;
	}

	public String getMethodName() {
		return methodName;
	}
}

// Contains all interactions entered by the user and
// offers various comfort-functions for finding and
// working with interactions
class InteractionManagement {
	private final Set<Interaction>[] level;

	// private Set[] level;

	@SuppressWarnings("unchecked")
	InteractionManagement(int numLevels) {
		level = new HashSet[numLevels];
		for (int i = 0; i < numLevels; i++) {
			level[i] = new HashSet<Interaction>();
		}
	}

	public boolean controlBoxExists(int levelNum, int objNum) {
		Iterator<Interaction> it = level[levelNum - 1].iterator();
		while (it.hasNext()) {
			Interaction ia = it.next();
			if (ia.hasControl(objNum)) {
				return true;
			}
		}
		return false;
	}

	public void add(int numLevel, Interaction i) {
		level[numLevel - 1].add(i);
	}

	public Set<Interaction> getInteractionsInLevel(int levelNum) {
		return level[levelNum - 1];
	}

	public int getNumLevels() {
		return level.length;
	}
}

@SuppressWarnings("serial")
public class SequenceDiagram extends OldGridElement {

	public int controlFlowBoxWidth = 20;

	// the dimensions for the rectangle(s) (=objects) in the first line
	public int rectDistance; // computed distance between two columns
	public int rectHeight; // computed
	public int rectWidth; // computed
	// int extraTextSpace = 0;

	public int borderDistance = 10; // d between the component bordeR and the diagram
	private int yOffsetforTitle = 0;

	public int rectToFirstLevelDistance = 0;
	public int levelHeight = 30;// LME//60;

	// these two constants are important for the arrowhead
	public int arrowX = 5;
	public int arrowY = 5;

	public static final int SYNC = 1;
	public static final int ASYNC = 2;
	public static final int EDGE = 3; // LME
	public static final int FILLED = 4; // LME
	public static final int SOLID = 1; // LME
	public static final int DOTTED = 2; // LME

	private Map<String, Integer> labeltonumber;
	private int levelNum = 0;
	private InteractionManagement im;

	@Override
	public void paintEntity(Graphics g) {
		rectDistance = 60;

		zoomValues();
		float zoom = HandlerElementMap.getHandlerForElement(this).getZoomFactor();

		// Some unimportant initialization stuff; setting color, font
		// quality, etc. You should not have to change this.
		Graphics2D g2 = (Graphics2D) g;
		FontHandler fontHandler = HandlerElementMap.getHandlerForElement(this).getFontHandler();
		g2.setFont(fontHandler.getFont());
		g2.setColor(fgColor);

		// draw the border
		g2.drawRect(0, 0, getRectangle().width - 1, getRectangle().height - 1);

		levelNum = 1;

		Vector<String> lines = Utils.decomposeStrings(getPanelAttributes());

		if (lines.size() == 0) {
			return;
		}
		if (lines.elementAt(0).startsWith("title:")) {
			String title = lines.elementAt(0).substring("title:".length());
			if (title != null && title.length() > 0) {
				fontHandler.writeText(g2, title, (int) (5 * zoom), (int) fontHandler.getFontSize() + (int) fontHandler.getDistanceBetweenTexts(), AlignHorizontal.LEFT);
				int titlewidth = (int) fontHandler.getTextWidth(title);
				int ty = (int) (8 * zoom) + (int) (fontHandler.getFontSize() + fontHandler.getDistanceBetweenTexts());
				g2.drawLine(0, ty, titlewidth + (int) (10 * zoom), ty);
				g2.drawLine(titlewidth + (int) (10 * zoom), ty, titlewidth + ty + (int) (10 * zoom), 0);
				lines.remove(0);
				yOffsetforTitle = (int) (25 * zoom);
			}
		}
		else {
			yOffsetforTitle = 0;
		}

		for (int i = 1; i < lines.size(); i++) {
			String element = lines.elementAt(i);
			if (element.indexOf("iframe{") >= 0) {
				element = "9999->0:" + element; // dummy: space for interactionframe
				lines.set(i, element);
			}
			else if (element.indexOf("iframe}") >= 0) {
				element = "9999<-0:" + element; // dummy: space for interactionframe
				lines.set(i, element);
			}
			// AB: match whitespace characters from the beginning to the end of the line
			if (lines.elementAt(i).matches("\\A\\s*\\z")) {
				continue;
			}
			levelNum++;
		}

		if (lines.size() == 0) {
			return; // return if it only has the title line (Issue 146)
		}

		String firstLine = lines.elementAt(0);
		Vector<String> obj = Utils.decomposeStrings(firstLine, "|");
		int numObjects = obj.size();

		// LABEL ADDING
		// get the labels of the Sequencediagram
		StringBuilder sb = new StringBuilder(""); // delete the ids from the header
		labeltonumber = new HashMap<String, Integer>();

		Pattern p_label = Pattern.compile("([^\\~]+)(\\~([a-zA-Z0-9]+))?(\\_)?");
		for (int i = 1; i <= numObjects; i++) {
			Matcher m = p_label.matcher(obj.get(i - 1));
			if (m.matches() && m.group(2) != null) {
				labeltonumber.put(m.group(3), i);
				sb.append("|").append(m.group(1)).append(m.group(4) == null ? "" : m.group(4));
			}
			else {
				sb.append("|").append(obj.get(i - 1));
			}
			if (!labeltonumber.containsKey(Integer.toString(i))) // only write number if no other label has this number
			{
				labeltonumber.put(Integer.toString(i), i); // columnnumber as label for backward compatibility
			}
		}
		String newhead = sb.toString();
		obj = Utils.decomposeStrings(newhead.length() > 0 ? newhead.substring(1) : "", "|");
		// LABELADDING STOP (exchanged parseInteger Methods with labeltonumber.get methods

		calcWidthOfLineHeaderBoxes(g2, fontHandler, obj, numObjects);

		// parse the messages
		int curLevel = 0;
		im = new InteractionManagement(levelNum);
		String boxStrings = "";
		for (int i = 1; i < lines.size(); i++) {
			String methodName = "";
			if (lines.elementAt(i).matches("\\A\\s*\\z")) {
				continue;
			}
			curLevel++;
			Vector<String> interactions = Utils.decomposeStrings(lines.elementAt(i), ";");

			for (int j = 0; j < interactions.size(); j++) {
				Pattern p = Pattern.compile("\\A(\\w+)(->>|->|-/>|.>>|.>|./>|->>>|.>>>|<<-|<-|</-|<<.|<.|</.|<<<-|<<<.)(\\w+)(:((\\w+)(,(\\w+))*))?(?::(.*))?\\z");
				// Pattern.compile("\\A(\\d+)(->>|->|-/>|.>>|.>|./>|->>>|.>>>)(\\d+)(:((\\d+)(,(\\d+))*))*(?::(.*))?\\z");

				// 1->2:1,2:methodName
				// 1->2:abc
				Matcher m = p.matcher(interactions.elementAt(j));

				if (!m.matches()) {
					continue;
				}
				Integer srcObj = labeltonumber.get(m.group(1));
				Integer destObj = labeltonumber.get(m.group(3));
				String methodNameFromText = m.group(9);
				if (srcObj != null && destObj != null && methodNameFromText != null) {
					Integer span = Math.abs(srcObj - destObj);
					if (span != 0) {
						double lineSpaceRequiredForMessage = fontHandler.getTextWidth(methodNameFromText) / span;
						double totalDist = lineSpaceRequiredForMessage - rectWidth + controlFlowBoxWidth; // add the rectWidth (because the text can exceed the half rect to the left and right and add the controlFlowBoxWidth to avoid text overlapping with control flow boxes
						rectDistance = (int) Math.max(rectDistance, totalDist);
					}
				}

				int arrowKind = -1;
				int lineKind = -1;
				boolean reverse = false; // arrow direction flag
				if (m.group(2).equals("->")) {
					arrowKind = ASYNC;
					lineKind = SOLID;
				}
				else if (m.group(2).equals("->>")) {
					arrowKind = SYNC;
					lineKind = SOLID;
				}
				else if (m.group(2).equals("-/>")) {
					arrowKind = EDGE;
					lineKind = SOLID;
				} // LME
				else if (m.group(2).equals("->>>")) {
					arrowKind = FILLED;
					lineKind = SOLID;
				} // LME
				else if (m.group(2).equals(".>")) {
					arrowKind = ASYNC;
					lineKind = DOTTED;
				} // LME
				else if (m.group(2).equals(".>>")) {
					arrowKind = SYNC;
					lineKind = DOTTED;
				} // LME
				else if (m.group(2).equals("./>")) {
					arrowKind = EDGE;
					lineKind = DOTTED;
				} // LME
				else if (m.group(2).equals(".>>>")) {
					arrowKind = FILLED;
					lineKind = DOTTED;
				} // LME
				else if (m.group(2).equals("<-")) {
					arrowKind = ASYNC;
					lineKind = SOLID;
					reverse = true;
				}
				else if (m.group(2).equals("<<-")) {
					arrowKind = SYNC;
					lineKind = SOLID;
					reverse = true;
				}
				else if (m.group(2).equals("</-")) {
					arrowKind = EDGE;
					lineKind = SOLID;
					reverse = true;
				} // LME
				else if (m.group(2).equals("<<<-")) {
					arrowKind = FILLED;
					lineKind = SOLID;
					reverse = true;
				} // LME
				else if (m.group(2).equals("<.")) {
					arrowKind = ASYNC;
					lineKind = DOTTED;
					reverse = true;
				} // LME
				else if (m.group(2).equals("<<.")) {
					arrowKind = SYNC;
					lineKind = DOTTED;
					reverse = true;
				} // LME
				else if (m.group(2).equals("</.")) {
					arrowKind = EDGE;
					lineKind = DOTTED;
					reverse = true;
				} // LME
				else if (m.group(2).equals("<<<.")) {
					arrowKind = FILLED;
					lineKind = DOTTED;
					reverse = true;
				} // LME

				String group = m.group(5);
				if (group == null) {
					group = "#";
					String element = interactions.elementAt(j);
					if (element.indexOf("iframe") >= 0) {
						group += element.substring(element.indexOf("iframe")); // append info for interactionframe
					}
				}
				else // LABLING ADD
				{
					String[] grouparray = group.split(",");
					group = "";
					for (String tmp : grouparray) {
						Integer tempgroup = labeltonumber.get(tmp);
						if (tempgroup != null) {
							group += "," + tempgroup;
						}
					}
					if (group.length() > 0) {
						group = group.substring(1);
					}
					else {
						if (grouparray.length == 1) {
							group = "#";
							methodName = grouparray[0];
						}
					}
				} // STOP LABLING ADD
				boxStrings += ";" + group; // LME: get alive Objects

				boolean srcObjHasControl = srcObj != null ? group.contains(String.valueOf(srcObj)) : false;
				boolean destObjHasControl = destObj != null ? group.contains(String.valueOf(destObj)) : false;

				if (methodName == null || methodName.isEmpty()) {
					methodName = methodNameFromText;
				}

				// LME: removed (in V6) since not necessary
				// if(destObj==srcObj) levelNum++; //LME: expand the Entity's size

				if (srcObj == null || destObj == null) {
					continue;
				}

				if (!reverse) {
					im.add(curLevel, new Interaction(srcObj, srcObjHasControl, arrowKind, lineKind, destObj, destObjHasControl, methodName)); // normal arrow direction 1->2
				}
				else {
					im.add(curLevel, new Interaction(destObj, destObjHasControl, arrowKind, lineKind, srcObj, srcObjHasControl, methodName)); // reverse arrow direction 1<-2
				}
			} // #for
		}
		// end message parsing

		// draw the first line of the sequence diagram
		int ypos = borderDistance + yOffsetforTitle;
		int xpos = borderDistance;
		for (int i = 0; i < numObjects; i++) {
			boolean underline = false;
			String s = obj.elementAt(i);
			if (s.startsWith(FormatLabels.UNDERLINE.getValue()) && s.endsWith(FormatLabels.UNDERLINE.getValue()) && s.length() > 2) {
				underline = true;
				s = s.substring(1, s.length() - 1);
			}
			TextLayout layout = new TextLayout(s, fontHandler.getFont(),
					g2.getFontRenderContext());

			g2.drawRect(xpos, ypos, rectWidth - 1, rectHeight - 1);

			int dx = (rectWidth - 2 - (int) Math.floor(layout.getBounds().getWidth() + 1)) / 2;
			int dy = (rectHeight - 2 - (int) Math.floor(layout.getBounds().getHeight() + 1)) / 2;
			int tx = xpos + dx;
			int ty = ypos + dy + (int) layout.getBounds().getHeight();

			layout.draw(g2, tx, ty);

			if (underline) {
				g2.drawLine(tx,
						ty + (int) fontHandler.getDistanceBetweenTexts() / 2,
						tx + (int) layout.getBounds().getWidth(),
						ty + (int) fontHandler.getDistanceBetweenTexts() / 2);
			}

			xpos += rectWidth + rectDistance;
		}

		// draw the messages
		int maxTextXpos = drawMessages(g2);
		maxTextXpos += 3 * fontHandler.getDistanceBetweenTexts(); // add extra space
		if (boxStrings.length() > 1) {
			try {
				drawControlFlowBoxesWithLines(g2, boxStrings.substring(1), numObjects); // LME: 1,2;1,2;... cut first ;-character
			} catch (ArrayIndexOutOfBoundsException e) {
				// do nothing: this exception is thrown, when entering text,
				// that is not rendered to an control flow box
			}
		}

		// set our component to the correct size
		int rWidth = rectWidth * numObjects + rectDistance * (numObjects - 1) + 2 * borderDistance;
		int rHeight = 2 * borderDistance + yOffsetforTitle + rectHeight + rectToFirstLevelDistance + levelNum * levelHeight;
		rWidth = rWidth > maxTextXpos ? rWidth : maxTextXpos;
		// align the borders to the grid
		rWidth += HandlerElementMap.getHandlerForElement(this).getGridSize() - rWidth % HandlerElementMap.getHandlerForElement(this).getGridSize();
		rHeight += HandlerElementMap.getHandlerForElement(this).getGridSize() - rHeight % HandlerElementMap.getHandlerForElement(this).getGridSize();
		setSize(rWidth, rHeight);
	}

	private void calcWidthOfLineHeaderBoxes(Graphics2D g2, FontHandler fontHandler, Vector<String> obj, int numObjects) {
		// find out the width of the column with the longest text
		double maxWidth = 0;
		double maxHeight = 0;
		for (int i = 0; i < numObjects; i++) {
			String s = obj.elementAt(i);
			if (s.startsWith(FormatLabels.UNDERLINE.getValue()) && s.endsWith(FormatLabels.UNDERLINE.getValue()) && s.length() > 2) {
				s = s.substring(1, s.length() - 1);
			}
			TextLayout layout = new TextLayout(s, fontHandler.getFont(), g2.getFontRenderContext());
			maxWidth = Math.max(layout.getBounds().getWidth(), maxWidth);
			maxHeight = Math.max(layout.getBounds().getHeight(), maxHeight);
		}

		rectWidth = (int) Math.floor(maxWidth + 1) + 2 * (int) fontHandler.getDistanceBetweenTexts() + (int) fontHandler.getFontSize();
		rectHeight = (int) Math.floor(maxHeight + 1) + (int) fontHandler.getDistanceBetweenTexts() + (int) fontHandler.getFontSize();
	}

	private int drawMessages(Graphics2D g2) {

		float zoom = HandlerElementMap.getHandlerForElement(this).getZoomFactor();

		int maxTextXpos = 0;
		for (int i = 0; i < im.getNumLevels(); i++) {
			Set<Interaction> interactions = im.getInteractionsInLevel(i + 1);
			Iterator<Interaction> it = interactions.iterator();
			while (it.hasNext()) {
				Interaction ia = it.next();
				if (ia.getSrcObj() == ia.getDestObj()) {
					// draw an arc-arrow
					int xTextOffset = 0;
					int w = (int) (30 * zoom);
					int h = (int) (levelHeight * 0.66);
					int x = hCenterForObj(ia.getSrcObj()) - w / 2;
					// nt y= vCenterForLevel(i+1) - h/2;
					int ay = vCenterForLevel(i + 1) + (int) (5 * zoom); // + levelHeight/2 -1;
					if (im.controlBoxExists(i + 1, ia.getSrcObj())) {
						x += controlFlowBoxWidth / 2;
						xTextOffset = controlFlowBoxWidth / 2;
					}
					g2.drawArc(x, ay, w, h, 90, -180);
					Point p1 = new Point(x + w / 2, ay + h);
					Point d1 = new Point(x + w / 2 + (int) (3 * zoom), p1.y - (int) (6 * zoom));
					Point d2 = new Point(x + w / 2 + (int) (4 * zoom), p1.y + (int) (4 * zoom));

					if (ia.getArrowKind() == ASYNC) { // Pfeil offen
						g2.drawLine(p1.x, p1.y, d1.x, d1.y);
						g2.drawLine(p1.x, p1.y, d2.x, d2.y);
					}
					else if (ia.getArrowKind() == SYNC) {
						int[] xs = { p1.x, d1.x, d2.x };
						int[] ys = { p1.y, d1.y, d2.y };
						Color oldColor = g2.getColor();
						g2.setColor(bgColor);
						g2.fillPolygon(xs, ys, 3);
						g2.setColor(oldColor);
						g2.drawPolygon(xs, ys, 3);
					}
					else if (ia.getArrowKind() == EDGE) {
						g2.drawLine(p1.x, p1.y, d2.x, d2.y);
					}
					else if (ia.getArrowKind() == FILLED) {
						Polygon p = new Polygon();
						p.addPoint(p1.x, p1.y);
						p.addPoint(d1.x, d1.y);
						p.addPoint(d2.x, d2.y);
						g2.fillPolygon(p);
					}

					// print the methodname
					if (ia.getMethodName() != null && !ia.getMethodName().equals("")) {
						int fx1 = x + w + 2;
						int fy1 = ay;
						int fx2 = hCenterForObj(ia.getSrcObj()) + rectWidth / 2;
						int fy2 = ay + h;
						int tx = printMethodName(g2, ia.getMethodName(), fx1 + xTextOffset, fx2 + xTextOffset,
								fy1, fy2, true, false);
						maxTextXpos = maxTextXpos > tx ? maxTextXpos : tx;
					}

				}
				else {
					// draw an arrow from the source-object to the destination object
					int begX = hCenterForObj(ia.getSrcObj());
					int endX = ia.getSrcObj() < ia.getDestObj() ? hCenterForObj(ia.getDestObj()) - 1 : hCenterForObj(ia.getDestObj()) + 1;
					int arrowY = vCenterForLevel(i + 1) + levelHeight / 2 - 1;

					if (ia.getSrcObjHasControl()) { // LME: shrink arrow if box exists
						begX += ia.getSrcObj() < ia.getDestObj() ? controlFlowBoxWidth / 2 : -controlFlowBoxWidth / 2;
					}
					if (ia.getDestObjHasControl()) { // LME: shrink arrow if box exists
						endX += ia.getSrcObj() < ia.getDestObj() ? -controlFlowBoxWidth / 2 : controlFlowBoxWidth / 2;
					}

					drawArrow(g2, new Point(begX, arrowY), new Point(endX, arrowY), ia.getArrowKind(), ia.getLineKind());

					if (ia.getMethodName() != null && !ia.getMethodName().equals("")) {
						final int b = 2;
						if (ia.getSrcObj() < ia.getDestObj()) {
							int tx = printMethodName(g2, ia.getMethodName(), begX + b, endX - arrowX - b, arrowY - 1 - levelHeight / 1, arrowY - 1, false, true);
							maxTextXpos = maxTextXpos > tx ? maxTextXpos : tx;
						}
						else {
							int tx = printMethodName(g2, ia.getMethodName(), endX + arrowX + b, begX - b, arrowY - 1 - levelHeight / 2, arrowY - 1, false, true);
							maxTextXpos = maxTextXpos > tx ? maxTextXpos : tx;
						}
					}
				}
			}
		}
		return maxTextXpos;
	}

	// prints the given methodName in an intelligent manner into
	// the supplied rectangle.
	// The method may put pixels anywhere into the supplied rectangle
	// including the borders. (In other words eg. the point begX/endY
	// may be set by this method.)
	// if the fontsize gets very big this method may cross the vertical borders.
	private int printMethodName(Graphics2D g2, String methodName,
			int begX, int endX, int begY, int endY,
			boolean centerVertically, boolean centerHorizontically) {

		if (methodName == null || methodName.equals("")) {
			log.error("SequenceDiagram->printMethodName was called with an invalid argument.");
			return 0;
		}

		Font font = HandlerElementMap.getHandlerForElement(this).getFontHandler().getFont();
		TextLayout layout = new TextLayout(methodName, font, g2.getFontRenderContext());

		// draw it horizontally centered
		int dx = centerHorizontically ? (endX - begX - (int) layout.getBounds().getWidth()) / 2 : 0;
		int dy = centerVertically ? (endY - begY - (int) layout.getBounds().getHeight()) / 2 : 1;

		layout.draw(g2, begX + dx, endY - dy);

		return begX + dx + (int) layout.getBounds().getWidth();
	}

	public void drawControlFlowBoxesWithLines(Graphics2D g2, String s, int numObjects) { // LME
		int level = 1;
		StringTokenizer mainTokens = new StringTokenizer(s, ";");

		int tokNum = mainTokens.countTokens();
		int[][] tField = new int[numObjects][tokNum + 2];

		Vector<Integer> interactionframes = new Vector<Integer>();
		HashMap<String, String> interactionframesText = new HashMap<String, String>();

		// collect all tokens into an array: create another view to sequentially access data of a specific object
		// 1,2;#;1,3;1,3;1;3 will be transfomed to
		// tField: [110]
		// [000]
		// [101]
		// [101]
		// [100]
		// [001]
		while (mainTokens.hasMoreTokens()) {
			String main = mainTokens.nextToken();
			if (main.indexOf("#") >= 0) { // if no box, clear entire row
				for (int i = 0; i < numObjects; i++) {
					tField[i][level - 1] = 0; // clear
				}
				if (main.indexOf("#iframe{") >= 0 || main.indexOf("#iframe}") >= 0) {
					if (main.indexOf("#iframe{") >= 0) {
						interactionframes.add(Integer.valueOf(level));
					}
					else {
						interactionframes.add(Integer.valueOf(level * -1)); // distinguish betweeen start and end of the iframe
					}
					if (main.indexOf("iframe{:") >= 0) {
						interactionframesText.put("" + level, main.substring(main.indexOf("iframe{:") + 8)); // put text into hashmap
					}
				}
				level++;
			}
			else {
				StringTokenizer innerT = new StringTokenizer(main, ",");
				for (int i = 0; i < numObjects; i++) {
					tField[i][level - 1] = 0; // clear
				}
				while (innerT.hasMoreTokens()) {
					String is = innerT.nextToken();
					int objNum = Integer.parseInt(is);
					tField[objNum - 1][level - 1] = 1;
				}
				level++;
			} // #else
		} // #while

		for (int actObjNum = 0; actObjNum < numObjects; actObjNum++) {
			// controlFlowBoxWidth = controlFlowBoxWidth * getHandler().getGridSize() / 10;
			// rectDistance = rectDistance / getHandler().getGridSize() / 10;
			// levelHeight = levelHeight / getHandler().getGridSize() / 10;

			int offset = 2;
			int objNum = actObjNum + 1;
			int x1 = hCenterForObj(objNum) - controlFlowBoxWidth / 2;
			int startLevel = -1, boxSize = 0;

			int lineX = hCenterForObj(actObjNum + 1);
			int lineY1 = borderDistance + yOffsetforTitle + rectHeight;

			for (int i = 0; i < tokNum + 1; i++) {
				if (tField[actObjNum][i] == 1) {
					if (startLevel == -1) {
						startLevel = i;
					}
					boxSize++;
				}
				if (tField[actObjNum][i] == 0 && startLevel != -1) {
					int y1 = vCenterForLevel(startLevel + offset) - levelHeight - 1;
					g2.drawRect(x1, y1, controlFlowBoxWidth - 1, levelHeight * boxSize); // draw the box

					g2.setStroke(Utils.getStroke(LineType.DASHED, 1)); // #draw the line between the boxes
					g2.drawLine(lineX, lineY1, lineX, y1); // #
					g2.setStroke(Utils.getStroke(LineType.SOLID, 1)); // #
					lineY1 = y1 + levelHeight * boxSize; // #
					startLevel = -1;
					boxSize = 0;
				}
			} // #for(int i
				// LME: draw the tail
			int lineY2 = borderDistance + yOffsetforTitle + rectHeight + levelNum * levelHeight + rectToFirstLevelDistance;
			g2.setStroke(Utils.getStroke(LineType.DASHED, 1));
			g2.drawLine(lineX, lineY1, lineX, lineY2);
			g2.setStroke(Utils.getStroke(LineType.SOLID, 1));
		} // #for(int actObjNum

		// LME: draw the interaction frames
		int fullSets = interactionframes.size() - interactionframes.size() % 2;
		if (fullSets >= 2) {
			int pos = 0;
			while (pos < fullSets) {
				pos = recurseInteractionFrames(g2, interactionframes, interactionframesText, pos, 0);
				pos++;
			}
		}
	}

	/**
	 * Recusion level step of interactionframes
	 * ie:
	 * iframe{
	 * iframe{
	 * }
	 * iframe{
	 * }
	 * }
	 */
	public int recurseInteractionFrames(Graphics2D g2, Vector<Integer> interactionframes, HashMap<String, String> interactionframesText, int pos, int recursionLevel) {
		int pos1 = interactionframes.elementAt(pos);
		int posX;
		while (pos < interactionframes.size() && (posX = interactionframes.elementAt(pos)) > 0) { // traverse through iframes an same level
			pos1 = posX;
			pos++;
			pos = recurseInteractionFrames(g2, interactionframes, interactionframesText, pos, recursionLevel + 1); // step on level deeper into recursion
			if (pos1 <= 0) {
				return pos;
			}
			int pos2 = interactionframes.elementAt(pos) * -1;
			drawInteractionFrame(g2, pos1, pos2, recursionLevel, interactionframesText.get("" + pos1));
			pos++;
		}
		return pos;
	}

	private void drawInteractionFrame(Graphics2D g2, int pos1, int pos2, int recursionLevel, String text) {

		float zoom = HandlerElementMap.getHandlerForElement(this).getZoomFactor();

		int pos11 = (pos1 + 1) * levelHeight + yOffsetforTitle;
		int h = (pos2 - pos1) * levelHeight;
		int x = (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts() * 2 + recursionLevel * 4;
		g2.drawRect(x, pos11, getRectangle().width - (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts() * 4 - 1 - recursionLevel * 8, h);
		int uLinePos = pos11 + (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts() + (int) (HandlerElementMap.getHandlerForElement(this).getFontHandler().getFontSize() + (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts());
		int textPos = pos11 + (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts() + (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getFontSize();

		int textWidth = 0;
		if (text == null || text.equals("")) {
			text = " ";
		}
		g2.drawString(text, x + (int) (10 * zoom), textPos);
		int pW = (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getTextWidth(text);
		textWidth = pW > textWidth ? pW : textWidth;

		g2.drawLine(x, uLinePos, x + textWidth + (int) (15 * zoom), uLinePos);
		g2.drawLine(x + textWidth + (int) (15 * zoom), uLinePos, x + textWidth + (int) (25 * zoom), pos11 + (int) (10 * zoom));
		g2.drawLine(x + textWidth + (int) (25 * zoom), pos11, x + textWidth + (int) (25 * zoom), pos11 + (int) (10 * zoom));
	}

	public void drawArrow(Graphics2D g2, Point srcObj, Point destObj, int arrowKind, int lineKind) {
		Point p1, p2;
		if (srcObj.x < destObj.x) {
			p1 = new Point(destObj.x - arrowX, destObj.y + arrowY);
			p2 = new Point(destObj.x - arrowX, destObj.y - arrowY);
		}
		else {
			p1 = new Point(destObj.x + arrowX, destObj.y + arrowY);
			p2 = new Point(destObj.x + arrowX, destObj.y - arrowY);
		}

		if (arrowKind == SYNC) {
			g2.drawLine(p1.x, p1.y, p2.x, p2.y);
			g2.drawLine(destObj.x, destObj.y, p1.x, p1.y);
			g2.drawLine(destObj.x, destObj.y, p2.x, p2.y);

			if (lineKind == DOTTED) {
				g2.setStroke(Utils.getStroke(LineType.DASHED, 1));
			}
			g2.drawLine(srcObj.x, srcObj.y, p1.x, destObj.y);
		}
		else if (arrowKind == ASYNC) {
			g2.drawLine(destObj.x, destObj.y, p1.x, p1.y);
			g2.drawLine(destObj.x, destObj.y, p2.x, p2.y);

			if (lineKind == DOTTED) {
				g2.setStroke(Utils.getStroke(LineType.DASHED, 1));
			}
			g2.drawLine(srcObj.x, srcObj.y, destObj.x, destObj.y);
		}
		else if (arrowKind == EDGE) {
			g2.drawLine(destObj.x, destObj.y, p2.x, p2.y);

			if (lineKind == DOTTED) {
				g2.setStroke(Utils.getStroke(LineType.DASHED, 1));
			}
			g2.drawLine(srcObj.x, srcObj.y, destObj.x, destObj.y);
		}
		else if (arrowKind == FILLED) {
			Polygon p = new Polygon();
			p.addPoint(p1.x, p1.y);
			p.addPoint(p2.x, p2.y);
			p.addPoint(destObj.x, destObj.y);
			g2.fillPolygon(p);

			if (lineKind == DOTTED) {
				g2.setStroke(Utils.getStroke(LineType.DASHED, 1));
			}
			g2.drawLine(srcObj.x, srcObj.y, p1.x, destObj.y);
		}
		g2.setStroke(Utils.getStroke(LineType.SOLID, 1));
	}

	protected int hCenterForObj(int objNum) {
		return objNum * rectWidth + (objNum - 1) * rectDistance + borderDistance - rectWidth / 2;
	}

	protected int vCenterForLevel(int level) {
		return level * levelHeight + rectToFirstLevelDistance +
				rectHeight + borderDistance + yOffsetforTitle - levelHeight / 2;
	}

	@Override
	public Set<Direction> getResizeArea(int x, int y) {
		return new HashSet<Direction>(); // deny size changes
	}

	public void zoomValues() {

		float zoom = HandlerElementMap.getHandlerForElement(this).getZoomFactor();

		controlFlowBoxWidth = (int) (20 * zoom);
		rectDistance = (int) (rectDistance * zoom);

		borderDistance = (int) (10 * zoom);
		levelHeight = (int) (30 * zoom);

		arrowX = (int) (5 * zoom);
		arrowY = (int) (5 * zoom);
	}

	@Override
	public boolean isDeprecated() {
		return false;
	}

	@Override
	public GridElementDeprecatedAddons getDeprecatedAddons() {
		return new GridElementDeprecatedAddons() {

			@Override
			public void doBeforeExport() {
				// Issue 159: the old all in one grid elements calculate their real size AFTER painting. although it's bad design it works for most cases, but batch-export can fail if the element width in the uxf is wrong (eg if it was created using another umlet-default-fontsize), therefore a pseudo-paint call is made to get the real size
				paintEntity(new EpsGraphics2D());
			}
		};
	}
}
