/** 
* HTML Navbar 
* for use with UL/LI-based markup
* @date: February 8, 2006
* @author: Brian Willy (a348146), Fidelity Investments
*
* @depends: x_event.js, x_core.js, ofClassFunctions.js, ofList.js, ofNavOop_events.js, ofNavOopStatic.js, ofStack.js, ofHashtable.js
*/


// CONSTANTS
var STYLECLASS_NAVROOT = "ofNavRoot"; // added dynamically

// ofHasSubnav is appended to all <li>'s embedded in <li><ul><li>...</li></ul></li> structures
var STYLECLASS_HASSUBNAV = "ofHasSubnav"; // added dynamically
var STYLECLASS_NOSUBNAV = "ofNoSubnav"; // added dynamically
var STYLECLASS_HASSUBNAV_AND_SELECTED = "ofHasSubnavAndOfSelected"; // added dynamically
var STYLECLASS_FIRSTCHILD_AND_SELECTED = "ofFirstChildAndOfSelected"; // added dynamically (requires ofSelected and ofFirstChild classes to be present on tag at time of Nav init.
var STYLECLASS_SELECTED = "ofSelected"; // added dynamically
var STYLECLASS_HOVER = "ofHover"; // added dynamically
var STYLECLASS_HOVER_AND_LASTCHILD = "ofHoverAndOfLastChild";
var STYLECLASS_TOPLEVELHOVER = "ofTopLevelOver";
var STYLECLASS_SELECTED = "ofSelected";
var STYLECLASS_FIRSTCHILD = "ofFirstChild";
var STYLECLASS_LASTCHILD = "ofLastChild";

var NAVITEM_OUT_DELAY = 150;  // out must occur after over in order for code to know if parent should not mouseOut
var NAVITEM_OVER_DELAY = 200;
var NAVREGION_OUT_DELAY = 2000;

var EVENT_CLICK = "click";
var EVENT_MOUSEOVER = "mouseover";
var EVENT_MOUSEOUT = "mouseout";

var EVENTMODE_CLICK2ON_MOUSEOUT = "mixedClickOverMouseOut"; // parents will not trigger mouseout, only leaves (unless menu already open)
var EVENTMODE_CLICK = "clickMode";
var EVENTMODE_OVEROUT = "overOut"; // default


var IS_DEBUG = false;


/**
Facilitates the lookup of the nav master from a native UL or LI event
Allows for a single page to have multiple master navbar object.
*/
function NavMasters() {
	this.list = new Array();
}

NavMasters.prototype.addNavMaster = function(oNavbar)
	{ this.list[oNavbar.ul.id]=oNavbar; }

/**
@ return a Navbar master that is the parent of the param
*/
NavMasters.prototype.getNavMaster = function(anyChildEle) {
	var rootUl = null;
	rootUl = this._getNavMaster(anyChildEle);

	if(rootUl == null) alert("Failed to find master nav for ele: " + anyChildEle);
	else {
		var navbar = this.list[rootUl.id];
		if(navbar == null) alert("Failed to find master nav for ele: " + anyChildEle);
		return navbar;
	}
}


// Seems to work on click if <UL><LI>
// recursive
// find parent UL based on classname
NavMasters.prototype._getNavMaster = function(ele)
{
	if(ele==null) return null;

    else if(ele.tagName!="UL")
		{ return this._getNavMaster(ele.parentNode); }

	else if(hasClass(ele,STYLECLASS_NAVROOT))
		{ return ele; }

	else
		{return this._getNavMaster(ele.parentNode); }
}


/* ---------------------------------------------------------- */
//
/* ---------------------------------------------------------- */


/**
constructor takes UL id string, or UL object
@param oNavMasters is a list object that contains one or more root Navbar objects. Use to match browser event with correct Navbar.
*/
function Navbar(sUlIdOrUlEle, oNavMasters) { 

	if(NAVITEM_OUT_DELAY < 100 || NAVITEM_OVER_DELAY < 100) {
	    alert("Configuration error: delays should be greater than 100 to prevent mouseOut even when moving directly from parent to child (this could happen if there were any space at all between the two).");
	}

	// props
  var me = this; // required for callbacks such as delay/timeout
  this.id = null;
	this.ul = null;
	this.isRoot = false;
	this.parentNavItem = null;
	this.currentItem = null; // delayed item that is truly in a mouseOver state
  this.stack = new Stack(); // runtime
  this.navTimeout = null;
	this.nextSiblingItemIndex = 0;
	this.attachEventHandlersMode = EVENTMODE_OVEROUT; // default is mouseover. Override with setter.

	if(typeof sUlIdOrUlEle=="string" || oNavMasters!=null) { // root
		this.id = sUlIdOrUlEle;
		this.ul = xGetElementById(this.id);

		if(this.ul==null || this.ul.tagName!="UL") alert("Nav init failed. Confirm that the ID passed in the creation of the Navbar is correctly matched to the root UL.");

		this.isRoot = true; // assumed root, since root is set w/ string
		addClass(this.ul,STYLECLASS_NAVROOT); // used by native events to find object counterpart

        if(IS_DEBUG) debug(this.ul.id + " classes: " + this.ul.className);

        oNavMasters.addNavMaster(this);
    } else { // subnav
		this.ul = sUlIdOrUlEle;
	}
	this.name; // not used
	this.items = new List(); // type:NavItem
  this.isInit=false;

	// methods
	this.getId= function() { return this.id };
	this.getName = function() { return this.name };
	this.setName = function(name) { this.name = name };
	this.getItems=function() { return this.items };

    /**
    * Called as a setTimeout, thus must use ME not THIS
    */
    this.clearStack = clearStack;
    function clearStack() {
        cleanStack(me.stack);     
    }
}

// private
Navbar.prototype.assignChildren = function() {
	var myKids = this.ul.childNodes;

	for(var x=0; x<myKids.length; x++) {
		if(myKids[x].tagName=="LI")	{
			var navItem = new NavItem(myKids[x]);
			if(this.isRoot) {
				navItem.isToplevel = true;
                navItem.setMasterNav(this);
      }	else {
			navItem.parentNavItem = this.parentNavItem;
            navItem.setMasterNav(findMasterNav(navItem));
      }

			navItem.init();
			navItem.getMasterNav().items.add(navItem);
			navItem.itemIndex = this.nextSiblingItemIndex++;
		}
	}
}

Navbar.prototype.init=init;
function init() {

		try {
        if(this.ul==undefined) {
            this.isInit=false;
            throw("Cannot initialize a Nav UL that has no master UL.");
        } else {
            this.assignChildren();
            this.isInit=true;
            return true;
        }
    } catch(e) {
        alert("Failed to init master nav: " + e.message);
    }
}

/**
* @param li must be an LI
* @return NavItem
*/
Navbar.prototype.getItem = getItem;
function getItem(li) {
    var list = this.items.getList();
    for(x=0; x<this.items.size; x++) {
        if(list[x].li == li) { return list[x]; }
    }

    throw "Couldn't find Item that matches LI.";
}

Navbar.prototype.setStack = setStack;
function setStack(navItem) {
    var tempTop = this.stack.pop(); // read the top value
    if(tempTop!=-1)
		this.stack.push(tempTop); // and then put it back

    if(isSame(navItem,this.stack.stack[this.stack.stack.length-1]))
		{ // do nothing
        if(IS_DEBUG)debug("stack same for: " + navItem.li.id);
        return;
    }	else if(navItem.isToplevel)	{
        if(tempTop==-1) { // stack is empty
            if(IS_DEBUG)debug("stack is empty for: " + navItem.li.id);
            this.stack.push(navItem);
            return;
				}	else { // new top-level item
            if(IS_DEBUG)debug("stack clean for: " + navItem.li.id);
            cleanStack(this.stack);
            this.stack.push(navItem);
            return;
				}
    }	else if(navItem.parentNavItem==tempTop)	{ // if new item is child of previous
        if(IS_DEBUG)debug("stack child for: " + navItem.li.id)
        this.stack.push(navItem);
        return;
    }	else if(areSiblings(navItem,tempTop))	{
			if(IS_DEBUG)debug("stack siblings for: " + navItem.li.id);
			var pop = this.stack.pop();
			pop.mouseout("pop sibling for: " + navItem.li.id);
			this.stack.push(navItem);
		}	else {
			// clear possible sibling decendants, or decendants
			var notYetSibling = true;
			while(notYetSibling) {
				if(IS_DEBUG)debug("popping sibling and its decendants...");
				var top = this.stack.pop();
				if(top!=-1)	{
					top.mouseout("pop loop: " + top.li.id);
					if(areSiblings(navItem, top) || isSame(navItem, top))	{
						notYetSibling = false;
						if(IS_DEBUG)debug("pop loop is complete");
					}
				}	else {
					break;
				}
			}

			this.stack.push(navItem);
		}
}



Navbar.prototype.setMousingOver = function() {
    try {
        if(this.navTimeout!=null) {
            if(IS_DEBUG)debug("clear navOut timeout");
            clearTimeout(this.navTimeout);
            this.navTimeout = null;
        } else {
            //if(IS_DEBUG)debug("no navOut timeout to clear");
            return false;
        }
    } catch(e) {
        alert("Nav mouseOver failed: " + e);
    }
}

Navbar.prototype.setMousingOut = function() {
    try {
        if(this.navTimeout==null) {
            if(IS_DEBUG)debug("NavOut. Set outTimer.");
            this.navTimeout = setTimeout(this.clearStack, NAVREGION_OUT_DELAY);
        }
    } catch(e) {
        alert("Nav mouseOut failed: " + e);
    }
}

/**
* click or mouseover style/mode
*/
Navbar.prototype.setRespondToEventMode = function(sEventType) {
	/*if(sEventType==null || sEventType==EVENTMODE_OVEROUT) this.attachEventHandlersItem = attachMouseOverEventHandlersItem;
	else if(sEventType==EVENTMODE_CLICK) this.attachEventHandlersItem = attachClickEventHandlersItem;
	else if(sEventType==EVENTMODE_CLICK2ON_MOUSEOUT) this.attachEventHandlersItem = attachHybridClickOutEventHandlersItem;
	else alert("Navbar component was incorrectly configured to respond to an event type that it does not support: " + sEventType);
	  */

	if(sEventType==null || sEventType==EVENTMODE_OVEROUT) this.attachEventHandlersMode = EVENTMODE_OVEROUT;
	else if(sEventType==EVENTMODE_CLICK) this.attachEventHandlersMode = EVENTMODE_CLICK;
	else if(sEventType==EVENTMODE_CLICK2ON_MOUSEOUT) this.attachEventHandlersMode = EVENTMODE_CLICK2ON_MOUSEOUT;
	else alert("Navbar component was incorrectly configured to respond to an event type that it does not support: " + sEventType);
}


/* ---------------------------------------------------------- */
// 
/* ---------------------------------------------------------- */
function NavItem(li) {
	var me = this; // required for callbacks such as delay/timeout
 
    this.li = li;
	this.itemIndex = null; // index value at sibling level only
	this.isToplevel = false;
    this.masterNav = null;
    this.hasSubnav = false;
	this.subNav = null;
	this.parentNavItem = null;
    this.outTimer = null;
    this.overTimer = null;
    this.isOver = false;

    this.init = function() {
		var childUl = this.getChildUl();
		if(childUl!=null && hasChildTag(childUl,"LI")) { // need to also check to see if maybe child UL is empty, as Accordion impl need child UL, even when empty.
			try {
				this.hasSubnav = true;
				this.subNav = new Navbar(childUl);
				this.subNav.parentNavItem = this;
				this.subNav.init();
			} catch(e) {
				alert("Failed to init a subnav: " + e);
			}
		}

        if(IS_DEBUG) this.li.id="fixMe"; // todo:step/sequence the ids for debugging

        this.attachEventHandlersItem = getItemSpecificEventMode(this);

		// Add class if <li> has Subnav... see var STYLECLASS_HASSUBNAV above
		if(this.hasSubnav) {
			addClass(this.li,STYLECLASS_HASSUBNAV);
            if(IS_DEBUG) debug(this.li.id + " classes: " + this.li.className);
        }	else {
            addClass(this.li,STYLECLASS_NOSUBNAV); // required because IE6 can't differentiate between ".ofAccordion ul li.ofHasSubnav.ofSelected div" and ".ofAccordion ul li.ofSelected div", so even single level, when selected has down arrow
		    if(IS_DEBUG) debug(this.li.id + " classes: " + this.li.className);
        }

        if(hasClass(this.li,STYLECLASS_FIRSTCHILD)&&hasClass(this.li,STYLECLASS_SELECTED)) {
            addClass(this.li,STYLECLASS_FIRSTCHILD_AND_SELECTED);
            if(IS_DEBUG) debug(this.li.id + " classes: " + this.li.className);
        }

        if(this.hasSubnav && hasClass(this.li,STYLECLASS_SELECTED)) {
            addClass(this.li,STYLECLASS_HASSUBNAV_AND_SELECTED);
            if(IS_DEBUG) debug(this.li.id + " classes: " + this.li.className);
        }

        this.attachEventHandlersItem(this.li); // Attach events to LI
		this.attachEventHandlersLink();   // Attach events to A (see note for method for explanation)
	}

	this.setSubnav = function(subNavUl)
		{ this.subNav = new subNavUl; }
	
	// must use ME not THIS (as it is called via timeout)
	this.mouseover = mouseover;
	function mouseover() {
		me.overTimer = null;
		me.getMasterNav().currentItem = me;
		me.isOver = true;
		me.getMasterNav().setStack(me);

        if(!(me.isToplevel && !me.hasSubnav)) {
            addClass(me.li,STYLECLASS_HOVER); // don't do this to toplevel that has no subnav (like the HOME button)

            if(hasClass(me.li,STYLECLASS_LASTCHILD)) {
                addClass(me.li,STYLECLASS_HOVER_AND_LASTCHILD);
            }

            if(IS_DEBUG) debug(me.li.id + " classes: " + me.li.className);
        }

        if(IS_DEBUG)debug('<b>ItemOver</b>: ' + me.li.id);
		//if(IS_DEBUG)debugSetColor('green');
   }

	// must use ME not THIS (as it is called via timeout)
	this.mouseout = mouseout;
	function mouseout(msg) {
        try {
          // if(me.isOver) { pr11 -- 2_a_1 --> 2_a --> 2_b is not clearing 2_a_1 menu
					if(msg!=null) msg = "("+msg+"): ";
					else msg = "(direct): ";
					if(IS_DEBUG)debug('<b>ItemOut</b>' + msg + me.li.id);
					me.outTimer=null;
					me.getMasterNav().currentItem = null;
					me.isOver = false;
					removeClass(me.li,STYLECLASS_HOVER);
            removeClass(me.li, STYLECLASS_HOVER_AND_LASTCHILD);
                    if(me.isToplevel) removeClass(me.li, STYLECLASS_TOPLEVELHOVER);
        } catch(e) {alert("mouseOut failed: " + e);}
	}
}

// private
NavItem.prototype.setMasterNav = function(masterNav) {
    this.masterNav = masterNav;
}

// private
// fast. Can be used for mouseOver
NavItem.prototype.getMasterNav = function() {
    return this.masterNav;
}

// slow. Use only at init
// @see getMasterNav
NavItem.prototype.findMasterNav = findMasterNav;
function findMasterNav(navItem) {
    if(navItem.isToplevel) {
        return navItem.getMasterNav();
    } else {
        return navItem.findMasterNav(navItem.parentNavItem);
    }
}

// private
// @deprecated: use getChildTag(tag)
NavItem.prototype.getChildA = function() {
  	var myKids = this.li.childNodes;

	for(var x=0; x<myKids.length; x++) { 
		if(myKids[x].tagName=="A") {
			return myKids[x];
		}
	}
}

NavItem.prototype.getChildTag = function(sTag) {

  var myKids = this.li.childNodes;

	for(var x=0; x<myKids.length; x++) {
		if(myKids[x].tagName==sTag) {
			return myKids[x];
		}
	}
}

// private
NavItem.prototype.getChildUl = function() {
	// return first UL encountered
	if(this.li==null || this.li==undefined) {
		return null;
	}

	var myKids = this.li.childNodes;

	for(var x=0; x<myKids.length; x++) {
		if(myKids[x].tagName=="UL") {
			return myKids[x];
		}
	}

	return null;
}

// private
// Due to IE's handling of mouseOver and mouseOut, a "hack" is needed
// <a> tags are usually found w/in <li>, but not always. When the <a> within an <li> is clicked, the <li>
// event is fired, but this is not true for mouseOver and mouseOut.
// Thus, I must manually fire the <li> event for the <a> event.


NavItem.prototype.attachEventHandlersLink = function() {
	// for 'a' tags
	var a = this.getChildTag("A");
	if(a!=null) {
		this.attachEventHandlersItem(a);
	}

	// for div tags
	var div = this.getChildTag("DIV");
	if(div!=null)	{
		this.attachEventHandlersItem(div);
	}
}



// exists so that the mouse out is logged, such as on a subsequent mouseOver of the same item
// will cause the mouseOut timer to be cancelled.
// This should happen in the case of IE, when a mouseOver of the A w/in an LI gives a mouseOut of the LI
// even though one is still really over the LI.
/**
* "fires" immediately and does not wait for the timeout
*/
NavItem.prototype.setMousingOut = function() {
	if(this.outTimer==null) {
		this.getMasterNav().setMousingOut();
		this.cancelOverTimer();
		if(!this.isToplevel) this.setOutTimer(); // added so that mousing out of nav alltogether from toplevel won't hide nav at the standard itemout interval, but rather at the "all-out" interval. Odd that this was needed, as mousing out from a sub-item into oblvion still waited the all-out time, not the shorter time. 
		if(IS_DEBUG)debug("set out timer: " + this.li.id + " (" + new String(this.outTimer).substring(5,8) + ")");
	}
}

NavItem.prototype.setMousingOver = setMousingOver;
function setMousingOver(xEvent) {
	var stack = this.getMasterNav().stack;

	this.getMasterNav().setMousingOver(xEvent);

	this.cancelOutTimer("self");

  if(this.overTimer==null) {
		addParentToStack(this, stack, xEvent);   // In certain nav configurations, a user can mouseover a subitem w/o first being over its parent

		this.setOverTimer();
		if(!this.isToplevel) {
	  	this.parentNavItem.cancelOutTimer("by child " + this.li.id);
		}
		if(IS_DEBUG)debug("set over timer: " + this.li.id + " (" + new String(this.overTimer).substring(5,8) + ")");
  } else if(stack.stack.length>1) if(IS_DEBUG)debug("not doing 2x over: this(" + this.li.id + "), top(" + stack.stack[stack.stack.length-1].li.id + ")");
}

NavItem.prototype.calcNodeFromTop = calcNodeFromTop;
function calcNodeFromTop() {
	var notTop = true;
	var fromTop = 0;
	var walkNode = this;
	while(notTop) { 
			if(walkNode.isToplevel) {
					notTop=false;
			} else {
					walkNode = walkNode.parentNavItem;
					fromTop++;
			}
	}

	// alert("From top: " + fromTop);

	return fromTop;
}

NavItem.prototype.setOutTimer = function() {
    this.outTimer = setTimeout(this.mouseout, NAVITEM_OUT_DELAY);
}

NavItem.prototype.cancelOutTimer = function(msg) {
    if(this.outTimer != null) {
        clearTimeout(this.outTimer);
        if(IS_DEBUG)debug("cancelled outTimer (" + msg + "): " + this.li.id + " (" + new String(this.outTimer).substring(5,8) + ")");
        this.outTimer = null;
    }
}

NavItem.prototype.setOverTimer = function() {
    this.overTimer=setTimeout(this.mouseover, NAVITEM_OVER_DELAY);
}

NavItem.prototype.cancelOverTimer = function() {
    if(this.overTimer != null) {
        clearTimeout(this.overTimer);
        if(IS_DEBUG)debug("cancelled overTimer: " + this.li.id + " (" + new String(this.overTimer).substring(5,8) + ")");
        this.overTimer = null;
    }
}

NavItem.prototype.getTopLevelItem = function(navItem) {
	if(navItem.isToplevel) {
		return navItem;
	} else {
		return navItem.getTopLevelItem(navItem.parentNavItem);
	}
}


