/**
* 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";
var STYLECLASS_HASSUBNAV = "ofSubnav";
var NAVITEM_OUT_DELAY = 100;  // out must occur after over in order for code to know if parent should not mouseOut
var NAVITEM_OVER_DELAY = 100;
var NAVREGION_OUT_DELAY = 1000;

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;
	}
}

// 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;

	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
        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);
    }
}

/**
* @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 {
            //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);
    }
}


/* ---------------------------------------------------------- */
//
//
//
//
//
//
//
/* ---------------------------------------------------------- */


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) {
			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);
			}
	}

	// Add class if Subnav
    if(this.hasSubnav) {
		addClass(this.li,STYLECLASS_HASSUBNAV);
	}

	attachEventHandlersItem(this); // 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);
        addClass(me.li,"ofOver");

        if(IS_DEBUG)debug('<b>ItemOver</b>: ' + me.li.id);
		//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,"ofOver");
           // }
        } 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
/* overridden in ofxNavOop_accordion.js
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];
		}
	}
}
*/

// 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() {
  var a = this.getChildA();
  if(a!=null) {
    var e=a;
    xAddEventListener(e, 'mouseover', onFireParent, false);
    xAddEventListener(e, 'mouseout', onFireParent, false);
    //xAddEventListener(e, 'click', onOverFireParent, false); // not needed, as IE does pass the click event
  }
}

// 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 = setMousingOut;
function setMousingOut() {
    if(this.outTimer==null) {
        this.getMasterNav().setMousingOut();
        this.cancelOverTimer();
        this.setOutTimer();
        if(IS_DEBUG)debug("set out timer: " + this.li.id + " (" + new String(this.outTimer).substring(5,8) + ")");
    }
}

NavItem.prototype.setMousingOver = setMousingOver;
function setMousingOver() {
    this.getMasterNav().setMousingOver();
    this.cancelOutTimer("self");
	var stack = this.getMasterNav().stack;
    if(this.overTimer==null) {
        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 && 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);
	}
}


/* standard NavJS overrides for Accordion nav */

var SLOW_DURATION = 500;
var EXPAND_COUNT_CONTEXT = 0;
// was boolean, but turns out that it must run 2x at instant
var HREF = location.href;


// private
// this override accounts for the fact that top-level LIs have the A nested inside a DIV
NavItem.prototype.getChildA = function()
{
	var myKids = this.li.childNodes;

	if (!this.isToplevel)
	{
		for (var x = 0; x < myKids.length; x++)
		{
			if (myKids[x].tagName == "A")
			{
				return myKids[x];
			}
		}
	}
	else
	{
		// top-level override
		var childDiv = this.li.childNodes[0];
		if (childDiv.tagName != "DIV") alert('Accordion override of getChildA failed to find child DIV of toplevel LI: ' + this.li.id);
		myKids = childDiv.childNodes;

		for (var x = 0; x < myKids.length; x++)
		{
			if (myKids[x].tagName == "A")
			{
				return myKids[x];
			}
		}

		alert("Couldn't find child A of DIV for: " + this.li.id);
	}
}


function getPageId()
{
	var pageID = HREF.toLowerCase();
	var iSep = pageID.lastIndexOf("/");
	var iDot = pageID.lastIndexOf(".");
	pageID = pageID.substring(iSep + 1, iDot);

	return pageID;
}

function getPageIndex()
{
	return pageMap[getPageId()];
}

function initSelected()
{ // todo: adding ofSelected class should be part of NavItem oop
	EXPAND_COUNT_CONTEXT = 0;
	var li = findParentLI(PAGE_ID.toLowerCase());
	addClass(li, "ofSelected");
}

function findParentLI(fileNameOfHrefLink)
{ // todo: what if the path is a servlet; i.e., no filename
	var allLinks = document.getElementsByTagName("A");

	for (x = 0; x < allLinks.length; x++)
	{ // todo: there could be multiple A's on page with same link, but only one will have the parent LI
		var a = allLinks[x];
		var href = a.getAttribute("HREF").toLowerCase();

		if (href.indexOf(fileNameOfHrefLink) != -1)
		{ // todo: could get false positive -- should look just at the end of string
			var li = findAncestor(a, "LI");
			return li;
		}
	}
}

// When accordion is stickyState, we want the accordion to load
// with the particular toplevel nav already open.
// To emulate, set duration of expand to 0
function isExpanding(inProgressTopLevelItemIndex, initIndex)
{
	if (inProgressTopLevelItemIndex == initIndex)
	{
		return true;
	}
	else return false;
}


// obj.el = UL
/*function getSlideDuration(obj) {
	var topLevelItem = findTopLevelItem(obj.el);

	if(isExpanding(topLevelItem.itemIndex, INIT_TOPLEVEL_INDEX) && EXPAND_COUNT_CONTEXT<=1) {
		//alert("will do 0 for: " + topLevelItem.li.id);
		EXPAND_COUNT_CONTEXT++;
		return SLOW_DURATION;
	} else {
		return SLOW_DURATION; // todo: clean up and externalize these two values
	}
}
*/


function getSlideDuration(obj)
{
	var topLevelItem = findTopLevelItem(obj.el);

	if (isExpanding(topLevelItem.itemIndex, INIT_TOPLEVEL_INDEX))
	{
		if (EXPAND_COUNT_CONTEXT <= 1)
		{
			//alert("will do 0 for: " + topLevelItem.li.id);
			EXPAND_COUNT_CONTEXT++;
			return 0;
		}
		else
		{
			return SLOW_DURATION;
		}
	}
	else
	{
		return SLOW_DURATION;
		// todo: clean up and externalize these two values
	}
}

