/*
http://www.alistapart.com

http://v2studio.com/k/code/lib/
*/

// ARRAY EXTENSIONS

if (!Array.prototype.push) Array.prototype.push = function() {
    for (var i=0; i<arguments.length; i++) this[this.length] = arguments[i];
    return this.length;
}

Array.prototype.find = function(value, start) {
    start = start || 0;
    for (var i=start; i<this.length; i++)
        if (this[i]==value)
            return i;
    return -1;
}

Array.prototype.has = function(value) {
    return this.find(value)!==-1;
}

// FUNCTIONAL

function map(list, func) {
    var result = [];
    func = func || function(v) {return v};
    for (var i=0; i < list.length; i++) result.push(func(list[i], i, list));
    return result;
}

function filter(list, func) {
    var result = [];
    func = func || function(v) {return v};
    map(list, function(v) { if (func(v)) result.push(v) } );
    return result;
}


// DOM

function getElem(elem) {
    if (document.getElementById) {
        if (typeof elem == "string") {
            elem = document.getElementById(elem);
            if (elem===null) throw 'cannot get element: element does not exist';
        } else if (typeof elem != "object") {
            throw 'cannot get element: invalid datatype';
        }
    } else throw 'cannot get element: unsupported DOM';
    return elem;
}

function hasClass(elem, className) {
    return getElem(elem).className.split(' ').has(className);
}

function getAll(tagName, parent) {
    parent = isdef(parent)? getElem(parent) : document;
    if (undef(tagName)) tagName = '*';
    var r = parent.getElementsByTagName(tagName);
    
    return r.length || tagName != '*'?  map(r) :
        reduce(filterElementNodes(parent.childNodes), [], function(l,c){
            return l.merge([c], getAll(tagName, c))
        })
}

function getElementsByClass(searchClass,tag,node) {
	var classElements = new Array();
	if (node == null) node = document;
	if (tag == null) tag = '*';

	var els = node.getElementsByTagName(tag);
	var elsLen = els.length;

	for (i = 0, j = 0; i < elsLen; i++) {
		if (hasClass(els[i], searchClass)) {
			classElements[j] = els[i];
			j++;
		}
	}

	return classElements;
}

// get parent of the element with the indicated tagName
function getParent(elem, tagName) {
	elem = getElem(elem);

	if (elem) {
		while(elem.parentNode) {
			elem = elem.parentNode;
			if (elem.nodeName && elem.nodeName.toUpperCase() == tagName.toUpperCase()) {
				return elem;
			}
		}
	}
	return null;
}

// WL: utility function to avoid browser inconsistencies
function getElementsByName(name, tagName, parentNode) {
	parentNode = !isUndefined(parentNode)? getElem(parentNode) : document;
	if (isUndefined(tagName)) tagName = '*';

	var elem = parentNode.getElementsByTagName(tagName);
	var arr = new Array();
	var nextIndex = 0
	for(var i = 0; i < elem.length; i++) {
		att = elem[i].getAttribute("name");
		if(att == name) {
			arr[nextIndex] = elem[i];
			nextIndex++;
		}
     }
     return arr;
}

// Revision of Scott Andrew's original addEvent
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
function addEvent( obj, type, fn )
{
	if (obj.addEventListener)
		obj.addEventListener( type, fn, false );
	else if (obj.attachEvent)
	{
		obj["e"+type+fn] = fn;
		obj[type+fn] = function() { obj["e"+type+fn](new W3CDOM_Event(obj)); }
//		obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
		obj.attachEvent( "on"+type, obj[type+fn] );
	}
}

function addEvents( obj, type, fn ) {
	for (var i = 0; i < obj.length; i++) {
		addEvent(obj[i], type, fn);
	}
}

function removeEvent( obj, type, fn )
{
	if (obj.removeEventListener)
		obj.removeEventListener( type, fn, false );
	else if (obj.detachEvent)
	{
		obj.detachEvent( "on"+type, obj[type+fn] );
		obj[type+fn] = null;
		obj["e"+type+fn] = null;
	}
}

// WL: add classes together
function addClass(element,value) {
	if (!element.className) {
		element.className = value;
	} else {
		var newClassName = element.className;
		newClassName += " ";
		newClassName += value;
		element.className = newClassName;
	}
}

function removeClass(element,value) {
	if (element.className) {
		var newClassName = " " + element.className + " ";	// WL: work this out later
		newClassName = newClassName.replace(value, "");
		// trim off spaces
		while (newClassName.charAt(0) == ' ')
			newClassName = newClassName.substring(1);
		while (newClassName.charAt(newClassName.length - 1) == ' ')
			newClassName = newClassName.substring(0, newClassName.length - 1);

		element.className = newClassName;
	}
}

function insertAfter(parent, node, referenceNode) {
	parent.insertBefore(node, referenceNode.nextSibling);
}


// DOM EVENTS

function listen(event, elem, func) {
    elem = getElem(elem);
    if (elem.addEventListener)  // W3C DOM
        elem.addEventListener(event,func,false);
    else if (elem.attachEvent)  // IE DOM
        elem.attachEvent('on'+event, function(){ func(new W3CDOM_Event(elem)) } );		// WL: know that I changed this to window.event, but not sure what didn't work originally
//        elem.attachEvent('on'+event, function(){ func(window.event)});	
        // for IE we use a wrapper function that passes in a simplified faux Event object.
    else throw 'cannot add event listener';
}


// code to find the source of the event
function getEventSource(e) {
	if (typeof e == 'undefined') {
		var e = window.event;
	}
	var source;
	if (typeof e.target != 'undefined') {
		source = e.target;
	} else if (typeof e.srcElement != 'undefined') {
		source = e.srcElement;
	} else {
		return;
	}

	return source;
}

function mlisten(event, elem_list, func) {
//    map(elem_list, function(elem) { listen(event, elem, func) } );
    map(elem_list, function(elem) { addEvent(elem, event, func) } );
}

function W3CDOM_Event(currentTarget) {
    this.currentTarget   = currentTarget;
    this.preventDefault  = function() { window.event.returnValue  = false }
// WL: added these to better support IE
    this.stopPropagation = function() { window.event.cancelBubble = true }
    this.target  = window.event.srcElement;
    var self = this;

    return this;
}


// handle browser inconsistences with XML HTTP object
function getHttpObject()
{ 
	var objXMLHttp = null;
	if (window.XMLHttpRequest) {
		objXMLHttp = new XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		// use latest version available
		try {
			objXMLHttp=new ActiveXObject("Msxml2.XMLHTTP");
		} catch(e) {
			objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
	return objXMLHttp;
}


// MISC CLEANING-AFTER-MICROSOFT STUFF

function isUndefined(v) {
    var undef;
    return v===undef;
}

// format numbers with group separators
function formatNumber(n, groupSeparator, isCurrency, currencySymbol, decimalSeparator) {
	var isNegative = false;

	if (decimalSeparator == null) {
		decimalSeparator = '.';
	}

	// check for negative
	if (n < 0) {
		isNegative = true;
		n = n * -1;
	}

	var formattedNumber;
	var numberString = n.toString();
	var decimalPortion = '';
	if (numberString.indexOf(decimalSeparator) >= 0)
		decimalPortion = numberString.substring(numberString.indexOf(decimalSeparator));

	var numOctets = ((Math.floor(n)).toString().length - 1)/3;
	numOctets = Math.floor(numOctets);

	for (i = numOctets; i >= 0; i--) {
		if (i == numOctets) {
			formattedNumber = Math.floor(n/Math.pow(1000,i));
		} else {
			var octet = Math.floor(n/Math.pow(1000,i)).toString();
			if (octet.length == 1) {
				octet = "00" + octet;
			} else if (octet.length == 2) {
				octet = "0" + octet;
			}
			formattedNumber += groupSeparator + octet;
		}
		n = n%(Math.pow(1000,i));
	}

	if (isCurrency) {
		formattedNumber = currencySymbol + formattedNumber;
	}

	// add in decimals
	if (decimalPortion != '' || isCurrency) {
		numberString = decimalPortion.toString();
		formattedNumber = formattedNumber + decimalPortion;
	}

	// restore negativeness
	if (isNegative) {
		formattedNumber = "-" + formattedNumber;
	}

	return formattedNumber;
}

// from quirksmode.org
// using this only to detect Safari for purposes of working around lack of event.preventDefault() support
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{	// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 	// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();

