//
// To Whom It May Concern:
//
// 1) The methods of Browser are declared in the global namespace rather than being
//    encapsulated in the Browser namespace. This OO-design error should be fixed, but
//    before removing the methods from the global namespace, make sure that they are
//    not being used from the global namespace!
//
// 2) The summer 2010 update of minimum browser requirements renders isFlashFriendly
//    obsolete. There are differences in how different browsers handle embedding
//    (best resolved IMHO with SWFObject) and the support for features such as 
//    ExternalInterface and Flash Methods (Play, StopPlay, etc). It is probably best
//    not to use browser string-based decisions about whether to use these features.
//
// 3) Browser string-based branching is a generally deprecated method in the JS dev 
//    world. It would be a good idea to encapsulate functionality that varies from
//    browser to browser (such as AJAX and object embedding) and delegate the browser-
//    dependant branching to these dedicated objects. This minimizes the maintenance
//    required to adapt to future browsers and allows the encapsulated code to use
//    the best approach for the given problem.
//
// 4) It would be a good idea to replace inline and traditional event binding to 
//    DOM-based event binding using new DOM-based event binding methods added to
//    the Browser class.
//
// 5) Some of Browser's methods make more sense as static methods than instance methods.
//
// Michael Volk (mvolk@volksys.com)
// August 3, 2010
//

function Browser() {
	this.browserString = navigator.userAgent;

	this.UNKNOWN = 'x';
	this.IE = 'i';
	this.NETSCAPE = 'n';
	this.MOZILLA = 'm';
	this.SAFARI = 's';
	this.FIREFOX = 'f';
	this.browserType = this.UNKNOWN;
	this.browserName = "Unknown";

	this.WINDOWS = 'w';
	this.MAC = 'm';
	this.UNIX = 'u';
	this.LINUX = 'l';

	this.osType = this.UNKNOWN;
	this.osName = "Unknown";

	this.version = 0.0;
	this.versionString = "0.0";

	// parse browser string
	this.parseString = parseBrowserString;
	this.parseString();

	this.isQualified = false;
	
	// MV: OUTDATED and retained for reverting if necessary
	//// temp allow safari without version check
	//if (this.browserType == this.SAFARI || (this.browserType == this.IE && this.version >= 5.0) || (this.browserType == this.IE && this.osType == this.MAC && this.version >= 5.1) || (this.browserType == this.NETSCAPE && this.version >= 6.2) || (this.browserType == this.MOZILLA && this.version >= 1.0) || (this.browserType == this.FIREFOX && this.version >= 1.0)) {
	//	this.isQualified = true;
	//}

	// MV: New 7-19-2010
	if ((this.browserType == this.SAFARI && this.version >= 525.0) || (this.browserType == this.IE && this.version >= 6.0) || (this.browserType == this.MOZILLA && this.version >= 1.9) || (this.browserType == this.FIREFOX && this.version >= 2.0)) {
		this.isQualified = true;
	}

	// for backward compatibility
	this.isIE = (this.browserType == this.IE);
	this.isIE5 = (this.isIE && this.version < 6.0 && this.version >= 5.0);
	this.isIE5Mac = (this.isIE && this.osType == this.MAC && this.version < 6.0);
	if (this.isIE && this.version < 5.5) undefined = null;
	this.isIE55 = (this.isIE5 && this.version >= 5.5);
	this.isIE6 = (this.isIE && this.version >= 6.0);
	this.isNN = (this.browserType == this.NETSCAPE);
	this.isNN6 = (this.isNN && this.version >= 6.0);

	// MV on 7-19-2010: this should be revisited 
	this.isFlashFriendly = (this.browserType != this.SAFARI && this.browserType != this.MOZILLA && !this.isIE5);
	
	this.handCursor = ((this.isIE && this.version < 6.0) ? "hand" : "pointer") ;

	/*
	//var ie = (navigator.appName.search(/Microsoft Internet Explorer/) >= 0);
	var userAgent = navigator.userAgent;
	this.isIE5 = (userAgent.search(/MSIE 5/) >= 0);
	this.isIE5Mac = (this.isIE5 && userAgent.search(/Macintosh/) >=0);
	this.isIE55 = (userAgent.search(/MSIE 5\.[5-9]/) >= 0) ;
	this.isIE6 = (userAgent.search(/MSIE 6/) >= 0);
	this.isIE = (this.isIE5 || this.isIE55 || this.isIE5Mac || this.isIE6);
	nn = (navigator.appName == "Netscape");
	this.isNN6 = (nn && userAgent.charAt(0) >= "5");
	this.isNN = nn;
	this.isQualified = (this.isNN || this.isIE);
	*/

	this.viewWidth = 0;
	this.viewHeight = 0;

	this.refreshViewSize = browserRefreshViewSize;

	this.getViewHeight = browserGetViewHeight;
	this.getViewWidth = browserGetViewWidth;

	this.hasDoneFlashDetection = false;
	this.flashInstalled = false;
	this.flashVersion = 0;

	this.detectFlash = browserDetectFlash;
	this.isFlashInstalled = browserIsFlashInstalled;
	this.getFlashVersion = browserGetFlashVersion;

	this.encodeURL = browserEncodeURL;
	this.getCookie = browserGetCookie;
	this.setCookie = browserSetCookie;

}

function parseBrowserString() {
	var start = 0;

	var bs = this.browserString;
	var ieIndex = bs.indexOf("MSIE");
	var geckoIndex = bs.indexOf("Gecko");
	var nnIndex = bs.indexOf("Netscape");
	var safariIndex = bs.indexOf("Safari");
	var firefoxIndex = bs.indexOf("Firefox");
	
	if (ieIndex >= 0){
		this.browserType = this.IE;
		this.browserName = "Internet Explorer";
		if (bs.indexOf("Mac") >= 0) {
			this.osType = this.MAC;
			this.osName = "Mac";
		} else {
			this.osType = this.WINDOWS;
			this.osName = "Windows";
		}
		start = ieIndex;
	} else if (geckoIndex >= 0) {
		if (nnIndex >= 0) {
			this.browserType = this.NETSCAPE;
			this.browserName = "Netscape";
			start = nnIndex;
		} else if (firefoxIndex >= 0) {
			this.browserType = this.FIREFOX;
			this.browserName = "Firefox";
			start = firefoxIndex;
		} else if (safariIndex >= 0) {
			this.browserType= this.SAFARI;
			this.browserName = "Safari";
			start = safariIndex;
		} else {
			this.browserType = this.MOZILLA;
			this.browserName = "Mozilla";
			start = bs.indexOf("rv:");
		}
		if (bs.indexOf("Linux") >= 0) {
			this.osType = this.LINUX;
			this.osName = "Linux";
		} else if (bs.indexOf("Windows") >= 0) {
			this.osType = this.WINDOWS;
			this.osName = "Windows";
		} else if (bs.indexOf("Mac") >= 0) {
			this.osType = this.MAC;
			this.osName = "Mac";
		}
	}

	// try to find the version with regular expression.
	// IE 8 may report version as MSIE 7.0
	bs = bs.substring(start);
	var regExp = /\d+[.]\d*/g;
	var vers = regExp.exec(bs);
	if (vers != null) {
		this.version = parseFloat(vers[0]);
	}

	regExp = /\d+[.]\d*[.]?\d*/g;
	vers = regExp.exec(bs);
	if (vers != null) {
		this.versionString = vers[0];
	}

/*
	Matcher vm = Pattern.compile("\\d+[.]\\d*").matcher(browserString);
	if (vm.find(start)) {
		String vers = vm.group();
		version = Double.parseDouble(vers);
	}
	vm = Pattern.compile("\\d+[.]\\d*[.]?\\d*").matcher(browserString);
	if (vm.find(start)) {
		versionString = vm.group();
	}
*/

}

function browserGetViewHeight(refresh) {
	if (refresh) this.refreshViewSize();
	return this.viewHeight;
}

function browserGetViewWidth(refresh) {
	if (refresh) this.refreshViewSize();
	return this.viewWidth;
}

function browserRefreshViewSize() {
	if (typeof window.innerWidth!='undefined') {
		this.viewWidth = window.innerWidth;
		this.viewHeight = window.innerHeight;
	} else if (this.isIE) {
		if (document.compatMode && document.compatMode != 'BackCompat') {
			this.viewWidth = document.documentElement.clientWidth;
			this.viewHeight = document.documentElement.clientHeight;
		} else {
			this.viewWidth = document.body.clientWidth;
			this.viewHeight = document.body.clientHeight;
		}
	}
}

function browserGetElementHeight(node) {
	var h;
	if (this.isIE) {
		if (document.compatMode && document.compatMode != 'BackCompat') {
			//debug("IE: not backk compat");
			h = node.offsetHeight;
		} else {
			//debug("IE: BackCompat");
			h = node.clientHeight;
		}
	} else {
		//debug("netscape");
		h = node.offsetHeight;
	}
	//debug("height: " + h);
	//debug("clientHeight: " + node.clientHeight);
	//debug("offsetHeight: " + node.offsetHeight);
	return h;
}


function browserIsFlashInstalled() {
	if (!this.hasDoneFlashDetection) {
		this.detectFlash();
	}
	return this.flashInstalled;
}

function browserGetFlashVersion() {
	if (!this.hasDoneFlashDetection) {
		this.detectFlash();
	}
	return this.flashVersion;
}

function browserDetectFlash() {
	// check Netscape-compatible plug-ins first.
	if (navigator.plugins && navigator.plugins.length) {
		for (var x=0; x < navigator.plugins.length; x++) {
			if (navigator.plugins[x].name.indexOf('Shockwave Flash') != -1) {
				var version = navigator.plugins[x].description.split('Shockwave Flash ')[1];
				this.flashVersion = parseInt(version);
				this.flashInstalled = true;
				break;
			}
		}
	}

	// then ActiveX-style plug-ins
	else if (window.ActiveXObject) {
		var max_versions = 20;
		var oFlash;
		for (var x = 2; x <= max_versions; x++) {
			try {
				oFlash = eval("new ActiveXObject('ShockwaveFlash.ShockwaveFlash." + x + "');");
				if(oFlash) {
					this.flashInstalled = true;
					this.flashVersion = x;
				}
			} catch(e) {}
		}
	}
	this.hasDoneFlashDetection = true;
}

/* Encode the URL with the encoder String, usually obtained
* by the caller in the .jsp file. This encoding is equivalent
* to Servlet API's Response.encodeURL(). A global URL_ENCODER var
* can be defined in the file that includes this file, browser.js.
* Then the encoder string does not have to be passed. If
* URL_ENCODER is not defined and the an encoder string is not
* passed, the url will not be encoded.
*
* Note that Resin requires the encoder string to be before
* the query string. That is what is function does.
*/
function browserEncodeURL(url, encoderString) {
	if (encoderString == undefined) {
		if (URL_ENCODER != undefined && URL_ENCODER != null) {
			encoderString = URL_ENCODER;
		}
	}

	if (encoderString != undefined && url.indexOf(encoderString) < 0) {
		var qIndex = url.indexOf("?");
		if (qIndex >= 0) {
			url =  url.substring(0, qIndex) + encoderString + url.substring(qIndex);
		} else {
			url = url + encoderString;
		}
	}
	return url;
}

// NOTE: a check on 8/3/2010 found this function NOT IN USE
function browserGetCookie(cookieName) {
	var value = null;
	if (document.cookie.length > 0) {
		var index = document.cookie.indexOf(cookieName + "=");
		if (index >= 0) {
			var start = index + cookieName.length + 1;
			var end = document.cookie.indexOf(";", start);
			if (end == -1) end = document.cookie.length;
			value = document.cookie.substring(start, end);
		}
	}
	return value;
}

// NOTE: a check on 8/3/2010 found this function NOT IN USE
function browserSetCookie(cookieName, cookieValue, daysToLive) {
	document.cookie = cookieName + "=" + cookieValue + ";";
	var expDate = null;
	if (daysToLive != undefined && daysToLive != null) {
		expDate = new Date();
		expDate.setDate(expDate.getDate() + daysToLive);
		document.cookie += "expires=" + expDate;
	}
}

function ElementPosition(node) {
	this.top = browserGetOffsetTop(node, 0);
	this.left = browserGetOffsetLeft(node, 0);
	this.height = node.offsetHeight;
	this.width = node.offsetWidth;
}

/**
* Recursively add up the offsetTop of all offsetParents. It appears that offsetParent is the parentNode
* that this node's offset is relative to.
*/
function browserGetOffsetTop(node, top) {
	//debug("node: " + node.tagName + ", top: " + node.offsetTop);
	if (!top) top = 0;
	top += node.offsetTop;
	if (node.offsetParent.tagName == "BODY") return top;
	else return browserGetOffsetTop(node.offsetParent, top);
}

/**
* Recursively add up the offsetLeft of all offsetParents. The debug statement is defined in debug.js,
* which must be included in the html page for the debug to work.
*/
function browserGetOffsetLeft(node, left) {
	//debug("node: " + node.tagName + ", left: " + node.offsetLeft);
	if (!left) left = 0;
	left += node.offsetLeft;
	if (node.offsetParent.tagName == "BODY") return left;
	else return browserGetOffsetLeft(node.offsetParent, left);
}

/**
* Binds a callback function to a specific event fired by a specific element
* using (in order of preference) W3C DOM Level 2 binding, IE event binding
* or event property binding.
*
* If event property binding must be used, the previously registered event
* handler, if any, will be replaced.
*
* Note that callbacks should not use 'this' keyword, as the meaning of 'this'
* is not stable across browsers in the callback context. When callbacks are 
* within objects, try defining 'self' as a private variable within the object 
* and setting 'self' equal to 'this' during object construction. Then construct
* callbacks as closures that have access to 'self'.
*
* Note that the only parameter that may (or may not) be passed to a callback is
* an event. This is not dependable across browsers, so wrap your event 
* parameter with EventWrapper. Also, avoid 'event' as a parameter name, as this
* may conflict with IE's use of 'event'.
*
* This method may not be used until after the page has loaded.
* 
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param evt the name of the event, case-insensitive, with or without "on" prefix.
* @param obj the element to which to bind the event handler.
* @param fn  the function to call when the event is triggered; an event object 
*            may be passed an argument, but should be handled with Browser to 
*            avoid cross-browser variations in the nature of that event object.
* @param cap optional; true to fire on capture phase if possible, false to fire 
*            on bubbling phase; default is false. Events bubble in IE regardless 
*            of the value provided.
* @return    true if probability of success is high, false otherwise.
*/
Browser.addEvent = function(evt, obj, fn, cap) {
	var r = true;
	var n = Browser.normalizeEventName(evt);
	if (obj.addEventListener) {	 				// look for W3C event binding model
		cap = ( cap === true );
		obj.addEventListener(n, fn, cap);
	} else if (obj.attachEvent) {				// look for IE event binding model
		r = obj.attachEvent('on'+n, fn);
	} else {									// fallback to event property model
		obj['on'+n] = fn;
		r = false;
	}
	return r;
}


/**
* Unbinds a callback function from a specific event fired by a specific element
* using (in order of preference) W3C DOM Level 2 binding, IE event binding
* or event property binding.
*
* This method may not be used until after the page has loaded.
* 
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param evt the name of the event, case-insensitive, with or without "on" prefix.
* @param obj the element from which to unbind the event handler.
* @param fn  the callback function to unbind.
* @param cap use same value provided to addEvent when this event was bound.
* @return    true if probability of success is high, false otherwise.
*/
Browser.removeEvent = function(evt, obj, fn, cap) {
	var r = true;
	var n = Browser.normalizeEventName(evt);
	if (obj.removeEventListener) {	 			// look for W3C event binding model
		cap = ( cap === true );
		obj.removeEventListener(n, fn, cap);
	} else if (obj.detachEvent) {				// look for IE event binding model
		r = obj.detachEvent('on'+n, fn);
	} else {									// fallback to event property model
		if (obj['on'+n] === fn) {
			obj['on'+n] = null;
		} else {
			r = false;
		}
	}
	return r;
}

/**
* Takes an event name with or without an "on" prefix and in any mixture of
* case and returns the core events name without the "on" prefix and all
* lowercase.
* 
* Written by Michael Volk (mvolk@volksys.com) on August 3, 2010 for the
* benefit of Northwest Media, Inc.
*
* @param eventName the name of the event, case-insensitive, with or without 
*                  "on" prefix.
* @return          the all-lowercase event name without "on" prefix.
*/
Browser.normalizeEventName = function(eventName) {
	var n = eventName.toLowerCase();
	if (n.indexOf('on') == 0) n = n.substring(2);
	return n;
}
