/*
Namespace: sIFR
	scalable Inman Flash Replacement.

Plugin Name: jquery.sifr.js

About: Version
	1.0

Description:
	A super-optimized sIFR3 engine, stripped of pretty much everything made redundant by jQuery

Requires:
	jQuery 1.3 <http://jquery.com>
	$.event.special.wheel <http://blog.threedubmedia.com/2008/08/eventspecialwheel.html>
	jQuery Flash Plugin (http://jquery.lukelutman.com/plugins/flash)

Optional:
	Text Resizing with jQuery <http://www.shopdev.co.uk/blog/text-resizing-with-jquery/>
	(a customized version was used for Logitech, with a more descriptive "onTextResize" namespace)

*/
(function($) {
	
	// Global element cache
	var WIN = $(window),
	    DOC = $(document),
	    BODY = $("body");

	/*****************************************************************************
	scalable Inman Flash Replacement (sIFR) version 3, revision 436.

	Copyright 2006 – 2008 Mark Wubben, <http://novemberborn.net/>

	Older versions:
	* IFR by Shaun Inman
	* sIFR 1.0 by Mike Davidson, Shaun Inman and Tomas Jogin
	* sIFR 2.0 by Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben

	See also <http://novemberborn.net/sifr3> and <http://wiki.novemberborn.net/sifr3>.

	This software is licensed and provided under the CC-GNU LGPL.
	See <http://creativecommons.org/licenses/LGPL/2.1/>
	*****************************************************************************/
	var sIFR = (function() {
		var self = $.sIFR = {},
		    ClassNames, PREFETCH_COOKIE, util, ua;

		ClassNames = {
			ACTIVE : "sIFR-active",
			REPLACED : "sIFR-replaced",
			IGNORE : "sIFR-ignore",
			ALT : "sIFR-alternate",
			CLASS : "sIFR-class",
			LAYOUT : "sIFR-layout",
			WRAP : "sIFR-flash-wrap",
			FLASH : "sIFR-flash",
			FIX_FOCUS : "sIFR-fixfocus",
			DUMMY : "sIFR-dummy"
		};
	
		ClassNames.IGNORE_CLASSES = [ClassNames.REPLACED, ClassNames.IGNORE, ClassNames.ALT];
	
		self.ClassNames = ClassNames;
		
		self.MIN_FLASH = "8.0.0";
		self.MIN_SIZE = 6;
		self.MAX_SIZE = 126;
		self.PADDING_BOTTOM = 5;
		self.VERSION = "436";

		self.isActive = false;
		self.isEnabled = true;
		self.fixHover = true;
		self.domains = [];
		self.forceWidth = true;
		self.fitExactly = false;
		self.forceTransform = true;
		self.replacements = {};
	
		self.elementCount = 0; // The number of replaced elements.
	
		self.isInitialized = false;
	
		function Util(sIFR) {
			function capitalize(str) {
				return str.toLocaleUpperCase();
			}
		
			this.textTransform = function(type, str) {
				switch (type) {
					case "uppercase":
						return str.toLocaleUpperCase();
					case "lowercase":
						return str.toLocaleLowerCase();
					case "capitalize":
						return str.replace(/^\w|\s\w/g, capitalize);
				}
				return str;
			};
	
			this.getCSS = function(css, property, remove, selector) {
				var value = null;
				selector = selector || ".sIFR-root";
	
				if (css && css[selector] && css[selector][property]) {
					value = css[selector][property];
					if (remove) {
						delete css[selector][property];
					}
				}
	
				return value;
			};
		
			this.toString = function(arg) {
				var css = [];
				for (var selector in arg) {
					var rule = arg[selector];
					if (rule == Object.prototype[selector]) {
						continue;
					}
	
					css.push(selector, "{");
					for (var property in rule) {
						if (rule[property] == Object.prototype[property]) {
							continue;
						}
						var value = rule[property];
						if (Util.REMOVE[property]) {
							value = parseInt(value, 10);
						}
						css.push(property, ":", value, ";");
					}
					css.push("}");
				}
	
				return css.join("");
			};
			
			this.getTypography = function(el, parent) {
				
				var typo = (parent || el).data("sIFR-typography");
				
				var node = (parent) ? el[0] : typo.node,
				    fontSize = typo.fontSize,
				    pixelFont = typo.pixelFont,
				    ratios = typo.ratios,
				    tuneHeight = typo.tuneHeight,
				    leading = typo.leading,
				    forceWidth = typo.forceWidth;
				
				var size, lines;
				// Get the width (to approximate the final size).
				var width = el.width();
				var forcedWidth = forceWidth ? width : "100%";
				
				if (!fontSize) {
					var calculation = sIFR.calculate(node);
					size = Math.min(sIFR.MAX_SIZE, Math.max(sIFR.MIN_SIZE, calculation.fontSize));
					if (pixelFont) {
						size = Math.max(8, 8 * Math.round(size / 8));
					}

					lines = calculation.lines;
				} else {
					size = fontSize;
					lines = 1;
				}

				// Approximate the final height to avoid annoying movements of the page
				var renderHeight = Math.round(lines * sIFR.getRatio(size, ratios) * size) + sIFR.PADDING_BOTTOM + tuneHeight;

				if (lines > 1 && leading) {
					renderHeight += Math.round((lines - 1) * leading);
				}
				
				return {
					size : size,
					lines : lines,
					renderHeight : renderHeight,
					width : width,
					forcedWidth : forcedWidth
				};
			};
	
			this.escape = function(str) {
				return escape(str).replace(/\+/g, "%2B");
			};
		
			this.domain = function() {
				var domain = "";
				// When trying to access document.domain on a Google-translated page with Firebug, I got an exception. 
				// Try/catch to be safe.
				try {
					domain = document.domain;
				} catch(e) {}
			
				return domain;
			};
		
			this.domainMatches = function(domain, match) {
				if (match == "*" || match == domain) {
					return true;
				}
	
				var wildcard = match.lastIndexOf("*");
				if (wildcard > -1) {
					match = match.substr(wildcard + 1);
					var matchPosition = domain.lastIndexOf(match);
					if (matchPosition > -1 && (matchPosition + match.length) == domain.length) {
						return true;
					}
				}
			
				return false;
			};
			
			this.getComputedStyle = function(node, property) {
				var result;
				if (document.defaultView && document.defaultView.getComputedStyle) {
					var style = document.defaultView.getComputedStyle(node, null);
					result = style ? style[property] : null;
				} else {
					if (node.currentStyle) {
						result = node.currentStyle[property];
					}
				}
				return result || ''; // Ensuring a string.
			};
			
			this.parseVersion = function(version) {
				return version.replace(/\,/g, ".").replace(/(^|\D)(\d+)(?=\D|$)/g, function(version, nonDigit, digits) {
					version = nonDigit;
					for (var i = 4 - digits.length; i >= 0; i--) {
						version += '0';
					}
					return version + digits;
				});
			};
		}
	
		Util.REMOVE = {
			leading : true,
			"margin-left" : true,
			"margin-right" : true,
			"text-indent" : true
		};
		
		Util.SINGLE_WHITESPACE = " ";
		
		util = self.util = new Util(self);
	
		self.activate = function() {
			if (!this.isEnabled || this.isActive || !isValidDomain() || isFile()) {
				return;
			}
			
			this.isActive = true;
			this.setFlashClass();
			
			self.isInitialized = true;
		};
	
		self.setFlashClass = function() {
			if (this.hasFlashClassSet) {
				return;
			}
			
			BODY.addClass(ClassNames.ACTIVE);
			this.hasFlashClassSet = true;
		};
	
		// The goal here is not to prevent usage of the Flash movie, but running sIFR on possibly translated pages
		function isValidDomain() {
			if (self.domains.length === 0) {
				return true;
			}
		
			var domain = util.domain();
			for (var i = 0; i < self.domains.length; i++) {
				if (util.domainMatches(domain, self.domains[i])) {
					return true;
				}
			}
		
			return false;
		}
	
		function isFile() {
			if (document.location.protocol == 'file:') {
				return true;
			}
			return false;
		}
	
		// Gives a font-size to required vertical space ratio
		function getRatio(size, ratios) {
			for (var i = 0; i < ratios.length; i += 2) {
				if (size <= ratios[i]) {
					return ratios[i + 1];
				}
			}
			return ratios[ratios.length - 1] || 1;
		}
		self.getRatio = getRatio;
		
		function getNewDimensions(element, parent) {
			element.removeClass(ClassNames.ALT);
			parent.removeClass(ClassNames.REPLACED);
			
			var typo = util.getTypography(element, parent),
			    height = typo.renderHeight,
			    width = typo.width;
			
			element.addClass(ClassNames.ALT);
			parent.addClass(ClassNames.REPLACED);
			
			return {
				width : width,
				height : height
			};
		}
	
		function calculate(node) {
			var el = $(node);
			
			// HACK: jQuery converts ems to pixels incorrectly in IE7
			// solution: use a generic computedStyle/currentStyle function
			// var fontSize = el.css("font-size");
			var fontSize = sIFR.util.getComputedStyle(node, "fontSize");
			var deduce = fontSize.indexOf('px') == -1;
		
			var html = el.html();
			if (deduce) {
				el.html("X");
			}
			
			var props = [
				"padding-top",
				"padding-bottom",
				"border-top",
				"border-bottom",
				"line-height",
				"display"
			];
			
			// Reset padding and border, so offsetHeight works properly
			$.each(props, function(e, prop) {
				if (prop != "line-height" && prop != "display") {
					el.css(prop, 0);
				}
			});
			
			// 2em magically makes offsetHeight correct in IE
			el.css("line-height", "2em");
			
			// Provided display is block
			el.css("display", "block");
		
			fontSize = deduce ? node.offsetHeight / 2 : parseInt(fontSize, 10);
		
			if (deduce) {
				el.html(html);
			}
		
			var lines = Math.round(node.offsetHeight / (2 * fontSize));
			
			// Revert
			$.each(props, function(e, prop) {
				el.css(prop, "");
			});
		
			if (isNaN(lines) || !isFinite(lines) || lines === 0) {
				lines = 1;
			}
		
			return {
				fontSize : fontSize,
				lines : lines
			};
		}
		self.calculate = calculate;
	
		function parseContent(source, textTransform) {
			var node = $(source),
			    link = node.find("a"),
			    primaryLink = {}, clone, html;
		
			if (link.eq(0)) {
				link = link.eq(0);
			
				primaryLink = {
					href : link.attr("href"),
					target : link.attr("target")
				};
			}
			
			// Clone node to avoid manipulating actual element
			clone = node.clone();
			
			// Replace non-link elements with just their text contents
			clone.find(":not(a)").each(function() {
				var not = $(this);
				not.replaceWith(not.text());
			});
			
			// Store HTML
			html = clone.html();
			
			// Cleanup
			clone.remove();
		
			return {
				text : html,
				primaryLink : primaryLink
			};
		}
		self.parseContent = parseContent;
		
		return self;
	})();
	
	jQuery.fn.sifr = function(kwargs) {

		// Activate sIFR
		if (!sIFR.isInitialized) {
			sIFR.activate();
		}
		
		// If flash plugin is not defined
		// Or if Flash version is too low, return
		var flash = $.fn.flash;
		var parse = sIFR.util.parseVersion;
		
		if (!flash || (parse(flash.hasFlash.playerVersion()) < parse(sIFR.MIN_FLASH))) {
			return this;
		}
		
		return this.each(function() {
			var node = this,
			    currKW = kwargs,
			    el = $(node),
			    cache = el.data("kwargs"),
			    ClassNames = sIFR.ClassNames,
			    getCSS = sIFR.util.getCSS,
			    src, ratios = [];

			if (node == document) {

				// Set default font
				if (currKW && currKW.defaultFont) {
					sIFR.defaultFont = currKW.defaultFont;
				}

				if (!currKW) {

					// Toggle between sifr/unsifr
					var func = (currKW === false) ? "unsifr" : "sifr";

					var replacements = sIFR.replacements,
					    backup = {}, key;

					for (key in replacements) {

						// Keep a backup, because we don't want to delete
						// references to our elements. Yet.
						backup[key] = replacements[key];

						// Initialize function
						replacements[key].element[func]();
					}

					// Revert to backed up object
					sIFR.replacements = backup;
				}

				return;
			}

			if (currKW === false) {
				if (el.hasClass(ClassNames.REPLACED)) {
					var removeID = el.find("." + ClassNames.FLASH).attr("id");

					sIFR.replacements[removeID] = null;
					delete sIFR.replacements[removeID];

					el.removeClass(ClassNames.REPLACED).html(el.find("." + ClassNames.ALT).html()).css("min-height", "");
				}

				return;
			}

			if (el.hasClass(ClassNames.REPLACED)) {
				return;
			} else if (!currKW && cache) {
				currKW = cache;
			} else if (!cache) {
				el.data("kwargs", currKW);
			}

			if (sIFR.onReplacementStart) {
				sIFR.onReplacementStart(currKW);
			}
			
			// If no args
			currKW = currKW || {};

			// If no font, check for cached font.
			if (!currKW.font) {
				if (sIFR.defaultFont) {
					currKW.font = sIFR.defaultFont;
				} else {
					return;
				}
			}

			if (typeof currKW.font === "string") {
				src = currKW.font;
			} else if (typeof currKW.font === "object") {
				src = currKW.font.src;
				ratios = currKW.font.ratios || [];
			}

			var css = currKW.css,
			    cssText = sIFR.util.toString(css),
			    forceSingleLine = (currKW.forceSingleLine === true),
			    preventWrap = (currKW.preventWrap === true) && !forceSingleLine,
			    fitExactly = forceSingleLine || (currKW.fitExactly === null ? sIFR.fitExactly : currKW.fitExactly) === true,
			    forceWidth = fitExactly || (currKW.forceWidth === null ? sIFR.forceWidth : currKW.forceWidth) === true,
			    pixelFont = currKW.pixelFont === true,
			    tuneHeight = parseInt(currKW.tuneHeight) || 0,
			    events = !!currKW.onRelease || !!currKW.onRollOver || !!currKW.onRollOut,
			    fixFlash = $.browser.ie && (flash.hasFlash.playerVersion() < parse("9.0.115")),
			    fontSize = getCSS(css, "font-size", true) || "0",
			    backgroundColor = getCSS(css, "background-color", true) || "#FFFFFF",
			    kerning = getCSS(css, "kerning", true) || "",
			    opacity = getCSS(css, "opacity", true) || "100",
			    cursor = getCSS(css, "cursor", true) || "default",
			    leading = parseInt(getCSS(css, "leading")) || 0,
			    gridFitType = currKW.gridFitType || (getCSS(css, "text-align") == "right") ? "subpixel" : "pixel",
			    textTransform = (sIFR.forceTransform === false) ? "none" : getCSS(css, "text-transform", true) || "none",
			    wmode = currKW.wmode || 'opaque';


			// Alignment should be handled by the browser in this case.
			if (fitExactly) {
				getCSS(css, "text-align", true);
			}

			// Only font sizes specified in pixels are supported.
			fontSize = /^\d+(px)?$/.test(fontSize) ? parseInt(fontSize) : 0;

			// Make sure to support percentages and decimals
			opacity = (parseFloat(opacity) < 1) ? (100 * parseFloat(opacity)) : opacity;

			// Store typography settings for later use
			el.data("sIFR-typography", {
				node : node,
				fontSize : fontSize,
				pixelFont : pixelFont,
				ratios : ratios,
				tuneHeight : tuneHeight,
				leading : leading,
				forceWidth : forceWidth
			});

			var props = sIFR.util.getTypography(el),
			    size = props.size,
			    lines = props.lines,
			    renderHeight = props.renderHeight,
			    width = props.width,
			    forcedWidth = props.forcedWidth,

			    content = sIFR.parseContent(node, textTransform),
			    id = "sIFR_replacement_" + sIFR.elementCount++,
			    flashvars, alternate, dummy;

			flashvars = {
				"id" : id,
				"content" : sIFR.util.escape(content.text),
				"width" : width,
				"renderheight" : renderHeight, 
				"link" : sIFR.util.escape(content.primaryLink.href || ""),
				"target" : sIFR.util.escape(content.primaryLink.target || ""),
				"size" : size, 
				"css" : sIFR.util.escape(cssText),
				"cursor" : cursor,
				"tunewidth" : (currKW.tuneWidth || 0),
				"tuneheight" : tuneHeight,
				"offsetleft" : (currKW.offsetLeft || ""),
				"offsettop" : (currKW.offsetTop || ""),
				"fitexactly" : fitExactly,
				"preventwrap" : preventWrap,
				"forcesingleline" : forceSingleLine,
				"antialiastype" : (currKW.antiAliasType || ""),
				"thickness" : (currKW.thickness || ""),
				"sharpness" : (currKW.sharpness || ""),
				"kerning" : kerning,
				"gridfittype" : gridFitType,
				"opacity" : opacity,
				"blendmode" : (currKW.blendMode || ""), 
				"selectable" : true,
				"fixhover" : (sIFR.fixHover === true),
				"events" : events,
				"delayrun" : fixFlash,
				"version" : sIFR.VERSION
			};

			alternate = $('<span></span>').addClass(ClassNames.ALT).html(el.html());
			el.html(alternate.attr("id", id + "_alternate")).addClass(ClassNames.REPLACED);

			// Before removing the existing content, set its height such that the element does not collapse.
			el.css("min-height", renderHeight);

			// Create a dummy node so Flash has something to overwrite
			dummy = $('<span></span>').prependTo(el);

			// Overwrite dummy
			dummy.flash({
				"name" : id,
				"flashvars" : flashvars,
				"id" : id,
				"class" : "sIFR-flash",
				"src" : src,
				"width" : forcedWidth,
				"height" : renderHeight,
				"wmode" : wmode,
				"allowScriptAccess" : "always"
			}).addClass(ClassNames.WRAP);

			// Cache objects
			sIFR.replacements[id] = sIFR.replacements[id] || {
				element : el,
				alternate : alternate,
				flash : document.getElementById(id)
			};
		});
	};

	jQuery.fn.unsifr = function() {
		return this.sifr(false);
	};
	
})(jQuery);