
var RightContext = {
	//some final vars:
	TYPE_MENU: 0,       // menu item
	TYPE_TEXT: 1,       // inline text (non-mutable hard coded)
	TYPE_TEXT_EXT: 2,   // external text (retrived via rpc call)
	TYPE_SEPERATOR:3,   // separator line
	TYPE_ATTRIBUTES:4,  // menu attributes.

	// some simple browser detection
	browser: null,

	// set the event to trigger the menus: RIGHT,LEFT (right/left click) or mouse MOVE 
	menuTriggerEvent: "RIGHT",
	
	// object to hold temp mouse position
	mousePos: {x:0, y:0},

	// offset for menu from mouse pointer
	rightOffset: 5,
	
	// kill menu timeout - sets the timeout from mouse out to menu dissapearing
	killMenuTimeout: 25, 

	// type of html tags that can have context menus. You can edit this to 
	// allow more tags into the party.
	allowedContexts: ["a","div","span","input","textarea"],

	// object to hold a collection of menus indexed by name
	menuCollection: new Object(),

	// the currently visible context menu DIV element
	contextMenu: null,
	
	// some state machine: is the menu showing (LEFT), and should killing it be aborted (MOVE)
	isShowing: false,
	abortKill: false,
	
	// image cache
	images: new Object(),
	
	// var to hold external requests
	req: null,
	
	// initialize RightContext object 
	initialize: function () {
		this.browser = RightContext.detectBrowser();
		this.attachContextEvents();   
	},

	// adds a menu to the menuCollection
	addMenu: function (n, m) { 
		this.menuCollection[n] = m;
	},

	// return a menu from the menu collection
	getMenu: function (n) {
		return this.menuCollection[n];
	},

	// loop all context allowed tags in the document and attach menu events to 
	// those that contain the menu attribute
	attachContextEvents: function () {
		var tagContext, thisTag;
		for (var t=0; t<this.allowedContexts.length; t++) {
			tags = document.getElementsByTagName(this.allowedContexts[t]);

			for (e=0; e<tags.length; e++) {
				thisTag = tags[e];
				tagContext = thisTag.getAttribute("context");
				if (tagContext!=null && tagContext != "undefined") {
					this.bindEvent('mousemove', tags[e], function(e) { return RightContext.locateMousePos(e); });
					if (this.menuTriggerEvent=="RIGHT") {
						tags[e].oncontextmenu = function() {   return RightContext.render(this);   }; 
					} else if (this.menuTriggerEvent=="LEFT") {
						//this.bindEvent('click', tags[e],  function() {  return RightContext.render(this, tagContext);  });
						tags[e].onclick = function(e) { 
														RightContext.killBubble(e); 
														return RightContext.render(this)
													};
						tags[e].onmouseout = function(e) { setTimeout("RightContext.killMenu()", 5000);};
					} else if (this.menuTriggerEvent=="MOVE") {
						if (!document.all) {
							this.bindEvent('mouseover', tags[e], function(e) { RightContext.locateMousePos(e); return RightContext.render(this); });
							this.bindEvent('mouseout',  tags[e], function(e) { setTimeout("RightContext.killMenu()", RightContext.killMenuTimeout); });
						} else {
							tags[e].onmouseover =  function(e) { RightContext.locateMousePos(e); return RightContext.render(this); };
							tags[e].onmouseout = function(e) { setTimeout("RightContext.killMenu()", RightContext.killMenuTimeout); };
						}
					}
				}
			}
		}
	},
	
	killBubble: function(e) {
		if (!e) var e = window.event;
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
	},
	
	// binds an event handler to an object
	bindEvent: function (evt, obj, act, bubble) {
		if (!bubble) bubble = false;
		if (obj.addEventListener) {
			obj.addEventListener(evt, act, bubble);
		} else if (obj.attachEvent) {
			obj.attachEvent('on'+evt, act);
		}
	},
	

	/*
	renders a given menu and attaches it to the caller object.
	The caller is responsible to contain a few extra attributes
	that will help construct the links for this menu (i.e., provide the Context)
	*/
	render: function (caller, name) {
		var url, title;
		// if name was not specified, grab it from the caller
		// v0.2 - changed to getAttribute (used direct nodeValue access before my mistake). pointed out by JDG.
		var name = name || caller.getAttribute("context");

		// get the requested menu
		var thisMenu = this.getMenu(name);
		
		// extracts this menus attributes list and items
		var attributes = thisMenu["attributes"].split(',');   
		var items = thisMenu.items;

		// constructs a map from the callers attributes
		var objMap = this.buildAttributeMap(attributes, caller);

		// start building the menu itself, but first remove menu if visible
		this.killMenu();
		this.buildMenu(caller);
		
		// create a table to build the menu items in
		tbl = document.createElement("TABLE");
		tbl.id = "rcRightContextTable";
		
		// loop the menu items and render each according to its type
		for (var m=0; m<items.length; m++) {
			switch (items[m]["type"]) {
				case this.TYPE_MENU:
					// add the menu item
					if (this.isDisplayed(items[m], objMap)) {
						this.addMenuItem(items[m], objMap, tbl);
					}
					break;

				case this.TYPE_TEXT:
					// add fixed text
					text = this.transform(items[m]["text"], objMap);
					cell = this.addTableCell(tbl, "rcMenuItemText", text);
					break;

				case this.TYPE_TEXT_EXT:
					cell = this.addTableCell(tbl, "rcMenuItemTextExt");
					url = this.transform(items[m]["url"], objMap);
					this.request(url, function() { if (RightContext.req.readyState == 4 && RightContext.req.status == 200) { cell.innerHTML = RightContext.req.responseText } });
					break;

				case this.TYPE_SEPERATOR:
					cell = this.addTableCell(tbl);
					cell.appendChild(this.getSeparator());
					break;
				
				default:
					// no default behaviour
					break;
			}
			
		}
		// append the menu item table to the menu itself
		this.contextMenu.appendChild(tbl);
		// make sure we're not overflowed to the edge of the screen.
		this.repositionMenu();
		
		if (this.menuTriggerEvent=="MOVE") {
			this.bindEvent('mouseout',  this.contextMenu, function(e) { RightContext.abortKill = false; setTimeout("RightContext.killMenu()", RightContext.killMenuTimeout); });
			this.bindEvent('mouseover', this.contextMenu, function(e) { RightContext.abortKill = true;  });
		} else if (this.menuTriggerEvent=="LEFT" || this.menuTriggerEvent=="RIGHT") {
			this.bindEvent('click', document.body, function(e) { setTimeout("RightContext.killMenu();", RightContext.killMenuTimeout); }, false);
		} 
		this.isShowing = true;
		
		return false;
	},

	isDisplayed : function(item, objMap) {
		var reqVar, reqVal;
		var shown = true; // by default all items are shown, unless they require something 
		// lets make sure this item does not require any condition to be true in order to display
		if (item["requires"] != null && item["requires"] != "undefined") {
			// yep, this one has a requirement...
			reqVar = item["requires"][0];
			reqVal = item["requires"][1];
			if (objMap[reqVar] != null && objMap[reqVar] != "undefined") {
				// if the condition is not met, do not show this item.
				if (objMap[reqVar] != reqVal) {
					shown = false;    
				}
			} else {
				// if the condition is not defined do not show the item
				shown = false;
			}
		}
		return shown;
	},
	
	// check if the menu goes outside the window boundries and adjust its 
	// location if so
	repositionMenu: function() {
		var mPos = this.findPosition(this.contextMenu);
		var mDim = this.getDimensions(this.contextMenu);
		var winHeight = this.getWindowHeight(); // window.innerHeight || document.body.clientHeight;   
		var winWidth = window.innerWidth || document.body.clientWidth;
		if (mPos.y + mDim.height > winHeight-30 ) {
			this.position(this.contextMenu, mPos.x, mPos.y - mDim.height);
			mPos = this.findPosition(this.contextMenu);
		} 
		if (mPos.x + mDim.width > winWidth - 30 ) {
			this.position(this.contextMenu, mPos.x-mDim.width, mPos.y);
		} 
	},
	
	// returns an HR sepearator which uses the rcMenuSeparator style
	getSeparator: function () {
		var sep = document.createElement("HR");
		sep.className = "rcMenuSeparator";
		return sep;
	},
	
	// adds a table cell to the provided table and returns it.
	// attached a class if provided and initializes the cell with some content 
	// where applicable
	addTableCell: function (table, className, content) {
		row = table.insertRow(-1);
		cell = row.insertCell(0);
		if (className) { 
			cell.className = className;
			if (content) {
				cell.innerHTML = content;
			}
		}
		return cell;
	},

	// adds a menu item to the provided table. transforms all data as defined 
	// in the objMap argument
	addMenuItem: function (item, objMap, tbl) {
		var title = this.transform(item["text"], objMap);
		var url, frame, img, imgAlign, itemSrc, tmp, itemAction; 
		var cell = this.addTableCell(tbl, "rcMenuItem", title); 
		cell.style.cursor = document.all?'hand':'pointer';
		this.bindEvent('mouseover', cell, function(e) { cell.className="rcMenuItemHover";});
		this.bindEvent('mouseout',  cell, function(e) { cell.className="rcMenuItem";     });
		
		// deal with image if applicable
		if (item["image"]!=null && item["image"]!="undefined") {
			// get image alignment or default to absmiddle
			imgAlign = (item["align"]!=null && item["align"]!="undefined") ? item["align"] : "absmiddle";
			// load the image from the cache, or from disk (and then cache it)
			if (this.images[item["image"]] != null && this.images[item["image"]] != "undefined") {
				img = this.images[item["image"]];
			} else {
				img = this.loadImage(item["image"]);
			}
			// set image alignment
			img.align=imgAlign;
			// insert the image as first child of the cell
			cell.insertBefore(this.images[item["image"]], cell.childNodes[0]);
		}
		
		if (item["url"]!=null && item["url"] != "undefined") {
			url   = this.transform(item["url"],  objMap);
			frame = false;
			if (item["frame"] != null && item["frame"] != "undefined") {
				frame = item["frame"];
			}
			cell.onclick = function () { RightContext.redirect(url, frame); }
		} else {
			// we first need to find out if the event handler contains a potential 
			// tag. if so, we grab its source, transform it and re-evaluate it. 
			// if this fails, the value reverts back to its original function
			itemAction = item["onclick"]; 
			try {

				itemSrc = item["onclick"].toString();
				if (itemSrc.indexOf('[')>-1) {
						itemSrc = this.transform(itemSrc, objMap);
						eval('itemAction = ' + itemSrc);
				}
			
			} catch (e) {
				// nothing...
			}

			// set the cell onclick event handler.
			cell.onclick=itemAction;
		}

	},

	// transforms a string based on the provided map
	transform: function (str, map) {
		var tStr, tmp;
		tStr = str;
		for (p in map) {
			tmp = "[" + p + "]";
			while (tStr.indexOf(tmp) > -1) {
				tStr = tStr.replace(tmp, map[p]);
			}
		}
		return tStr;
	},

	// returns the menu's attributes collection that will be used to construct 
	// the transformation map
	getMenuAttributeArray: function (menu) {
		for (var i=0; i<menu.length; i++) {
			if (menu[i].type == this.TYPE_ATTRIBUTES) {
				return menu[i]["attributes"].split(',');
			}
		}
		return new Array(0);
	},

	// construct the transformation map for a given object based on the tags in 
	// attribs
	buildAttributeMap: function (attribs, obj) {
		var thisAttr, thisValue;
		var attrMap = new Object();

		for (var a=0; a<attribs.length; a++) {
			thisAttr = attribs[a];
			thisValue = obj.getAttribute(attribs[a]);
			if (typeof thisValue != "undefined") {
				attrMap[thisAttr] = thisValue;
			}
		}
		return attrMap;
	},

	// find the position of an element on the screen and returns an array of [x,y]
	findPosition: function (obj) {
		var lft = 0;
		var top = 0;
		if (obj.offsetParent) {
			lft = obj.offsetLeft
			top = obj.offsetTop
			while (obj = obj.offsetParent) {
				lft += obj.offsetLeft
				top += obj.offsetTop
			}
		}
		return {x:lft,y:top};
	},
	
	getWindowHeight: function() {
		if (this.browser.khtml || this.browser.safari) {
			return this.innerHeight;
		} else if (this.browser.opera) {
			return document.body.clientHeight;			
		} else {
			return document.documentElement.clientHeight;
		}
	},

	// Returns the dimensions of an element on screen. Lifted from the wonderful 
	// prototype framework
	getDimensions: function(obj) {
		//var display = obj.getStyle('display');
		//if (display != 'none' && display != null) // Safari bug
		//  return {width: element.offsetWidth, height: element.offsetHeight};

		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily
		var objStyle = obj.style;
		var originalVisibility = objStyle.visibility;
		var originalPosition = objStyle.position;
		var originalDisplay = objStyle.display;
		objStyle.visibility = 'hidden';
		objStyle.position = 'absolute';
		objStyle.display = 'block';
		var originalWidth = obj.clientWidth;
		var originalHeight = obj.clientHeight;
		objStyle.display = originalDisplay;
		objStyle.position = originalPosition;
		objStyle.visibility = originalVisibility;
		return {width: originalWidth, height: originalHeight};
	},

	// positions object at x,y coordinates
	// v0.2 - added px to the position coordinate (provided by JDG)
	position: function (obj, x, y) {
		obj.style.left = x + 'px';
		obj.style.top  = y + 'px';
	},

	// builds a menu for parent object
	buildMenu: function (parent) {
		var pos, dim, tbl;
		//document.onmousemove  = RightContext.getMousePos;
		this.contextMenu = document.createElement("DIV");
		this.contextMenu.id = "rcRightContext";
		this.contextMenu.className = 'rcMenuContainer';

		// get the position and dimensions of the parent
		pos = this.findPosition(parent);
		dim = this.getDimensions(parent);

		// position the container to the bottom right of the element.
		this.position (this.contextMenu, this.mousePos.x + this.rightOffset, pos.y+dim.height);

		// set some event handlers
		// if the menu is triggered by a right click, disable the right click on the menu itself
		if (this.menuTriggerEvent == "RIGHT") {
			this.contextMenu.oncontextmenu = function () { return false; };
		}
		
		// add the container to the body of the document
		document.body.appendChild(this.contextMenu);
		
	},


	// kills the currently visible context menu
	killMenu: function () {
		if (!this.abortKill && this.isShowing) {
		try {
			rc = this.contextMenu;
			document.body.removeChild(rc);
		} catch (e) {
			// already removed?
		}
		this.contextMenu = null;
		this.isShowing = false;
		this.abortKill = false;
		}
	},

	// locate the mouse cursor position 
	locateMousePos: function(e) {
		var posx = 0, posy =0;
		if(e==null) e=window.event;
		if(e.pageX || e.pageY) {
			posx=e.pageX; posy=e.pageY;
		} else if (e.clientX || e.clientY) {
			if(document.documentElement.scrollTop){
				posx=e.clientX+document.documentElement.scrollLeft;
				posy=e.clientY+document.documentElement.scrollTop;
			} else {
				posx=e.clientX+document.body.scrollLeft;
				posy=e.clientY+document.body.scrollTop;
			}
		}
		this.mousePos = {x:posx , y:posy};

	},
	
	// redirects the browser to given url
	// if frame!=false, it will open in provided frame (or new win if _blank)
	redirect: function (u, frame) {
		if (!frame) {
			document.location = u;  
		} else {
			if (frame=="_blank") {
				w = window.open(u, 'w');
			} else {
				window.frames[frame].document.location = u;
			}
		}
	},
	
	// performs a request - ajax style
	request: function (url, callBack) {
		if (window.XMLHttpRequest) { // native XMLHttpRequest
			this.req = new XMLHttpRequest();
			this.req.onreadystatechange =  callBack; 
			this.req.open("GET", url, true);
			this.req.send(null);
		} else if (window.ActiveXObject) { // The M$ 'standard'
			this.req = new ActiveXObject("Microsoft.XMLHTTP");
			if (this.req) { 
				this.req.onreadystatechange =   callBack;
				this.req.open("GET", url, true);
				this.req.send();
			}
		}
	},
	
	loadImage: function (url) {
		var img = new Image();
		img.src = url; 
		img.className = "rcImage";
		this.images[url] = img;
		return img;
	},
	
	detectBrowser: function() {
		var ua = navigator.userAgent.toUpperCase();
		var up = navigator.platform.toUpperCase().substr(0,3);
		var isSafari  = (ua.indexOf("SAFARI"   )>0);
		var isKHTML   = (ua.indexOf("KONQUEROR")>0 || isSafari);
		var isFirefox = (ua.indexOf("FIREFOX"  )>0); 
		var isOpera   = (ua.indexOf("OPERA"    )>=0);
		return { safari: isSafari, khtml: isKHTML, opera: isOpera, firefox: isFirefox, platform: up }
	}

};
