var FlyOut = {};

FlyOut.AttachPoint = {
	Top: 0,
	TopRight: 1,
	Right: 2,
	BottomRight: 3,
	Bottom: 4,
	BottomLeft: 5,
	Left: 6,
	TopLeft: 7,
	Center: 8,
	Centre: 8
};

FlyOut.MenuCollection = function () {
	this.List = [];
	
	this.AddMenu = function (triggerNode, className, anchorNode, attachPoint, attachTo, offset) {
		//	see FlyOut.Menu for parameter description
		var menu = new FlyOut.Menu(triggerNode, className, anchorNode, attachPoint, attachTo, offset);
		menu.MenuCollection = this;
		this.List.push(menu);
		return menu;
	};
	
	this.Build = function () {
		for (var i = this.List.length; i--;)
			this.List[i].Build(document.body)
	};
};

FlyOut.MenuItem = function (text, href) {
	this.Text = text;
	this.HREF = href;
};

FlyOut.Menu = function (	/*(string|HTMLElement|array)*/ triggerNode,	//	node(s) that trigger this menu
							/*string?*/ className,						//	className to give the containing node
							/*(string|HTMLElement)?*/ anchorNode,		//	optional node that this menu is always anchored to
							/*FlyOut.AttachPoint?*/ attachPoint,		//	point on the menu node which will be attached to the anchor
							/*FlyOut.AttachPoint?*/ attachTo,			//	point on the anchor node to attach to
							/*{x:number, y:number}?*/ offset,			//	x/y offset from the anchorNode attachTo point
							/*(string)?*/ focusEvent					//	event type (per Prototype) to trigger on, e.g. 'mouseover'
							) {
	
	//	logic to accept string or array parameters for triggerNode
	if (triggerNode.constructor && triggerNode.constructor === Array) { this.TriggerNode = triggerNode; }
	else { this.TriggerNode = [triggerNode]; }
	
	this.FocusEvent = focusEvent ? focusEvent : 'mouseover';
	this.BlurEvent = 'mouseout';
	
	this.ClassName = className;
	
	this.MenuCollection = null;
	
	//	use prototype to collect and expand all trigger nodes
	for (var i = this.TriggerNode.length; i--;) { this.TriggerNode[i] = $(this.TriggerNode[i]); }
	
	this.Listen = function () {
		var node;
		for (var i = this.TriggerNode.length; i--;)
		{
			node = this.TriggerNode[i];
			node.observe(this.FocusEvent, this.Focus.bind(this));
			node.observe(this.BlurEvent, this.Blur.bind(this));
		}
	};
	
	//	option anchorNode
	if (anchorNode) { this.AnchorNode = $(anchorNode); }
	
	this.AttachPoint = attachPoint;
	this.AttachTo = attachTo;
	
	this.Items = [];
	
	this.AddItem = function (text, href) {
		//	see FlyOut.MenuItem for parameter description;
		var item = new FlyOut.MenuItem(text, href);
		this.Items.push(item);
		return item;
	};
	
	this.ContainerNode = null;
	this.ShadowNode = null;
	this.Build = function (canvas) {
		var outside = this.ContainerNode = $(Builder.node("div"));
		if (this.ClassName)
			outside.addClassName(this.ClassName);

		outside.setStyle({
			position: "absolute",
			top: "0",
			left: "0",
			zIndex: "999"
		});

		var container = $(Builder.node("ul"));
		var item, itemNode;
		for (var i = 0; i < this.Items.length; i++)
		{
			item = this.Items[i];
			container.appendChild(itemNode = $(Builder.node("li", [$(Builder.node("a", {href: item.HREF}, item.Text))])));
		}
		
		var children = container.descendants();
		for (var j = children.length; j--;)
		{
			children[j].observe('mouseover', this.Focus.bind(this));
			children[j].observe('mouseout', this.Blur.bind(this));
		}

		outside.appendChild(container);
		outside.hide();
		canvas.appendChild(outside);
		
		var shadow = this.ShadowNode = $(Builder.node("div", ""));
		shadow.setStyle({
			position: "absolute",
			top: "0",
			left: "0",
			zIndex: "998",
			opacity: 0.3,
			backgroundColor: "#000000",
			width: this.ContainerNode.getWidth() +"px",
			height: this.ContainerNode.getHeight() +"px",
			fontSize: "0"
		});
		shadow.hide();
		canvas.appendChild(shadow);
	};
	
	this.Showing = false;
	this.Show = function () {
		Effect.Appear(this.ContainerNode, {duration:0.3, from:0, to:1});
		Effect.Appear(this.ShadowNode, {duration:0.3, from:0, to:0.3});
		this.Showing = true;
		if (this.BlurTimeout)
			clearTimeout(this.BlurTimeout);
		
		var i;
		if (this.MenuCollection && (i = this.MenuCollection.List.length) > 1)
		{
			var menu;
			for (;i--;)
			{
				menu = this.MenuCollection.List[i];
				if (menu === this)
					continue;
				menu.Hide();
			}
		}
	};
	
	this.Hide = function () {
		Effect.Fade(this.ContainerNode, {duration:0.3, from:1, to:0});
		Effect.Fade(this.ShadowNode, {duration:0.3, from:0.3, to:0});
		this.Showing = false;
		if (this.BlurTimeout)
			clearTimeout(this.BlurTimeout);
	};
	
	this.Focus = function (evt) {
		Event.stop(evt);
		
		if (this.BlurTimeout)
			clearTimeout(this.BlurTimeout);

		if (this.Showing)
			return;
	
		var anchor = $(this.anchorNode ? this.anchorNode : Event.element(evt));
		if (anchor)
		{
			var apos = FlyOut.FindPos(anchor);
			var apos = {x:apos[0], y:apos[1]};
			var adim = {w:anchor.getWidth(), h:anchor.getHeight()};
			var mydim = {w:this.ContainerNode.getWidth(), h:this.ContainerNode.getHeight()};
			var anchorTo = {x:apos.x, y:apos.y};
			
			switch (this.AttachTo)
			{
				case FlyOut.AttachPoint.Top:
				case FlyOut.AttachPoint.Centre:
				case FlyOut.AttachPoint.Bottom:
					anchorTo.x += Math.round(adim.w/2);
					break;
					
				case FlyOut.AttachPoint.TopRight:
				case FlyOut.AttachPoint.Right:
				case FlyOut.AttachPoint.BottomRight:
					anchorTo.x += adim.w;
					break;
			}

			switch (this.AttachTo)
			{
				case FlyOut.AttachPoint.Left:
				case FlyOut.AttachPoint.Centre:
				case FlyOut.AttachPoint.Right:
					anchorTo.y += Math.round(adim.h/2);
					break;
				
				case FlyOut.AttachPoint.BottomLeft:
				case FlyOut.AttachPoint.Bottom:
				case FlyOut.AttachPoint.BottomRight:
					anchorTo.y += adim.h;
					break;
			}
			
			switch (this.AttachPoint)
			{
				case FlyOut.AttachPoint.Top:
				case FlyOut.AttachPoint.Centre:
				case FlyOut.AttachPoint.Bottom:
					anchorTo.x -= Math.round(mydim.w/2);
					break;
					
				case FlyOut.AttachPoint.TopRight:
				case FlyOut.AttachPoint.Right:
				case FlyOut.AttachPoint.BottomRight:
					anchorTo.x -= mydim.w;
					break;
			}
			
			switch (this.AttachPoint)
			{
				case FlyOut.AttachPoint.Left:
				case FlyOut.AttachPoint.Centre:
				case FlyOut.AttachPoint.Right:
					anchorTo.y -= Math.round(mydim.h/2);
					break;
				
				case FlyOut.AttachPoint.BottomLeft:
				case FlyOut.AttachPoint.Bottom:
				case FlyOut.AttachPoint.BottomRight:
					anchorTo.y -= mydim.h;
					break;
			}

			this.ContainerNode.setStyle({
				left: anchorTo.x +"px",
				top: anchorTo.y +"px"
			});
			
			this.ShadowNode.setStyle({
				left: (anchorTo.x+4) +"px",
				top: (anchorTo.y+4) +"px"
			});
		}
	
		this.Show();
	};
	
	this.BlurTimeout = null;
	
	this.OnBlurTimeout = function () {
		this.Hide();
	};

	this.Blur = function (evt) {
		if (this.BlurTimeout)
			clearTimeout(this.BlurTimeout);
		this.BlurTimeout = window.setTimeout(this.OnBlurTimeout.bind(this), 2000);
	};
	
	this.Listen();
};

FlyOut.FindPos = function (obj) {
	//	http://www.quirksmode.org/js/findpos.html - thanks
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
	}
	return [curleft,curtop];
};
