if (typeof(Patron) == "undefined") {
	Patron = {
		_adDefinitionArray: new Array(),

		// do an explicit init so that the tests can
		// zap everything and start over.
		init: function() {
			// config
			this.displayAds = true;
			this.inQA       = 'false' == 'true';

			// targting vars
			this.zone  = null; // MUST be set or ads won't work.
			this.ord   = this.generateOrd();
			this.ag    = "0";
			this.gnd   = "0";
            this.zip   = "";
            this.site  = "default";
			this.deviceKeys = "";
			this.hours = "0";
            this.comped = "0";
			this.genre = null;
            this.fam = null;
			this.tile  = 0;
			this.needClean = false;
			this.antiTargetGenre = false;

			if( document.cookie != null && document.cookie.match(/v3ad=(\d+):(\d+):(\d+):(\w*):([\w,]*):(\d+):(\d):(\d)/) ) {
				this.displayAds = RegExp.$1 != '0';
				this.ag  = RegExp.$2;
				this.gnd = RegExp.$3;
				var zipcode = parseInt(RegExp.$4);
				if (zipcode > 0) {
					this.zip = zipcode;
				}
				this.deviceKeys = RegExp.$5;
				this.hours = RegExp.$6;
                this.comped = RegExp.$7;
				this.needClean = RegExp.$8 == '1'; //default to false
			}

			if( document.cookie != null && document.cookie.match(/v2pub=(\w+)/) ) {
				this.site = RegExp.$1;
			}
		},
	
		setZone: function(zone) {
			this.zone = zone;
		},
		
		setSite: function(site) {
			this.site = site;
		},

		setProfile: function(cookieValue) {
			var date = new Date();
			date.setTime(date.getTime()+(30*24*60*60*1000));	// 30 days
			var cookieStr = "v3ad=" + cookieValue + "; ";
			cookieStr += "expires=" + date.toGMTString() + "; ";
			cookieStr += "path=/; ";
			if (document.domain != null && document.domain.match(/pandora.com$/) ) {
				cookieStr += "domain=.pandora.com;";
			}
			document.cookie = cookieStr;

			// reset vars for future embeds
			var args = cookieValue.split(":");
			this.displayAds = String(args[0]) != '0';
			this.ag         = args[1];
			this.gnd        = args[2];
            this.zip        = args[3];
			this.deviceKeys = args[4];
			this.hours      = args[5];
            this.comped     = args[6];
			this.needClean  = String(args[7]) == '1';//default to false
		},
		
		setQA: function(bool) {
			this.inQA = bool;
		},
		
		getDisplayAds: function() {
			return this.displayAds;
		},
		
		cacheGenre: function(genre) {
			this.genre = genre;
		},

		cacheAntiTargetGenre: function(antiTarget) {
			this.antiTargetGenre = antiTarget;
		},

        cacheFamIndex: function(fam) {
            this.fam = fam;
        },

		generateOrd: function() {
			return new Date().getTime() + "" + Math.floor(Math.random()*1000000);
		},
		
		generateEmbedUrl: function(path, options, id) {
			if (options == null)
				options = {};

			if (options.ord == null)
				options.ord = this.ord;

			if (options.genre != null) {
				// a new genre is specified.  We need to cache it.  (RADIO-4126)
				this.cacheGenre(options.genre);
			} else if (this.genre != null) {
				// see if we have a cached genre around
				options.genre = this.genre;
			}
            if (options.fam != null) {
                // a new family index is specified.  We need to cache it.
                this.cacheFamIndex(options.fam);
            } else if (this.fam != null) {
                // see if we have a cached family index around
                options.fam = this.fam;
            }
			if (options.clean != null) {
				// antiTarget was specified in the options, cache it.
				this.cacheAntiTargetGenre(options.clean=='1');
			} else {
				// we've got the needClean or antiTargetGenre flag on, add clean to options
				options.clean = (this.needClean || this.antiTargetGenre) ? '1' : '0';
            }

			if (window.location.search != null && window.location.search.match(/patron=([^&]+)/)) {
				options.patron = RegExp.$1;
			}

			var pairs = [];
			for (var n in options) {
				pairs[pairs.length] = n + "=" + options[n];
			}
			if (id != null) {
				pairs[pairs.length] = "_id=" + id;
			}
			
			return path + "?" + pairs.join('&');
		},

		setAdLoadOrder: function(idList) {
			// Ad IFRAMEs will not be loaded automatically after this call, so be sure to call Patron.loadNextAd() upon page load
			// to initiate the (sequential) loading of the IFRAMEs
			this.adLoadOrder = idList;
		},

		insertIFrame: function(id, width, height, options, embedPrefix) {
			if (!this.displayAds)
				return;

			// if zone isn't set, it's an error.  The zone must be set.
			if (this.zone == null)
				return;

			document.write(this.generateIFrame(id, width, height, options, embedPrefix));
		},

		generateIFrame: function(id, width, height, options, embedPrefix) {
			if (!options)
				options = {};

			options.sz = width + "x" + height;
			if (this.adLoadOrder == null && options.tile == null)
				options.tile = ++this.tile;

			if (embedPrefix == null)
				embedPrefix = this.zone;

			var url = this.generateEmbedUrl("/include/" + embedPrefix + "AdEmbed.html", options, id);

			if (options.sz == "2000x8") {
				// not the real size...
				width = 728;
				height = 90;
			}

			if (options.sz == "2000x13") {
				// promotional gallery links - actual iframe dimensions
				width = 152;
				height = 100;
			}

			// unique ids are necessary to prevent Safari caching bugs
			var unique = id + (new Date().getTime());
			var iframe = '<div id="' + id + '">'
			               + '<iframe id="' + unique +'" '
			               + (this.adLoadOrder == null ? 'src="' + url + '" ' : ' ')
			               + 'frameborder=0 marginheight=0 marginwidth=0 '
			               + 'scrolling="no" allowTransparency="true" '
			               + 'width="' + width + '" '
			               + 'height="' + height + '"></iframe></div>';

			this._adDefinitionArray.push(
			{
				unique: unique,
				options: options,
				id: id
			});

			return iframe;
		},

		loadNextAd: function(idJustLoaded) {
			if (this.adLoadOrder != null) {
				var foundLatestAd = false;
				if (idJustLoaded == null) {
					// First call, initiate loading sequence
					this.tile = 0;
					foundLatestAd = true;
				}
				for (var i=0; i < this.adLoadOrder.length; i++) {
					if (this.adLoadOrder[i] == idJustLoaded) {
						foundLatestAd = true;
					} else if (foundLatestAd) {
						if (this.refreshAds(this.adLoadOrder[i])) {
							return;
						}
					}
				}
			}
		},

		// fetch and paint new ads for the backstage page
		refreshAds: function(id){
			//regenerate the ord
			if (id == null) {
				this.ord = this.generateOrd();
				if (this.adLoadOrder != null) {
					this.loadNextAd();
					return;
				}
			}

			for (var i = 0; i < this._adDefinitionArray.length; i++){
				var adDefinition = this._adDefinitionArray[i];

				if (id == null || id == adDefinition.id) {
					if (adDefinition.options == null){
						adDefinition.options = {};
					}

					// use the new ord
					adDefinition.options.ord = this.ord;

					// insert tile sequence if not defined
					if (adDefinition.options.tile == null) {
						adDefinition.options.tile = ++this.tile;
					}
					
					var url = this.generateEmbedUrl("/include/" + this.zone + "AdEmbed.html", adDefinition.options, id);

					this.setIFrameURL(adDefinition.unique, url);

					if (id != null) {
						return true;
					}
				}
			}
			return false;
		},

		setIFrameURL: function(iframeID, url){
			$("#" + iframeID).attr("src", url);
		},

		updateTargetingOptions: function(options) {
			if (!this.displayAds)
				return null;

			// if zone isn't set, it's an error.  The zone must be set.
			if (this.zone == null)
				return null;

			if (!options)
				options = {};

			options.ord   = this.ord;
			options.ag    = this.ag;
			options.gnd   = this.gnd;
            options.zip   = this.zip;
			options.hours = this.hours;
			if (options.tile == null) {
				options.tile = ++this.tile;
			}
            options.comped = this.comped;
			options.clean = (this.needClean || this.antiTargetGenre) ? '1' : '0';

			// we can have a comma separated list of devices this listener is known 
			// to have
			this.deviceKeys = this.deviceKeys.replace(/^\W*/, "").replace(/\W*$/, "")
			if (this.deviceKeys.length > 0) {
				var keys = this.deviceKeys.split(",")
				for(var i = 0; i < keys.length; i++) {
					options[keys[i]] = "1";
				}
			}

			return options;
		},

		// Render an ad inline
		// *** WARNING: using this function will break the proper load order of ads,
		// because it is rendered inline instead of in an IFRAME.  Therefore, it should
		// not be used on backstage pages. See RADIO-8750. ***
		insertScript: function(width, height, options) {
			options = this.updateTargetingOptions(options);
			if (options == null) return;

			options.sz = width + "x" + height;
			if (options.tile == null)
				options.tile = ++this.tile;
			
			if (options.ord == null)
				options.ord = this.ord;

			var url = this.generateDCUrl(options, "adj");

			document.write('<script src="' + url + '"><\/script>');
		},
		
		// Used for blog leaderboards.
		// Not capable of supporting expandables.
		insertDCFrame: function(id, width, height, options) {
			options = this.updateTargetingOptions(options);
			if (options == null) return;
			
			options.sz = width + "x" + height;
			if (options.tile == null)
				options.tile = ++this.tile;
			
			if (options.ord == null)
				options.ord = this.ord;

			var url = this.generateDCUrl(options, "adi");
			
			// need a unique ID on the IFrame for Safari
			var adId = "ad" + (new Date().getTime());
			html = '<iframe src="' + url + '" id="' + adId + '" ' +
				   'frameborder="0" scrolling="no" marginheight="0" marginwidth="0" ' +
				   'topmargin="0" leftmargin="0" allowtransparency="true" width="' + width + '" ' +
				   'height="' + height + '"></iframe>';

			document.write('<div id="' + id + '">' + html + '</div>');
		},
		
		generateDCUrl: function(options, embedType) {

			// split things up to dete blockers
			var url = 'http://a'+'d.doublecl'+'ick.net/' + embedType + '/pand.';
		
			url += this.site;
			url += "/";
			url += this.inQA ? "test" : "prod";
			url += ".";
			url += this.zone;

			for (var n in options) {
				if (n != "sz" && n != "ord" && n != "tile" && options[n] != null && options[n] !== "") {
					url += ";" + n + "=" + options[n];
				}
			}

			// sz, tile, and ord need to be last on the URL according
			// Dart docs.  ord must be very last!  RADIO-4408
			url += ";sz=" + options.sz;
			if (options.tile != null)
				url += ";tile=" + options.tile;
			url += ";ord=" + options.ord;

			return url;
		},

		// for use within an embedded IFrame.  "private" -- do not call directly.
		insertAd: function(options) {
			options = this.updateTargetingOptions(options);
			if (options == null) return;
			var id = null;

			// sz is determined from the URL
            if(window.location.search != null && window.location.search.length > 1) {
				var query = window.location.search.substr(1);
				var pairs = query.split("&");
				for (var i = 0; i < pairs.length; i++) {
					var tuple = pairs[i].split("=");
					
					// skip vars that begin with _ so you can optionally avoid
					// inclusion in the options hash.
					if (tuple[0].charAt(0) != '_') {
						options[tuple[0]] = tuple[1];
					}

					if (tuple[0] == '_id') {
						id = tuple[1];
					}
				}
			}			

			var url = this.generateDCUrl(options, "adj");
			
			document.write('<script src="' + url + '"><\/script>');
			if (id != null && window.parent != null && window.parent.Patron != null) {
				// "loadNextAd" is called here instead of in the IFRAME onload because the IFRAME onload can be fired before
				// the DART script URL is actually loaded (probably because the script tag is written using document.write)
				window.parent.Patron.loadNextAd(id);
			}
		}
	};
	
	Patron.init();
}
