package com.umlet.element;

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 com.baselet.control.Constants.LineType;
import com.baselet.control.Utils;
import com.baselet.diagram.DiagramHandler;
import com.baselet.diagram.FontHandler;


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

	private int srcObj;
	private boolean srcObjHasControl;
	private int arrowKind; // 1=SYNC, 2= ASYNC, 3=EDGE, 4=FILLED
	private int lineKind; // 1=SOLID, 2=DOTTED
	private int destObj;
	private boolean destObjHasControl;
	private 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) {
		if ((srcObjHasControl && (srcObj == objNum)) ||
				(destObjHasControl && (destObj == objNum))) return true;
		else return false;
	}

	@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 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 com.baselet.element.GridElement {

	public int controlFlowBoxWidth = 20;

	// the dimensions for the rectangle(s) (=objects) in the first line
	public int rectDistance = 60; // 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 final int SYNC = 1;
	public final int ASYNC = 2;
	public final int EDGE = 3; // LME
	public final int FILLED = 4; // LME
	public final int SOLID = 1; // LME
	public final int DOTTED = 2; // LME

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

	public SequenceDiagram() {

	}

	@Override
	public void setHandler(DiagramHandler handler) {
		super.setHandler(handler);
		zoomValues();
	}

	@Override
	public void paintEntity(Graphics g) {

		zoomValues();
		float zoom = getHandler().getZoomFactor();

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

		// draw the border
		g2.drawRect(0, 0, this.getWidth() - 1, this.getHeight() - 1);

		levelNum = 1;

		Vector<String> lines = Utils.decomposeStrings(this.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)) {
				this.getHandler().getFontHandler().writeText(g2, title, (int) (5 * zoom), (int) this.getHandler().getFontHandler().getFontSize() + (int) this.getHandler().getFontHandler().getDistanceBetweenTexts(), false);
				int titlewidth = this.getHandler().getFontHandler().getTextWidth(title);
				int ty = (int) (8 * zoom) + (int) (this.getHandler().getFontHandler().getFontSize() + this.getHandler().getFontHandler().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++;
		}

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

		// LABEL ADDING
		// get the labels of the Sequencediagram
		String newhead = ""; // delete the ids from the header
		this.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)) {
				this.labeltonumber.put(m.group(3), i);
				newhead += "|" + m.group(1) + (m.group(4) == null ? "" : m.group(4));
			}
			else {
				newhead += "|" + obj.get(i - 1);
			}
			if (!this.labeltonumber.containsKey(Integer.toString(i))) // only write number if no other label has this number
			{
				this.labeltonumber.put(Integer.toString(i), i); // columnnumber as label for backward compatibility
			}
		}
		obj = Utils.decomposeStrings(newhead.length() > 0 ? newhead.substring(1) : "", "|");
		// LABELADDING STOP (exchanged parseInteger Methods with labeltonumber.get methods

		// 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 = this.labeltonumber.get(m.group(1));
				Integer destObj = this.labeltonumber.get(m.group(3));

				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 = this.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.isEmpty()) {
					methodName = m.group(9);
				}

				// 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

		// 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(FontHandler.FormatLabels.UNDERLINE) && s.endsWith(FontHandler.FormatLabels.UNDERLINE) && (s.length() > 2)) {
				s = s.substring(1, s.length() - 1);
			}
			TextLayout layout = new TextLayout(s, this.getHandler().getFontHandler().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) this.getHandler().getFontHandler().getDistanceBetweenTexts() + (int) this.getHandler().getFontHandler().getFontSize();
		rectHeight = (int) Math.floor(maxHeight + 1) + (int) this.getHandler().getFontHandler().getDistanceBetweenTexts() + (int) this.getHandler().getFontHandler().getFontSize();

		// 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(FontHandler.FormatLabels.UNDERLINE) && s.endsWith(FontHandler.FormatLabels.UNDERLINE) && (s.length() > 2)) {
				underline = true;
				s = s.substring(1, s.length() - 1);
			}
			TextLayout layout = new TextLayout(s, this.getHandler().getFontHandler().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) this.getHandler().getFontHandler().getDistanceBetweenTexts() / 2,
					tx + (int) layout.getBounds().getWidth(),
					ty + (int) this.getHandler().getFontHandler().getDistanceBetweenTexts() / 2);

			xpos += rectWidth + rectDistance;
		}

		// draw the messages
		int maxTextXpos = drawMessages(g2);
		maxTextXpos += 3 * this.getHandler().getFontHandler().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 += this.getHandler().getGridSize() - rWidth % this.getHandler().getGridSize();
		rHeight += this.getHandler().getGridSize() - rHeight % this.getHandler().getGridSize();
		setSize(rWidth, rHeight);
	}

	private int drawMessages(Graphics2D g2) {

		float zoom = getHandler().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 = this.getHandler().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(new Integer(level));
					else interactionframes.add(new Integer(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 = getHandler().getZoomFactor();

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

		int textWidth = 0;
		if ((text == null) || text.equals("")) text = " ";
		g2.drawString(text, x + (int) (10 * zoom), textPos);
		int pW = this.getHandler().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 int getPossibleResizeDirections() {
		return 0;
	} // deny size changes: dynamic resize is done by the diagram itself

	private void zoomValues() {

		float zoom = getHandler().getZoomFactor();

		controlFlowBoxWidth = (int) (20 * zoom);
		rectDistance = (int) (60 * zoom);
		rectHeight = 2 * (int) this.getHandler().getFontHandler().getDistanceBetweenTexts() + (int) this.getHandler().getFontHandler().getFontSize() + 10;
		rectWidth = 2 * (int) this.getHandler().getFontHandler().getDistanceBetweenTexts() + (int) this.getHandler().getFontHandler().getFontSize() + 10;

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

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