(function(){
	var element_table = {};
	/**
	 * Shortcut for getElementById
	 * @param {String} id
	 */
	var $ = document.all
		? function(id){
			return document.all[id];
		}
		: function(id){
			return document.getElementById(id);
		};

	/**
	 * Shortcut for getElementByTagName
	 * @param {String} tagName
	 */
	var $$ = function(tagName){
		return document.getElementsByTagName(tagName);
	}
	/**
	 * Grabs a frame's document object
	 * @param {String} frameName
	 */
	var $_ = function(frameName){
		return window.frames[frameName].document;
	}
	/**
	 * Dual purpose: both a Client-side ad cache buster 
	 * and a unique page id used for roadblocks
	 */
	var ord = Math.random() * 10000000000000000;
	/**
	 * This page document.writes the imdb cookie or cache variable of your choice.
	 */ 
	var cookie_dumper_url = "/images/a/ifb/doubleclick/indirect.html";
	/**
	 * Amazon Cookie Name
	 */
	var amazon_pa_cookie = "us";
	
	var twilight_url = "http://www.imdb.com/twilight/?";
		
	/**
	 * These regexes identify links that should not be rewritten for ads
	 */
	var restricted_url_regexes = [
		/\/r[gir]?\//,
		/\/tiger_redirect/,
		/^https:/,
		/secure\.imdb\.com/,
		/pro\.imdb\.com/,
		/^javascript:/,
		/imdb\.[a-z]+\/update/,
		/resume/,
		/help/
	];
	/**
	 * These regex replacement text pairs identify symbols which we will unescape
	 */
	var doubleclick_url_unescapes = [
		[/%3B/g, ";"],
		[/%3D/g, "="],
		[/%2C/g, ","],
		[/%3F/g, "?"]
	];
	/**
	 * The default regex for urls that should include an ord value
	 */
	var client_side_ord_regex = /(\[|%5B)CLIENT_SIDE_ORD(\]|%5D)/;
	/**
	 * The refault regex for urls that embed a pagesegment
	 */
	var page_segment_regex = /(\[|%5B)PASEGMENTS(\]|%5D)/;
	/**
	 * Private Cache
	 */
	var cache = {};
		
	this.ad_utils = {
	
		ord: ord,
		ord_regex: client_side_ord_regex,
		/**
		 * Keeps track of all defined ads
		 */
		ad_table: {},
		
		/**
		 * Keeps track of un-rendered ads
		 */
		ad_queue: new Array(),
		/**
		 * Called from our standard ad tags to preserve ad rendering order
		 * @param {Number} tile
		 * @param {String} slot
		 */
		queue_ad: function(tile, slot){
			var queue = this.ad_queue;
			var prev = queue.length > 0 ? queue[queue.length-1].slot: null;
			var entry = {
				tile: tile,
				slot: slot,
				prev: prev
			}; 
			queue.push(entry);
			this.ad_table[slot] = entry;
		},
		/**
		 * Called from an expand.html iframe to render an ad in "tile" order.
		 * If called prematurely, render_ad sets a callback to reload expand.html  
		 * @param {Document} doc
		 * @param {Window} win
		 */
		render_ad: function(doc, win){
			var queue = this.ad_queue;
			var slot = win.frameElement.id;
			if (!this.ad_table[slot])
				this.ad_table[slot] = {slot:slot};
			
			var prev = this.ad_table[slot].prev;
			var hash = doc.location.hash;
				
			try {
				// if the queue is empty OR we're at the top of the queue
				if (queue.length == 0 || queue[0].slot == slot) {
					
					if (prev) {
						this.ad_table[prev].onFireAd = null;
					}
					
					queue.shift();
					this.ad_table[slot].fired = true;
					
					try {
						// Timestamp the start of the load
						generic.monitoring.start_timing(slot);						
					} catch (e) {
						consoleLog("start_timing failed: " + e.toString());
					}
					
					doc.write('<script src="' +
					custom.doubleclick.construct_url(hash) +
					'" type="text/javascript"><\/script>');

					if (this.ad_table[slot].onFireAd) {
						this.ad_table[slot].onFireAd();
					}
				}
				else if (prev){
					this.ad_table[prev].onFireAd = function(){
						win.location.reload();
					};				
				}

			}catch(e){
				doc.write('<script src="' +
				custom.doubleclick.construct_url(hash) +
				'" type="text/javascript"><\/script>');				
			}
					
		},
		
		/**
		 * Deprecated by expand_ad   
		 * @param {Object} iframe
		 */
		resize_iframe: function(iframe){
			this.expand_ad(iframe);
		},
		
		/**
		 * This code is responsible for manually resizing and placing ad content.
		 * Generally called via the onload event of an ad slot's iframe
		 * @param {Object} iframe
		 */	
		expand_ad: function(iframe){
			var slot = iframe.name;
			var element = $(slot);
			var fDoc = $_(slot);
			var adObject = null;
			
			try {
				adObject = fDoc.ad;
			} 
			catch (e) {
				consoleLog("Cannot access ad parameters: " + e.toString());
			}
			
			/* If this ad HAS been registered in the ad_table, but hasn't successfully 
			 * fired yet, don't bother with this */
			if (this.ad_table[slot] && !this.ad_table[slot].fired) return;

			if (! adObject) {
				/* There is no ad object, so pick up the size
				 * from the document itself and adjust ad size
				 * to that. */
				
				var width = parseInt(fDoc.body.scrollWidth); 
				var height = parseInt(fDoc.body.scrollHeight);

				if (fDoc.body.firstChild) {
					width = Math.max(width, parseInt(fDoc.body.firstChild.scrollWidth));
					height = Math.max(height, parseInt(fDoc.body.firstChild.scrollHeight));
				}
				
				if (width > 1 && height > 1) {
					adObject = {
						w: width.toString(),
						h: height.toString(),
						aid: '0',
						cid: '0'
					};
				}
			}

			try {
				generic.monitoring.stop_timing(slot, this.get_type_of_ad(adObject), true);
			} catch(e) {
				consoleLog("stop_timing failed: " + e.toString());
			}

			//If this is a tracking iframe, log the ad impression
			if (fDoc.location.href.match(/\/ads\/request\//)) {
				this.log_ad_impression(adObject, slot);
			}

			//Detected an ad object
			if (adObject) {
				//Register ad parameters in primary document
				if (!document.ads) 
					document.ads = {};
				document.ads[adObject.cid] = adObject;
				
				//Adjust ad size
				if (adObject.w) {
					element.width = parseInt(adObject.w) + 'px';
				}
				if (adObject.h) {
					element.height = parseInt(adObject.h) + 'px';
				}
				
				if (adObject.st) {
					this.expand_supertab(adObject);
				}
				else 
					if (iframe.id == "top_ad" &&
					this.is_open(adObject)) {
						this.expand_top_banner(adObject);
					}
				
				if (adObject.wrap) {
					this.expand_wrap(adObject);
				}
				
				if (adObject.bamf) {
					this.expand_bamf(adObject, element);
				}
				
				if (adObject.inner_bamf) {
					this.expand_inner_bamf(adObject);
				}
				
				if (adObject.relative_bamf) {
					this.expand_relative_bamf(adObject);
				}
				
				if (this.is_open(adObject)) {
				
					//Detect if the ad has a hidden label
					var before = $(slot + "_before");
					if (before) {
						this.expand_label(before);
					}
					
					var after = $(slot + "_after");
					if (after) {
						this.expand_label(after);
					}
				}
				
				//Manually override styles
				if (adObject.styles) {
					for (var id in adObject.styles) {
						generic.override_style(id, adObject.styles[id].style);
					}
				}
			}
		},
		

		/**
		 * Analyzes an adObject to determine if an ad
		 * is being displayed
		 * @param {Object} adObject
		 */
		is_open: function(adObject){
			return adObject &&
			adObject.w &&
			adObject.h &&
			adObject.w > 0 &&
			adObject.h > 0;
		},
		
		/**
		 * Styles the page to properly fit a supertab
		 * @param {Object} adObject
		 */
		expand_supertab: function(adObject){
			var nb15 = $('nb15');
			if (nb15) {
				nb15.className = 'supertab';
			} else {
				$('nb20').className = 'supertab';
			}
		},
		
		/**
		 * Styles the page to properly fit a top banner
		 * @param {Object} adObject
		 */
		expand_top_banner: function(adObject){
			var nb15 = $('nb15');
			if (nb15) {
				nb15.className = 'banner';
			} else {
				$('nb20').className = 'banner';
			}
		},
		
		/**
		 * DEPRECATED
		 * Restyles the bamf's iframe into the correct location
		 * @param {Object} adObject
		 * @param {Object} element
		 */
		expand_bamf: function(adObject, element){
			var bamf = adObject.bamf;
			generic.extend(element.style, bamf.style);
		},
		/**
		 * DEPRECATED
		 * plucks a node out of an ad, and plugs it into the page.
		 * @param {Object} adObject
		 */
		expand_inner_bamf: function(adObject){
			var inner_bamf = adObject.inner_bamf;
			var targetNode = $(inner_bamf.targetId);
			if (targetNode && inner_bamf.node) {
				generic.insert_after(inner_bamf.node, targetNode);
			}
		},
		/**
		 * The current canonical way of generating a floater
		 * @param {Object} adObject
		 */
		expand_relative_bamf: function(adObject){
		
			var relative_bamf = adObject.relative_bamf;
			var targetNode = $(relative_bamf.targetId);
			var attach = generic[relative_bamf.relation];
			
			if (targetNode && relative_bamf.node) {
				attach(relative_bamf.node, targetNode);
			}
		},
		/**
		 * An explicit way to generate wraps
		 * @param {Object} adObject
		 */
		expand_wrap: function(adObject){
		
			if (!adObject.styles) {
				adObject.styles = {};
			}
			var styles = {
				wrapper: {
					style: {
						background: adObject.wrap
					}
				},
				root: {
					style: {
						background: "transparent"
					}
				},
				footer: {
					style: {
						background: "#ffffff"
					}
				},
				pagecontent: {
					style: {
						height: "100%"
					}
				}
			};
			generic.extend(adObject.styles, styles);
			
		},
		
		/**
		 * makes a hidden label visible by removing "hidden" from 
		 * an element's className
		 * @param {Element} label
		 */
		expand_label: function(label){
			label.className = label.className.replace(/hidden/, "");
		},
		
		//log the loading of an ad
		log_ad_impression: function(adObject, slot){
		
			var log_url = "/ads/record/[scope];[slot];[aid];[cid];[ord];[time].js";

			log_url = log_url.chained_replace([ 
				[/\[slot\]/g, slot],
				[/\[ord\]/g, this.ord],
				[/\[time\]/g, new Date().getTime()]
			]);			
			
			var replacements = adObject 
				? [
					[/\[scope\]/g, adObject.scope || '0'],
					[/\[aid\]/g, adObject.aid],
					[/\[cid\]/g, adObject.cid]
				]
				: [
					[/\[scope\]/g, "null"]
				];
			
			log_url = log_url.chained_replace(replacements);		

			generic.load_script(log_url);
		},
		
		get_type_of_ad: function(adObject) {
			if(!adObject) {
				return "null";
			}
			
			if(adObject.scope) {
				return adObject.scope;	
			}
			
			return "got_ad";
		}
		
		
	};
	
	this.generic = {
	
		// Used for recording all the urls that were loaded via the one_way_send function
		one_way_send_record: new Array(),

		/**
		 * Insert an element into the DOM after a given node
		 * @param {Element} newNode
		 * @param {Element} targetNode
		 */
		insert_after: function(newNode, targetNode){
			try {
				targetNode.parentNode.insertBefore(newNode, targetNode.nextSibling);
			} 
			catch (err) {
				targetNode.parentNode.appendChild(newNode);
			}
		},
		/**
		 * Insert an element into the DOM before a given node
		 * @param {Element} newNode
		 * @param {Element} targetNode
		 */
		insert_before: function(newNode, targetNode){
			targetNode.parentNode.insertBefore(newNode, targetNode);
		},
		/**
		 * Insert an element into the DOM inside a given node
		 * @param {Element} newNode
		 * @param {Element} targetNode
		 */
		insert_inside: function(newNode, targetNode){
			targetNode.appendChild(newNode);
		},
		/**
		 * Recursively extends all of the properties of the targetObject
		 * with the properties of the sourceObject
		 * @param {Object} targetObject
		 * @param {Object} sourceObject
		 */
		extend: function(targetObject, sourceObject){
			for (var h in sourceObject) {
				if (typeof sourceObject[h] == 'object') {
					if (targetObject[h] != sourceObject[h]) {
						if (typeof targetObject[h] == 'undefined') {
							targetObject[h] = {};
						}

						this.extend(targetObject[h], sourceObject[h]);
					}
				}
				else {
					targetObject[h] = sourceObject[h];
				}
			}
		},
		/**
		 * Takes a hash table of id / style pairs and attaches
		 * new css styles to the document head
		 * @param {String} id
		 * @param {Object} style a hash of id:style pairs
		 */
		override_style: function(id, style){
		
			var style_node = style_node = document.createElement("style");
			style_node.setAttribute("type", "text/css");
			
			var style_str = "#" + id + " {";
			for (var k in style) {
				style_str += k + ":" + style[k] + " !important;";
			}
			style_str += "}";
			
			if (style_node.styleSheet) {
				//IE
				style_node.styleSheet.cssText = style_str;
			}
			else {
				try {
					//Mozilla
					style_node.innerHTML = style_str;
				} 
				catch (err) {
					//Safari
					style_node.innerText = style_str;
				}
			}
			
			$$('head')[0].appendChild(style_node);
		},
		/**
		 * Wrapper function for cookie|cache get
		 * @alias generic.set
		 * @param {String} source (cookie|cache):name
		 * @param {String} value
		 */
		set:function(source, value){
			var path = source.split(':');
			generic[path[0]].set(path[1],value);
		},
		/**
		 * Wrapper function for cookie|cache set
		 * @alias generic.get
		 * @param {String} source (cookie|cache):name
		 */
		get: function(source){
			var path = source.split(':');
			return generic[path[0]].get(path[1]);
		},
		/**
		 * Wrapper function for cookie|cache erase
		 * @alias generic.erase
		 * @param {String} source (cookie|cache):name
		 */
		erase: function(source){
			var path = source.split(':');
			generic[path[0]].erase(path[1]);
		},
		cookie: {
			/**
			 * Escapes and Stores data within the page's cookies
			 * @alias generic.cookie.set
			 * @param {String} name
			 * @param {String} value
			 * @param {Number} days optional - default expiration at session's end
			 */
			set: function(name, value, days){
				if (days) {
					var date = new Date();
					date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
					var expires = "; expires=" + date.toGMTString();
				}
				else 
					var expires = "";
				document.cookie = name + "=" + escape(value) + expires + "; path=/";
			},
			/**
			 * Retrieves and Unescapes data from the page's cookies
			 * @alias generic.cookie.get
			 * @param {String} name
			 * @return {String}
			 */
			get: function(name){
				var ca = document.cookie.split(";");
				for (var i = 0; i < ca.length; i++) {
					if (typeof ca[i] != "string") continue;
					var pair = ca[i].split('=');
					if (pair[0] && pair[1] && pair[0].replace(/\s/, "") == name) 
						return unescape(pair[1]);
				}
				return null;
			},
			/**
			 * Delete cookie data
			 * @alias generic.cookie.erase
			 * @param {String} name
			 */
			erase: function(name){
				this.set(name, "", -1);
			}
		},
		cache:{
			/**
			 * Store data in a private cache
			 * @alias generic.cache.set
			 * @param {String} name
			 * @param {Object} value
			 */
			set: function(name,value){
				cache[name] = value;
			},
			/**
			 * Retrieve data from a private cache
			 * @alias generic.cache.get
			 * @param {String} name
			 * @return {Object}
			 */
			get: function(name){
				return cache[name];
			},
			/**
			 * Delete previously cached data
			 * @alias generic.cache.erase
			 * @param {String} name
			 */
			erase:function(name){
				delete cache[name];
			}
		},
		/**
		 * Rewrite all links on a page to use newonclick.
		 * @param {function} newonclick
		 */
		rewrite_links: function(newonclick){
			var links = document.links;
			
			for (var i = 0; i < links.length; i++) {
				var link = links[i];
				
				if (link.rewritten) continue;
				
				link.rewritten = true;
				
				if (link.onclick) 
					link.oldonclick = link.onclick;
					
				link.newonclick = newonclick;
				
				link.onclick = function(event){
					var status = true;

					// old onclick
					if (this.oldonclick){
						status = this.oldonclick(event);
					}

					// no pre-existing external
					if (!this.target || this.target == '_self'){
						status = this.newonclick(this, status);
						this.target = "_self";
					}

					// status is true | undefined
					if (status != false && this.href){
						window.open(this.href, this.target);	
					}
					
					// stop link processing here.
					return false;					
				}
			}
			
		},
		/**
		 * Dynamically inserts a script into the head of the document.
		 * @param {String} src
		 */
		load_script: function(src){
			var script = document.createElement('script');
			script.type = "text/javascript";
			script.src = src;
			$$('head')[0].appendChild(script);
		},
		/**
		 * An efficient (as in, "hopefully parallel") way to fire off requests
		 * at the server, not expecting or caring what the response might be
		 * @param {String} src
		 */
		one_way_send: function(src){
		  imageObj = new Image(0,0);
		  imageObj.src = src;
		  this.one_way_send_record.push(imageObj); 
		},
		extensions:{
			String:{
				/**
				 * Rewrites a string object by applying (in order) "regex, replacement" pairs from regexArray
				 * @param {Array} regexArray Array of regex,replacement pairs
				 */
				chained_replace: function(regexArray){
					var text = this;
					for (var i=0; i < regexArray.length; i++){
						var regexPair = regexArray[i];
						text = text.replace(regexPair[0],regexPair[1]);
					}
					return text;
				},
				/**
				 * Removes substrings (determined by regex) from a string object 
				 * until the string is less than a specified length or the regex cannot
				 * match anymore values.  
				 * @param {RegExp} regex identifies the string to remove
				 * @param {Number} length the desired number of characters
				 */
				collapse: function(regex, length){

					var text = this;
					while (text.length > length){
						var newText = text.replace(regex,'');
						if (newText.length == text.length){
							break;							
						}
						text = newText;
					}
					return text;
				}
			}
			},
		monitoring: {
			
			/**
			 * Keys: event names
			 * Values: the timestamp of the start of the event
			 */
			event_starts: {},
			/**
			 * Keep track of how many more events need to happen before we are done.
			 */
			events_to_stop: 0,
			
			/**
			 * Keys: event names
			 * Values: the duration of the event
			 */
			event_duration: {},
			
			/**
			 * An array of events that need to be recorded.  These events don't have a duration,
			 * we just want to count them up
			 */
			event_counters: [],
			/**
			 * Flag to tell us if the last "measurable" event already fired 
			 */
			all_events_started_flag: false,
			
			/**
			 * Global page info for Twilight
			 */
			twilight_info: "",
			
			/**
			 * Record the timestamp for the start of the event
			 * @param {Object} event
			 */
			start_timing: function(event) {
				var timestamp = new Date().getTime();
				this.event_starts[event] = timestamp;
				this.events_to_stop ++;
			},
			/**
			 * Record the duration of the event.
			 * Move the event from the event_starts table
			 * to the event_duration table.
			 * What's actually recorded in the event_duration table
			 * is <event_name>:<return_type>
			 * Also determines if it's time to send whatever events are 
			 * ready to the Twilight service, and calls send_metrics if that's the case
			 * @param {String} event
			 * @param (String) return_type
			 * @param (Boolean) flush
			 */
			stop_timing: function(event, return_type, flush) {
				var timestamp = new Date().getTime();
				if(return_type != '') {
					this.event_duration[event+"."+return_type] = timestamp - this.event_starts[event];	
				} else {
					this.event_duration[event] = timestamp - this.event_starts[event];
				}
				delete this.event_starts[event];
				this.events_to_stop --;
				if(flush || (this.all_events_started_flag && this.events_to_stop == 0)) {
					this.send_metrics();
				}
			},
			/**
			* Records the event.  Calls send_metrics if "flush" is true
			* @param {String} event
			* @param (Boolean) flush
			*/
			record_event: function(event, flush) {
				this.event_counters.push(event);
				if(flush) {
					this.send_metrics();
				}
		   },
		   /**
		   * This API is for the DIY types: you give it the metric name and value, and that's
		   * exactly what gets sent to Twilight.
		   * @param {String} metric_name
		   * @param {String} metric_value
		   * @param (Boolean) flush
		   */
		   record_metric: function(metric_name, metric_value, flush) {
				this.event_duration[metric_name] = metric_value;
				if(flush) {
					this.send_metrics();
				}
		   },
			/**
			* Send the acumulated timings to Twilight
			*/
			send_metrics: function() {
				var url = twilight_url;
				url += this.twilight_info;
				var i = 1;
				// get the metrics
				for(var operation in this.event_duration) {
					// record the operation name
					url += "&Operation." + i + "=" + operation;
					
					// record the timing
					url += "&OperationTiming." + i + "=" + this.event_duration[operation];  
					i++;	
				}
				// clean up the events
				this.event_duration = {};
					
				// get the counters
				for (var i=0; i<this.event_counters.length; i++) {;
					url += "&Counter." + (i+1) + "=" + this.event_counters[i];
				}
				// clean up the counters
				this.event_counters = [];
				
				// prevent caching
				url += "&ord=" + ord;
				
				generic.one_way_send(url);
			},
			  
			/**
			 * Called to populate server-generated information used by Twilight
			 */  
			set_twilight_info: function(page_type, country_code, twilight_ord, timestamp, twilight_url_arg) {
				this.twilight_info = "PageType=" + page_type + "&Geo=" + country_code + "&tw_ord=" + twilight_ord + "&timestamp=" + timestamp;
				twilight_url = twilight_url_arg;
			},
			/**
			 * Called to signify that the last event has started
			 */		
			all_events_started: function() {
				this.all_events_started_flag = true;
			}
		}
	};
	
	generic.extend(String.prototype, generic.extensions.String);
	
	
	/**
	 * "Advanced" content types 
	 */
	this.custom = {
		amazon : {
			segments:null,
			aan_call_name: "aan_call",
			/**
			 * Caches, and then returns the most recent amazon segments
			 */
			get_segments:function(){
				this.segments = this.segments 
					|| generic.cookie.get(amazon_pa_cookie) 
					|| "";
				 return this.segments;
			},
			/**
			 * Stores amazon pa_segments - usually called from amazon code
			 * @param {Object} segments
			 */
			load_callback:function(aan_segments){
				/* Trimming segments to 500 
				 * From DFP documentation:
				 * Key-values in an ad tag URL: DoubleClick recommends for optimal Availability Forecasting, 
				 * tag key-values not exceed 511 characters (starting after the site/zone key name and up to 
				 * the ;ord= parameter). Ad Server will interpret tag key-values if they exceed 511 however AF 
				 * will not consider any in excess of this number
				 */
				trimmed_segs = aan_segments.collapse(/s=[^;]*;$/, 500);
				if(trimmed_segs.length < aan_segments.length) {
					generic.monitoring.record_metric("seg_trimmed", aan_segments.length - trimmed_segs.length);
				}
				generic.cookie.set(amazon_pa_cookie, trimmed_segs, 7);
				if(this.segments == null) {
					// got back in time to be used
					generic.monitoring.stop_timing(this.aan_call_name, "in_time", false);
				} else {
					// segments was already set from the cookie
					generic.monitoring.stop_timing(this.aan_call_name, "late", false);
				}
			 },
			/**
			 * This gets called before calling AAN from the scriptloader iframe, to record
			 * the Twilight events.
			*/
			aan_iframe_oncall: function(){
				generic.monitoring.start_timing(custom.amazon.aan_call_name);
			}
		},
		doubleclick : {
			/**
			 * Takes an expand.html location.hash, and generates a doubleClick url
			 * @param {String} hash
			 */
			construct_url : function(hash){
				var dclkcmd = (hash.match(';ifb=pf;')) ? 'pfadj' : 'adj';
				var params = escape(hash.substring(1, hash.length));
				params = params.chained_replace([
					[client_side_ord_regex, ord],
					[page_segment_regex, custom.amazon.get_segments()]
				]);
				
				params = params.chained_replace(doubleclick_url_unescapes);
				
				return "http://ad.doubleclick.net/" + dclkcmd + "/" + params;
			}
		},
		indirect : {
			/**
			 * Closes an indirect.html ad, irrespective of type
			 * @param {Window} win
			 */
			close : function(win){
				if (win.location.href.match("popup")){
					win.close();
				}
				else if (win.location.href.match("interstitial")){
					custom.interstitial.close();
				}
				else if (win.location.href.match("floater")){
					custom.floater.close(win.frameElement.id)
				}
			}
		},
		popup : {
			current: null,
			/**
			 * Place the current window over the current popup window
			 */
			blur : function(){
				try {
					custom.popup.current.blur();
				}catch(e){}
				window.focus();
			},
			/**
			 * Open a popup window
			 * @param {String} url page that will render the popup content
			 * @param {String} target browser window name
			 * @param {Array} size [width,height]
			 * @param {String} window_style minimal|undef
			 * @param {Boolean} blur 
			 */
			launch: function(url, target, size, window_style, blur) {
				var options = "";
				
				switch( window_style ) {
					case 'minimal':
						options = "status=no,location=no,toolbar=no,scrollbars=no,directories=no,menubar=no,resizeable=no";
						break;
					default:
						options = "status=yes,location=yes,toolbar=yes,scrollbars=yes,directories=yes,menubar=yes,resizable=yes";
						break;
				}
				if (size){
					if (size[0]) {
						options += ",width="+size[0];	
					}
					if (size[1]){
						options += ",height="+size[1];
					}
				}

				custom.popup.current = window.open(url,target,options);
				
				if (blur){
					setTimeout("custom.popup.blur()", 100);					
				}
				
			},
			/**
			 * Rewrites all (non-popup) links to fire the desired popup using /images/a/ifb/doubleclick/indirect.html
			 * @param {String} content the html to be rendered
			 * @param {Array} size [width,height] 
			 * @param {String} window_style minimal|undef
			 * @param {Boolean} blur
			 */
			set: function(content, size, window_style, blur){
				generic.cookie.set("popup",content);
				// popup blocker friendly
				custom.popup.launch(cookie_dumper_url + "?source=cookie:popup", "popup", size, window_style, blur);
			}
		},
		interstitial : {
			/**
			 * Minimizes the interstitial, and jumps to the link target
			 */
			close: function(){
				$('wrapper').style.display = "block";
				$('interstitial_wrapper').style.display = "none";
				window.open($('interstitial_link').href,"_self");		
				return true;	
			},
			/**
			 * Open an interstitial
			 * @param {String} url page that will render the interstitial content
			 * @param {Array} size [width,height]
			 * @param {Object} link the link object to "click"
			 * @param {String} sec closing time (or 'none')
			 */
			launch: function(url, size, link, sec){
				
				if (sec != 'none')
					setTimeout("custom.interstitial.close()",sec * 1000);
				
				$('wrapper').style.display = "none";
				var anchor = document.createElement("div");
				generic.extend(anchor,{
					id:"interstitial_wrapper",
					name:"interstitial_wrapper",
					align:"center",
					style:{
						width:"100%",
						height:"100%",
						top:"0",
						left:"0",
						position:"absolute",
						zIndex:"10000",
						backgroundColor:'#ffffff'
					},
					innerHTML:'<div><a id="interstitial_link" onclick="custom.interstitial.close()" href="'+ link.href +'">Click here</a> to skip this ad</div>'
				});

				var iframe = document.createElement("iframe");
				generic.extend(iframe,{
					id:"interstitial",
					name:"interstitial",
					src:url,
					width:size[0]+'px',
					height:size[1]+'px',
					frameBorder:"0",
					scrolling:"no",
					allowTransparency:"true",
					marginHeight:"0px",
					marginWidth:"0px"
				});
				
				anchor.appendChild(iframe);
				document.body.appendChild(anchor);
			},
			/**
			 * Rewrites all (non-critical/non-popup) links to activate an interstitial using /images/a/ifb/doubleclick/indirect.html
			 * @param {String} content the html to be rendered
			 * @param {Array} size [width,height]
			 * @param {Object} sec closing time (or 'none')
			 */
			set:function(content, size, sec){
				generic.cache.set("interstitial",content);
				generic.rewrite_links(function(link, status){
					for (var i=0; i < restricted_url_regexes.length; i++){
						if (link.href.match(restricted_url_regexes[i])) {
							return status;
						}						
					}
					
					try {
						custom.interstitial.launch(cookie_dumper_url + "?source=cache:interstitial", size, link, sec);
					}catch(e){};
					return false; //stop processing - it is the interstitial's job to follow the link
				});
			}
		},
		floater:{
			/**
			 * Minimize a floater to a 0x0 element
			 * @param {String} id the floater's html element's id
			 */
			close:function(id){
				generic.extend($(id), {
					style:{
						width:'0px',
						height:'0px'
					}
				});
			},
			/**
			 * Open a floater and insert it into the page
			 * @param {String} url page that will render the floater's content
			 * @param {Array} size [width,height]
			 * @param {String} sec closing time (or 'none')
			 * @param {String} ref_node id of an html element your floater should appear next to
			 * @param {function} insertion_rule function to apply to your floater object and your ref_node
			 * @param {Array} offset [x,y,z]
			 * @param {String} id html element id of your new floater
			 */
			launch:function(url, size, sec, ref_node, insertion_rule, offset, id){
				
				if (sec != 'none')	
					setTimeout("custom.floater.close('"+id+"')", sec * 1000);

				var iframe = document.createElement("iframe");
				generic.extend(iframe,{
					id:id,
					name:id,
					src:url,
					width:size[0] + 'px',
					height:size[1] + 'px',
					frameBorder:"0",
					scrolling:"no",
					allowTransparency:"true",
					style:{
						marginLeft:offset[0] + 'px',
						marginTop:offset[1] + 'px',
						zIndex:offset[2],
						position:'absolute'
					}
				});
				insertion_rule(iframe,$(ref_node));
			},
			/**
			 * Prepares and then activates a floater using /images/a/ifb/doubleclick/indirect.html
			 * @param {Object} content the html to be rendered
			 * @param {Array} size [width,height]
			 * @param {String} sec closing time (or 'none')
			 * @param {String} ref_node id of an html element your floater should appear next to
			 * @param {function} insertion_rule function to apply to your floater object and your ref_node
			 * @param {Array} offset [x,y,z]
			 */
			set:function(content, size, sec, ref_node, insertion_rule, offset){
				generic.cache.set("floater",content);
				var id = "floater_" + Math.ceil(Math.random() * 9999999);
				custom.floater.launch(cookie_dumper_url + "?source=cache:floater", 
					size, sec, ref_node, insertion_rule, offset, id);
				return id;
			}
		}
	}
	var makeSwfParam = function(name, value){
		return ((value)) ? '<param name="' + name + '" value="' + value + '">' : '';
	}
	
	var makeSwfEmbed = function(name, value){
		return ((value)) ? ' ' + name + '="' + value + '" ' : '';
	}
	
	this.flashAdUtils = {
		/**
		 * Constructs an html string required to render a flash object
		 * @param {Object} args
		 */
		makeFlashAd: function(args){
			var swfID = args.id;
			var swfSRC = args.src;
			var swfWidth = args.width;
			var swfHeight = args.height;
			var swfBgcolor = args.bgcolor;
			var swfWmode = args.wmode || "opaque";
			var swfSAlign = args.salign;
			var swfScale = args.scale;
			var swfExtraParams = args.extraParams;
			var swfExtraTAGs = args.extraTAGs;
			
			var swfClickThru = escape(args.clickThru);
			var swfClickTAGs = args.clickTAGs;
			
			
			if (swfID == null || swfSRC == null ||
			swfWidth == null ||
			swfHeight == null) {
				consoleLog('makeFlashAd: Missing required params');
				return;
			}
			
			var clickSTR = '';
			if ((swfClickTAGs)) {
				for (var i = 0; i < swfClickTAGs.length; i++) {
					if (swfClickTAGs[i]) {
						clickSTR = clickSTR +
						'&clickTAG' +
						(i + 1) +
						'=' +
						escape(swfClickTAGs[i]);
					}
				}
			}
			if ((swfExtraTAGs)) {
				for (var tag in swfExtraTAGs) {
					clickSTR = clickSTR +
					'&' +
					escape(tag) +
					'=' +
					escape(swfExtraTAGs[tag]);
				}
			}
			clickSTR += "&ord=" + ad_utils.ord;

			var extra_obj = '';
			var extra_emb = '';
			try {
				var reserved = {
					width: 1,
					height: 1,
					src: 1,
					bgcolor: 1,
					wmode: 1,
					salign: 1,
					scale: 1,
					flashvars: 1
				};
				for (var param in swfExtraParams) {
					if (reserved[param]) 
						continue;
					
					extra_obj = extra_obj +
					makeSwfParam(param, swfExtraParams[param]);
					extra_emb = extra_emb +
					makeSwfEmbed(param, swfExtraParams[param]);
				}
			} 
			catch (err) {
			}
			
			var bgcolor_obj = makeSwfParam('bgcolor', swfBgcolor);
			var bgcolor_emb = makeSwfEmbed('bgcolor', swfBgcolor);
			
			var wmode_obj = makeSwfParam('wmode', swfWmode);
			var wmode_emb = makeSwfEmbed('wmode', swfWmode);
			
			var scale_obj = makeSwfParam('scale', swfScale);
			var scale_emb = makeSwfEmbed('scale', swfScale);
			
			var salign_obj = makeSwfParam('salign', swfSAlign);
			var salign_emb = makeSwfEmbed('salign', swfSAlign);
			
			var swfSTR = '<object ' +
			' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ' +
			' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" ' +
			' width="' +
			swfWidth +
			'" ' +
			' height="' +
			swfHeight +
			'" ' +
			' id="swf_' +
			swfID +
			'">' +
			'<param name="movie" value="' +
			swfSRC +
			'">' +
			'<param name="flashvars" value="env=WEB&clickTAG=' +
			swfClickThru +
			'&' +
			clickSTR +
			'">' +
			'<param name="play" value="true">' +
			'<param name="quality" value="high">' +
			'<param name="allowScriptAccess" value="always">' +
			scale_obj +
			salign_obj +
			bgcolor_obj +
			wmode_obj +
			extra_obj +
			'<!--HACK--><div style="margin-left:0.1px"><embed src="' +
			swfSRC +
			'" ' +
			' flashvars="env=WEB&clickTAG=' +
			swfClickThru +
			'&' +
			clickSTR +
			'" ' +
			' quality="high" ' +
			' play="true" ' +
			' width="' +
			swfWidth +
			'" ' +
			' height="' +
			swfHeight +
			'" ' +
			' name="swf_' +
			swfID +
			'" ' +
			' allowScriptAccess="always" ' +
			' type="application/x-shockwave-flash" ' +
			' pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
			scale_emb +
			salign_emb +
			bgcolor_emb +
			wmode_emb +
			extra_emb +
			'></embed></div></object>';
			
			
			return swfSTR;
		},
		
		canPlayFlash: function(minFlashVersion){
			var canPlay = false;
			
			if (minFlashVersion == null) {
				minFlashVersion = 6;
			}
			else {
				minFlashVersion = parseInt(minFlashVersion);
			}
			
			var flashPlugin = (navigator.mimeTypes && navigator.mimeTypes["application/x-shockwave-flash"]) ? navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin : 0;
			if (flashPlugin && navigator.plugins["Shockwave Flash"]) {
				var words = navigator.plugins["Shockwave Flash"].description.split(" ");
				var pluginVersion;
				for (var i = 0; i < words.length; ++i) {
					if (isNaN(parseInt(words[i]))) 
						continue;
					pluginVersion = parseInt(words[i]);
				}
				canPlay = pluginVersion >= minFlashVersion;
				
			}
			else 
				if (navigator.userAgent &&
				navigator.userAgent.indexOf("MSIE") >= 0 &&
				(navigator.appVersion.indexOf("Win") != -1)) {
				
					try {
						var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.' + minFlashVersion);
						axo.AllowScriptAccess = "always";
						var versionStr = new String(axo.GetVariable("$version"));
						var version = versionStr.match(/([0-9]+)/);
						canPlay = version[1] >= minFlashVersion;
					} 
					catch (err) {
					}
				}
			
			return canPlay;
		}
	};
	
})();

function consoleLog(message){
	try {
		if ((window.console) && (window.console.log)) {
			window.console.log(message);
		}
		else 
			if ((console) && (console.log)) {
				console.log(message);
			}
			else {
				throw (message);
			}
	} 
	catch (err) {
	}
}

/*Activates elements with class="yesScript" */
document.getElementsByTagName('html')[0].className += " scriptsOn";
