function InfoBalloon()
{
	this.group = document.createElementNS(nsSVG, tagGroup);
	this.group.id = "GroupForInfoBalloon";
	this.group.style.visible = kwVisHidden;

	this.path = document.createElementNS(nsSVG, tagPath);

	this.path.style.setProperty(attrFill, colorWhite, "");
	this.path.id = "BalloonPath";
	this.group.appendChild(this.path);

	document.documentElement.appendChild(this.group);

	this.Path = null;

	this.InnerClone = null;
	this.OuterClone = null;
	this.text = null;
	this.title = null;
	this.rule = null;

	this.textMgr = new TextAreaObj();

	this.ArrowDef = function()
	{
		this.length = 35; //length of the pointing arrow
		this.width = 10; //width of the pointing arrow at its base
	}
	referenced: arrowDef = new this.ArrowDef();

	this.StrokeSize = function()
	{
		this.inner = 2;
		this.outer = 6;

		this.total = this.inner + this.outer;
	}
	referenced: strokeSize = new this.StrokeSize();

	referenced: CornerDiameter = 10; //radius of corner
	referenced: xMargin = 10;
	referenced: yMargin = 10;
	referenced: MinTextWidth = 200;
	referenced: RuleThickness = 7; // includes the margin

	this.Hide = function()
	{
		if(this.group.style.visible == kwVisVisible)
			this.group.style.visible = kwVisHidden;

		if(this.text)
			this.group.removeChild(this.text);

		if(this.title)
			this.group.removeChild(this.title);

		this.text = null;
		this.title = null;
	}

	this.Show = function(refRect, text, title, layer)
	{
		this.Hide();

		var bPointRight = false;
		var SpaceAvailRect = layer.getBBox();
		SpaceAvailRect.width -= xMargin;
		SpaceAvailRect.height -= yMargin;

		if(SpaceAvailRect.x + SpaceAvailRect.width - (refRect.x + refRect.width) < MinTextWidth + xMargin * 2 + strokeSize.total * 2.5 + arrowDef.length)
		{
			bPointRight = true;
			refRect.x -= strokeSize.total * 2.5;
		}
		else
			refRect.width += strokeSize.total * 2.5;

		var MaxWidthForText = 0.0;
		var TextRefRect = {x: refRect.x, y: refRect.y + yMargin, width: refRect.width, height: refRect.height};
		if(bPointRight)
		{
			TextRefRect.x -= xMargin + arrowDef.length;
			MaxWidthForText = TextRefRect.x - SpaceAvailRect.x;
		}
		else
		{
			TextRefRect.width += xMargin + arrowDef.length;
			MaxWidthForText = SpaceAvailRect.x + SpaceAvailRect.width - TextRefRect.x - TextRefRect.width;
		}


		var TotalTextAndRuleHeight = 0;
		var ActualTextWidth = 0
		if(title != null && title != "")
		{
			this.title = this.textMgr.createText(title, TextRefRect, new FontInfo(null, null, 30, null, kwFwBold, true, null), MaxWidthForText, this.group, bPointRight, true);
			this.title.Move(0, (this.title.fontInfo.size - this.title.fontInfo.getDefaults().size) / 2);


			TextRefRect.y += this.title.GetMinHeight() + RuleThickness;
			TotalTextAndRuleHeight = this.title.GetMinHeight();
			ActualTextWidth = this.title.GetMinWidth();
		}

		if(text != null && text != "")
		{
			this.text = this.textMgr.createText(text, TextRefRect, null, MaxWidthForText, this.group, bPointRight, true);
			TotalTextAndRuleHeight += this.text.GetMinHeight();

			if(ActualTextWidth < this.text.GetMinWidth())
				ActualTextWidth = this.text.GetMinWidth();
		}


		if(this.text != null && this.title != null);
		{
			if(this.title.Element.getBBox().x < this.text.Element.getBBox().x)
				this.text.Move(this.title.Element.getBBox().x - this.text.Element.getBBox().x, 0);
			else
				this.title.Move(this.text.Element.getBBox().x - this.title.Element.getBBox().x, 0);

			if(this.rule == null)
			{
				this.rule = document.createElementNS(nsSVG, tagLine);
				this.rule.style.setProperty(attrStroke, colorDkGray, "");
				this.rule.style.setProperty(attrStrokeOpacity, 1, "");
				this.rule.style.setProperty(attrStrokeWidth, 2, "");
				this.rule.style.setProperty(attrStrokeDashStyle, "2,3", "");

				this.group.appendChild(this.rule);
			}

			this.rule.setAttribute(attrStartX, this.title.x);
			this.rule.setAttribute(attrStartY, this.title.Element.getBBox().y + this.title.GetMinHeight() + RuleThickness / 2);
			this.rule.setAttribute(attrEndX, this.title.x + ActualTextWidth);
			this.rule.setAttribute(attrEndY, this.title.Element.getBBox().y + this.title.GetMinHeight() + RuleThickness / 2);

			TotalTextAndRuleHeight += RuleThickness;
		}

		this.SetPath(bPointRight ? refRect.x : refRect.x + refRect.width, refRect.y + refRect.height / 2, ActualTextWidth, TotalTextAndRuleHeight, bPointRight);
		this.group.style.visible = kwVisVisible;
	}

		// Width and height parameters are for the measurements /inside/ the box + margins
		// px and py are for the tip of the point.
	this.SetPath = function(px, py, width, height, bPointRight)
	{
		// Compute start position (top left corner, but inside the arrow)
		var Top = py - CornerDiameter - arrowDef.width / 2;
		var Left = bPointRight ? px - width - xMargin * 2 - arrowDef.length : px + arrowDef.length;


		// draw "InfoBalloon" with relative path

		// start path where pointer indicated 
		var pathData = "M" + Left + "," + (Top + CornerDiameter);

		// draw rounded corner 
		if(0 < CornerDiameter)
			pathData += " q0," + -CornerDiameter + " " + CornerDiameter + "," + -CornerDiameter;

		// draw horizontal top of the box, minus corner rounding 
		pathData += " h" + (width + xMargin * 2 - CornerDiameter * 2);

		// draw rounded corner 
		if(0 < CornerDiameter)
			pathData += " q" + CornerDiameter + ",0 " + CornerDiameter + "," + CornerDiameter;

		if(bPointRight)
		{
			pathData += " l" + arrowDef.length + "," + arrowDef.width / 2;
			pathData += " l" + (-arrowDef.length) + "," + arrowDef.width / 2;
		}

		// draw vertical right side of the box, minus corner rounding 
		pathData += " v" + (height + yMargin * 2 - CornerDiameter * 2 - (bPointRight ? arrowDef.width : 0));

		// draw rounded corner 
		if(0 < CornerDiameter)
			pathData += " q0," + CornerDiameter + " " + -CornerDiameter + "," + CornerDiameter;

		// draw horizontal bottom of the box, minus corner rounding 
		pathData += " h" + (-(width + xMargin * 2 - CornerDiameter * 2));

		// draw rounded corner 
		if(0 < CornerDiameter)
			pathData += " q" + -CornerDiameter + ",0 " + -CornerDiameter + "," + -CornerDiameter;

		// draw vertical left side of the box, minus the arrow base and corner rounding 
		pathData += " v" + (-(height + yMargin * 2 - CornerDiameter * 2 - (bPointRight ? 0 : arrowDef.width)));

		if(!bPointRight)
		{
			// draw "lineto" to give arrow length 
			pathData += " l" + (-arrowDef.length) + "," + (-arrowDef.width / 2);
			pathData += " l" + arrowDef.length + "," + (-arrowDef.width / 2);
		}

		this.path.setAttribute(attrPathCoord, pathData);
	}

	this.CreateClone = function(bInner)
	{
		var clone = document.createElementNS(nsSVG, tagClone);

		clone.setAttribute(attrX, 0);
		clone.setAttribute(attrY, 0);

		clone.id = this.path.id + "_" + (bInner ? "Inner" : "Outer") + "_Border"
		clone.setAttributeNS(nsXlink, attrHREF, "#" + this.path.id);
		clone.style.setProperty(attrStroke, bInner ? colorBlack : colorWhite, "");
		clone.style.setProperty(attrStrokeWidth, bInner ? strokeSize.inner : strokeSize.outer, "");
		clone.style.setProperty(attrStrokeOpacity, 1, "");
		clone.style.setProperty(attrStrokeMiterLimit, "10", "");

		if(bInner)
			this.InnerClone = clone;
		else
			this.OuterClone = clone;

		this.group.insertBefore(clone, this.path);
	}

	this.SetParent = function(NewParent)
	{
		this.group.parentNode.removeChild(this.group);
		NewParent.appendChild(this.group);
	}

	this.SetOpacity = function(NewOpacity)
	{
		this.group.style.setProperty(attrOpacity, NewOpacity, "");
	}

	this.CreateClone(false);
	this.CreateClone(true);
}

var theInfoBalloon = new InfoBalloon(); //InfoBalloon element