/*************************************************\
  Generalized DHTML menu engine v2.3
  
  REQUIRES PROTOTYPE AND GSSI PROTOTYPE EXTENSION
\*************************************************/

var DMenu = Class.create();

Object.extend( DMenu, {
	// Static methods/properties

	_list: $H({}),
	
	_timeouts: {},

	_menupositions: $H({
		left: { halign:'before', valign:'top' },
		right: { halign:'after', valign:'top' },
		top: { halign:'left', valign:'above' },
		bottom: { halign:'left', valign:'below' }
	}),

	_clearresettimer: function( menuname ) {
		var timeout = DMenu._timeouts[ '*' + menuname ];
		
		if ( timeout ) {
			clearTimeout(timeout);
		};
		
		DMenu._timeouts[ '*' + menuname ] = null;
	},

	_setresettimer: function( menuname ) {
		DMenu._timeouts[ '*' + menuname ] = setTimeout( function(){ DMenu._list[menuname].reset(); } , DMenu._list[menuname].disappeardelay );
	},

	_hidemenublockdirect: function( blockid, useshadow ) {
		var block = $(blockid);
		block.style.visibility="hidden";
		$(blockid+'_iframe').style.display = "none";
		if (useshadow) {
			$(blockid + '_shadow').style.visibility="hidden";
		};
	},

	showMenuBlock: function ( blockid, menuname, callingelement, issubmenu ) {
		DMenu._clearresettimer( menuname );
		var block = $(blockid);
		callingelement = $(callingelement);

		var menu = DMenu._list[ menuname ];

		var options = menu.submenuoptions[ blockid ] || {};

		if ( issubmenu ) {
			position = options['position'] || menu.subposition;
		} else {
			position = options['position'] || menu.mainposition;
		}
				
		block.moveToElement( callingelement, position );
		Position.clone( block, $(block.id + '_iframe'), { setLeft:true, setTop:true, setWidth:true, setHeight:true } );

		if ( menu.useshadow ) {
			Position.clone( block, $(block.id + '_shadowblock'), { setLeft:true, setTop:true, setWidth:true, setHeight:true } );
			Position.clone( block, $(block.id + '_shadow'), { setWidth:true, setHeight:true, setLeft:true, setTop:true,
									 			offsetLeft:menu.options.shadowleft, offsetTop:menu.options.shadowtop,
												offsetHeight:menu.options.shadowheightdiff, offsetWidth:menu.options.shadowwidthdiff } );
		}

		DMenu._showWithAncestors( block.id, menuname );
		
		if (menu.options.onmenushow) {
			menu.options.onmenushow( menu, block );
		}

	},
	
	_showWithAncestors: function ( blockid, menuname ) {
		// Show exactly this block and its ancestors.
		var menu = DMenu._list[ menuname ];
		var useshadow = menu.useshadow; 
		var keepids = {};


		var ancestorids = blockid.split( '-' );
		var treeid = ancestorids[0];
		for( var i=1; i<ancestorids.length; i++ ) {
			treeid = treeid + '-' + ancestorids[i];
			keepids[treeid] = treeid;
		}		

		menu.submenulist.each( function( blockid ) {
			if ( keepids[blockid] ) {
				$(blockid).style.visibility="visible";
				$(blockid + '_iframe').style.display="block";
				if (useshadow) { $(blockid + '_shadow').style.visibility="visible"; };
			} else {
				$(blockid).style.visibility="hidden";
				$(blockid + '_iframe').style.display="none";
				if (useshadow) { $(blockid + '_shadow').style.visibility="hidden"; };
			}
		});
		
	},

	hideMenu: function ( menuname ) {
		DMenu._clearresettimer( menuname );
		DMenu._setresettimer( menuname );
	},

	keepMenu: function ( menuname ) {
		DMenu._clearresettimer( menuname );
	},

	addMenu: function( name, menu ) {
		DMenu._list[name] = menu;
	}
});

DMenu.prototype = {
	// Instance methods/properties

	submenusbyname: $H({}),
	submenulist: $A([]),
	
	initialize: function( menuname, menufile, optionshash ) {
		this.options = Object.extend( {
				mainposition:'bottom',
				subposition:'right',
				disappeardelay:100,
				appeardelay:0,
				useshadow:false,
				shadowleft: 5,
				shadowtop:5,
				shadowheightdiff:0,
				shadowwidthdiff:0,
				cssclass:"dmenu",
				onmouseover: "",
				onmousemove: "",
				onmouseout: "",
				onmenushow: "",
				onmenuhide: "",
				substitutions: false
			}, (optionshash || {}) );
		menuname = menuname.toString();
		
		
		this.menudata = null;
		this.menuhtml = null;
		this.disappeardelay = this.options.disappeardelay;
		this.appeardelay = this.options.appeardelay;
		this.mainposition = DMenu._menupositions[this.options.mainposition] || DMenu._menupositions['bottom'];
		this.subposition = DMenu._menupositions[this.options.subposition] || DMenu._menupositions['right'];
		this.showtimer = null;
		this.useshadow = (this.options.useshadow == true);
		this.submenuoptions = {};
		
		var newmenu = this;
		
		if ( typeof(menufile) == 'string' ) {		
			var ajax = new Ajax.Request(menufile, {
				method: 'get',
				onSuccess: function(transport) {
					if(! newmenu.menuhtml) {	
						newmenu._buildmenu( menuname, transport.responseText );
					};
				},
				onFailure: function(transport) {
					alert("Could not load the menu - the response was " + transport.status + " " + transport.statusText );
				}
			});	
		} else {
			newmenu._buildmenufromobject( menuname, menufile );
		}

	},

	
	_buildmenu: function( menuname, menucontent ) {
		
	    try {
		    eval( "this.menudata = " + menucontent);
		} catch (e) {
		    alert( "Could not load menu: " + e.message );
		}
		this.menuname = menuname;
		this.menuhtml = null;

		DMenu.addMenu( menuname, this );

		Event.observeDOMReady( function() { DMenu._list[menuname]._insert() } );		
	},

	_buildmenufromobject: function( menuname, menudataobject ) {
		
		this.menudata = menudataobject;
		this.menuname = menuname;
		this.menuhtml = null;

		DMenu.addMenu( menuname, this );

		Event.observeDOMReady( function() { DMenu._list[menuname]._insert() } );		
	},

	_constructmenuhtml: function( menudata, localmenuname, mainmenuname, cascadingsubposition, depth ) {
	    if (this.menuhtml) { return this.menuhtml; };
		var val;
		var submenus = {};
		var shadowhtml = "";
		var menuclass = this.options.cssclass;
		depth = depth || 0;
		if (this.useshadow) { shadowhtml = "<div class='" + menuclass + "' id='dmenu_" + localmenuname + "_shadowblock' style='position:absolute;overflow:visible;visibility:hidden;display:block;z-index:4950;'><div class='shadow' id='dmenu_" + localmenuname + "_shadow' style='position:absolute;display:block;visibility:hidden'></div></div>"; };
		
	
		var result = "<iframe style='position:absolute;display:none;visibility:visible;z-index:4900;border:none;padding:0;margin:0;' id='dmenu_" + localmenuname + "_iframe'></iframe>" + shadowhtml + "<ul class='" + menuclass + " @@" + localmenuname + "_class@@' style='position:absolute;display:block;visibility:hidden;z-index:5000;' id='dmenu_" + localmenuname + "' onMouseOver='DMenu.keepMenu(\"" + mainmenuname + "\");' onMouseMove='DMenu.keepMenu(\"" + mainmenuname + "\");' onMouseOut='DMenu.hideMenu(\"" + mainmenuname + "\");'>\n";
		var subcount = 0;
		
		// Make sure blank menus are hidden entirely - no orphaned borders, etc.
		if ( Object.keys(menudata).length < 1 ) {
			if (this.useshadow) { shadowhtml = "<div class='" + menuclass + "' id='dmenu_" + localmenuname + "_shadowblock' style='position:absolute;display:none;visibility:hidden;z-index:4950;'><div class='shadow' id='dmenu_" + localmenuname + "_shadow' style='position:absolute;display:none;'></div></div>"; };
			return "<iframe style='position:absolute;display:none;visibility:visible;z-index:4900;border:none;padding:0;margin:0;' id='dmenu_" + localmenuname + "_iframe'></iframe>" + shadowhtml + "<ul class='" + menuclass + "' style='position:absolute;display:none;visibility:hidden;z-index:5000;' id='dmenu_" + localmenuname + "' onMouseOver='DMenu.keepMenu(\"" + mainmenuname + "\");' onMouseMove='DMenu.keepMenu(\"" + mainmenuname + "\");' onMouseOut='DMenu.hideMenu(\"" + mainmenuname + "\");'>\n</ul>\n";
		}
		
		// Build the menu contents recursively.
		var _subposition = cascadingsubposition || this.subposition;
		var _position = (depth < 2) ? this.mainposition : _subposition;
		this.submenuoptions["dmenu_" + localmenuname] = { position:_position, subposition:_subposition, cssclass:"", link:"javascript:void(0);" }; // Accessed by block ID
		var localoptions = this.submenuoptions["dmenu_" + localmenuname];
		for( var prop in menudata ) {
			val = menudata[prop];
			if (val instanceof String ) {
				result += "<li onMouseOver='DMenu.keepMenu(\"" + mainmenuname + "\");'><a href='" + this.processlink(val) + "' onMouseOver='DMenu._showWithAncestors(\"dmenu_" + localmenuname + "\", \"" + mainmenuname + "\") '>" + prop + "</a></li>\n";			
			} else if (val instanceof Object ) {				
				if ( prop == '_options' ) {
					localoptions = Object.extend( localoptions, val ); 
				} else {
					subcount++;
					result += "<li class='submenu' onMouseOver='DMenu.keepMenu(\"" + mainmenuname + "\");;'><a href='javascript:void(\"@@dmenu_" + localmenuname + "-" + subcount + "_link@@\");' onMouseOver='DMenu.showMenuBlock(\"dmenu_" + localmenuname + "-" + subcount + "\", \"" + mainmenuname + "\", this, true)'>" + prop.toString().escapeHTML() + "</a></li>\n";
					submenus[ localmenuname + "-" + subcount ] = val;
					this.submenusbyname[ '*' + prop ] = "dmenu_" + localmenuname + "-" + subcount; // The '*' salt ensures no collision with Hash functions.
					this.submenulist.push("dmenu_" + localmenuname + "-" + subcount);
				}
			} else {
				result += "<li onMouseOver='DMenu.keepMenu(\"" + mainmenuname + "\");'><a href='" + this.processlink(val.toString()) + "' onMouseOver='DMenu._showWithAncestors(\"dmenu_" + localmenuname + "\", \"" + mainmenuname + "\") '>" + prop + "</a></li>\n";			
			}
		}
		result += "</ul>\n";
		
		result = result.replace( "@@" + localmenuname + "_class@@", "" + localoptions["cssclass"] );
		
		if ( ! ( (localoptions.position) instanceof Object ) ) { localoptions.position = DMenu._menupositions[localoptions.position]; };
		if ( ! ( (localoptions.subposition) instanceof Object ) ) { localoptions.subposition = DMenu._menupositions[localoptions.subposition];  };

		for( var subname in submenus ) {
			result += this._constructmenuhtml( submenus[subname], subname, mainmenuname, localoptions.subposition, depth+1 );
		}

		return result;
	},

	showMenu: function( submenuname, callingelement ) {
	    if (this.showtimer) {
	        clearTimeout(this.showtimer);
	    }
	    var target = this;
		this.showtimer = setTimeout(function(){ target._executeShowMenu( submenuname, callingelement); } , this.appeardelay );
    },
    
	_executeShowMenu: function( submenuname, callingelement ) {
	    this.showtimer = null;
		if ( this.submenusbyname['*' + submenuname] ) {
			DMenu.showMenuBlock( this.submenusbyname['*' + submenuname], this.menuname, callingelement, false );
		}
	},

	hideMenu: function( ) {
	    if (this.showtimer) {
	        clearTimeout(this.showtimer);
	        this.showtimer = null;
	    }
		DMenu._setresettimer( this.menuname );
	},
	
	reset: function () {
		var useshadow = this.useshadow;
		this.submenulist.each( function( value ) {
			var blockid = value;
			DMenu._hidemenublockdirect( blockid, useshadow );
		});
		if (this.options.onmenuhide) {
			this.options.onmenuhide( this );
		};
	},
	
    _insert: function () {
	    if (! this.menuhtml) {
			this.menuhtml = this._constructmenuhtml( this.menudata, this.menuname, this.menuname );
		}
		var menu=this;
		menu.submenulist.each( function ( value ) {
			var localoptions = menu.submenuoptions[value];
			if (localoptions) {
				menu.menuhtml = menu.menuhtml.replace("javascript:void(\"@@" + value + "_link@@\");",  menu.processlink(localoptions.link) );
			}
		} );
    	new Insertion.Top( document.body, this.menuhtml );
		this.submenulist.each( function( value ) {
			var block = $(value);
			if( menu.options.onmouseover && block ) { Event.observe( block, 'mouseover', menu.options.onmouseover ) };
			if( menu.options.onmousemove && block ) { Event.observe( block, 'mousemove', menu.options.onmousemove ) };
			if( menu.options.onmouseout && block ) { Event.observe( block, 'mouseout', menu.options.onmouseout ) };			
		});
	},
    
	processlink: function( link ) {
		var sublist = this.options.substitutions;
		if ( ! sublist ) { return link; }
		
		for( var subst in sublist ) {
			var re = new RegExp( ("%" + subst + "%" ), "g" );
			link = link.replace( re, sublist[subst] );
		}
		
		return link;
	}
	
};	

// Compatibility
DMenu.prototype.showmenu = DMenu.prototype.showMenu;
DMenu.prototype.hidemenu = DMenu.prototype.hideMenu;