// global.js
//   general purpose routines/classes

// FUNCTIONS
// get the position of a html element as a 2D vector
function getPos(obj)
{
	if(!obj.offsetParent) 
	{
		return new vector(obj.offsetLeft, obj.offsetTop);
	}
	return getPos(obj.offsetParent).add(new vector(obj.offsetLeft, obj.offsetTop));	
}

function isArray(o)
{
	return (Array && o.constructor && o.constructor == Array);
}

function isType(o, type)
{
    return o && o.constructor && o.constructor == type;
}

// CLASSES
// [class] 2D vector 
function vector(x, y) 
{ 
	this.x = typeof(x) == "undefined" ? 0 : x; 
	this.y = typeof(y) == "undefined" ? 0 : y; 
}
vector.prototype.x = 0; 
vector.prototype.y = 0;
vector.prototype.add = function(v) 		{ return new vector(this.x + v.x, this.y + v.y) }
vector.prototype.subtract = function(v) { return new vector(this.x - v.x, this.y - v.y) }
vector.prototype.multiply = function(v) { return new vector(this.x * v.x, this.y * v.y) }
vector.prototype.divide = function(v)	{ return new vector(this.x / v.x, this.y / v.y) }
vector.prototype.negate = function() 	{ return new vector(-this.x, -this.y) }

// [class] menu
function menu(element, parentMenu)
{
	this.element = element;
	this.menuItems = new Array();
	this.isVisible = true;
	this.parentMenu = parentMenu ? parentMenu : null;
	
	var menuNodes = this.element.childNodes;
	var lastItem;
	for (var i = 0; i < menuNodes.length; i++)
	{
		if (menuNodes[i].tagName == "A") 
		{
			lastItem = new menuItem(menuNodes[i], this);
			this.menuItems.push(lastItem);
		}
		else if (menuNodes[i].tagName == "DIV")
		{				
			lastItem.setSubMenu(new menu(menuNodes[i]), this);
		}
	}
}
menu.prototype.element;
menu.prototype.menuItems;
menu.prototype.isVisible;
menu.prototype.getPos = function() 
{ 
	if (this._position == null) this._position = getPos(this.element);
	return this._position;
}
menu.prototype.show = function() 
{ 
	this.isVisible = true;
	this.element.style.visibility = "visible"; 
}
menu.prototype.hide = function() 
{ 
	for(var i = 0; i < this.menuItems.length; i++)
	{
		if (this.menuItems[i].hasSubMenu()) this.menuItems[i].getSubMenu().hide();
	}	
	this.isVisible = false;
	this.element.style.visibility = "hidden"; 
}
// if multiple menus are visible hide all but the most recently updated
menu.prototype.updateActive = function()
{
	// first pass - find if multiple sub-menus are visible (at this
	// level), determine most recently activated
	var lastActiveTime = 0;
	var activeIndex = -1;
	var visibleMenus = 0;
	for (var i = 0; i < this.menuItems.length; i++) 
	{
    	var item = this.menuItems[i];
    	if (item.hasSubMenu() && item.getSubMenu().isVisible) 
		{
    		visibleMenus++;
			if (item.lastActive > lastActiveTime) 
			{
				lastActiveTime = item.lastActive;
				activeIndex = i;
			}
		}
	}

	// second pass - hide non-active memebers
	if (visibleMenus > 0)
	{
		for(var i = 0; i < this.menuItems.length; i++)
		{
			var item = this.menuItems[i];
			if (item.hasSubMenu() && i != activeIndex) item.getSubMenu().hide();
		}
	}
}
// [class] menuItem
function menuItem(element, parentMenu, subMenu)
{
	this.mouseOutDelay = 800;
	this._parentMenu = parentMenu;
	this._mouseOver = false;
	this._mouseOverSubMenu = false;
	this.active = false;
	this.element = element;
	this.setSubMenu(subMenu);
	this.lastActive = 0;
}
menuItem.prototype._parentMenu;
menuItem.prototype._mouseOver;
menuItem.prototype._mouseOverSubMenu;
menuItem.prototype._subMenu;
menuItem.prototype.lastActive;
menuItem.prototype.element;	
menuItem.prototype.mouseOutDelay;
menuItem.prototype.hasSubMenu = function() { return (this._subMenu != null); }
menuItem.prototype.getSubMenu = function() { return this._subMenu; }
menuItem.prototype.setSubMenu = function(subMenu)
{
	if (subMenu)
	{
		var thisItem = this;
		this._subMenu = subMenu;
		this.element.onmouseover = function() 			
		{ 
			thisItem._updateActivated();
			thisItem._mouseOver = true;
			thisItem._subMenu.show();
			thisItem._parentMenu.updateActive();
		};
		this.element.onmouseout = function() 
		{
			thisItem._mouseOver = false; 
			thisItem.delayedHide();			
			thisItem._parentMenu.updateActive();
		}
		this._subMenu.element.onmouseover = function() 
		{ 
			thisItem._updateActivated();
			thisItem._mouseOverSubMenu = true; 
			thisItem._parentMenu.updateActive();
		}
		this._subMenu.element.onmouseout = function()
    	{
	    	thisItem._mouseOverSubMenu = false;
			thisItem.delayedHide();
			thisItem.element.style.zIndex = "0";
			thisItem._parentMenu.updateActive();
		}		
		this._subMenu.hide();
		if (this._parentMenu)
		{
			this._subMenu.element.style.left = (getPos(this.element).x - this._parentMenu.getPos().x) + "px";
		}
	}
	else
	{
		if (this._subMenu)
		{
			// clear event handlers
			this.element.onmouseover = null;
			this.element.onmouseout = null;
			this._subMenu.element.onmouseover = null;
			this._subMenu.element.onmouseout = null;
		}
		this._subMenu = null;
	}
};

menuItem.prototype.isActive = function()
{
	return (this._mouseOver || (this.hasSubMenu && this._mouseOverSubMenu));
}
// evaluate mouse possition and hide sub-menu after a short delay
menuItem.prototype.delayedHide = function()
{
	var thisItem = this;
	function evaluateHide()
	{
		if (!thisItem.isActive()) { thisItem._subMenu.hide(); }
	}
	if (this.mouseOutDelay <= 0) this.evaluateHide(); 
	else window.setTimeout(evaluateHide, this.mouseOutDelay);
}
menuItem.prototype._updateActivated = function()
{
	this.lastActive = (new Date()).getTime();
}

// [class] style rule that applies rules to elements returned by a selector 
//   - 'selector' is a function that returns an array of elements to apply rules to
//   - 'rules' is a function that applies rules to a single element
function styleRule(selector, rules)
{
    if (!isType(selector, cssSelector)) selector = new cssSelector(selector);
	this.selector = selector || null;
	this.rules = rules || null;
}
styleRule.prototype.apply = function()
{
    //var thisRef = this;
    var items = this.selector.select();
    for(var i = 0; i < items.length; i++)
    {
        this.applyRule(items[i]);
    }
}
styleRule.prototype.applyRule = function(element)
{
    rules(element);
}


// [class] style rule that generates content
//  - create content should be a function that takes an input element and returns a generated element 
function generateContentRule(selector, rules)
{    
	styleRule.call(this, selector, rules)
}
generateContentRule.prototype = new styleRule;
generateContentRule.prototype.applyRule = function(element) // overload
{
	styleRule.call(this, this.createContent(element));
}
generateContentRule.prototype.createContent = function(element) {}


// [class] a style rule which generates an image before the selector
//  - equivalent to SELECTOR:before { url(SRC); RULES } 
function generateBeforeImg(selector, src, rules)
{
	this._src = src || "";
	generateContentRule.call(this, selector, rules);
}
generateBeforeImg.prototype = new generateContentRule;
generateBeforeImg.prototype.createContent = function(element)
{
    // remove any existing generated content
    if (element.firstChild && element.firstChild.isGeneratedContent)
    {
        element.removeChild(element.firstChild);
    }
    element.innerHTML = "<img src=\"" + this._src + "\" />" + element.innerHTML;
    element.firstChild.isGeneratedContent = true;
    return element.firstChild;
}

// [class] parse a css selector to yield a list of elements
function cssSelector(selectors)
{
    if (selectors)
    {
        if(typeof(selectors) == "string") selectors = selectors.split(" ");
        
	    var thisref = this;
	    this._selectors = new Array();

    	for (var i = 0; i < selectors.length; i++)
        {
		    var split = selectors[i].split(".", 2);
		    var tag = split.length > 0 && split[0] != "" ? split[0].toUpperCase() : null;
		    var className = split.length > 1 && split[1] != "" ? split[1] : null;
		    this._selectors.push({ tagName: tag, className: className }); 
	    }
	}
}
cssSelector.prototype.select = function()
{
    function match(selector, element)
    {
		var ret = 
            (!selector.tagName  || element.tagName == selector.tagName) &&
			(!selector.className || element.className == selector.className);
		return ret;
    }

    var matchedElements = new Array();
    var elements = document.getElementsByTagName("*");
    for (var i = 0; i < elements.length; i++)
    {
        var element = elements[i];
        var k = this._selectors.length - 1;        
        
		do 
		{
			// break if all selectors have been matched in sequence
			if (match(this._selectors[k], element) && --k < 0) 
			{
				matchedElements.push(elements[i]);
				break;
			}
			// break if the first match failed
			else if (k == this._selectors.length - 1) break;

		// walk up DOM tree
		} while (element = element.parentNode);
	}
	return matchedElements;
}

function styleRuleList() 
{ 
    this._rules = new Array();
}
styleRuleList.prototype.add = function(rule) 
{ 
    this._rules.push(rule); 
}
styleRuleList.prototype.addBeforeImg = function(selector, src, rule) 
{ 
    this.add(new generateBeforeImg(selector, src, rule)); 
}
styleRuleList.prototype.apply = function()  
{ 
    for(var i = 0; i < this._rules.length; i++) this._rules[i].apply(); 
}

// static objects
var styleRules = new styleRuleList();
