if (typeof(PandoraApp) != 'object') {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition = function(good, bad, opt) {
            if (bad) {
                bad({
                    code: 1,
                    message: "geolocation not allowed in Pandora",
                    PERMISSION_DENIED: 1,
                    POSITION_UNAVAILABLE: 2,
                    TIMEOUT: 3,
                    UNKNOWN_ERROR: 0
                });
            }
        }
        navigator.geolocation.watchPosition = navigator.geolocation.getCurrentPosition;
    }
	PandoraApp = {
		TRANSITION_TYPE_SLIDE: "slide",
		TRANSITION_TYPE_FLIP: "flip",
		TRANSITION_TYPE_CURL: "curl",
		TRANSITION_TYPE_FADE: "fade",
		TRANSITION_TYPE_GROW: "grow",
		
		_locations: [],
		_locationTimeout: null,
		_callbacks: [],
		_callMethod: function(/*string*/command, /*hash*/params, /*function|string*/callback) {
			var callbackID = this._callbacks.push(callback) - 1;
			var paramStr = "";
			for (var name in params) {
				if (params[name] != null) {
					paramStr += paramStr ? "&" : "?";
					paramStr += name + "=" + encodeURIComponent(params[name]);
				}
			}
			
			// we need to delay setting location for two reasons:
			// 1) setting location during page load will cause issues,
			// 2) setting location twice in a row will cause first location to be "lost"
			//this._locations.push("PandoraApp://" + command + "/" + callbackID + paramStr);
			this._locations.push("PandoraApp://" + callbackID + "/" + command + paramStr);
			if (this._locationTimeout == null) {
				this._locationTimeout = setTimeout(function() { PandoraApp._setNextLocation() }, 1);
			}
			
			return callbackID;
		},
		_setNextLocation: function() {
			this._locationTimeout = null;
			if (this._locations.length) {
				window.location = this._locations.shift();
				if (this._locations.length) {
					this._locationTimeout = setTimeout(function() { PandoraApp._setNextLocation() }, 1);
				}
			}
		},
		_methodResponse: function(/*string*/callbackID, /*hash*/response) {
			var callback = this._callbacks[callbackID];
			delete this._callbacks[callbackID];
			if (typeof(callback) == "function" || typeof(callback) == "string") {
				// we need to delay execution a bit to prevent strange (and
				// potentially deadly) interactions with the webview host.
				var _this = this;
				setTimeout(function() {_this._callCallback(callback, [response]);}, 1);
			}
		},
		_callCallback: function(nameOrFunction, args) {
			if (typeof nameOrFunction == "function") {
				nameOrFunction.apply(this, args);
			} else {
				// we need to support string names for parity with Android
				window[nameOrFunction].apply(this, args);
			}
		},
		echo: function(value, callback) {
			this._callMethod("echo", {value: value}, callback);
		},
		telephone: function(number, callback) {
			this._callMethod("telephone", {number: number}, callback);
		},
		playMovie: function(cellURL, wifiURL, callback) {
			function fixupURL(url) {
				if (url == null) return null;
				
				if (!url.match(/^https?:\/\//)) {
					if (url.charAt(0) == '/') {
						var matched = location.href.match(/^(\w*:\/\/[^\/]*)/);
					} else {
						var matched = location.href.match(/^(.*\/)[^\/]*$/);
					}
					if (matched) {
						url = matched[1] + url;
					}
				}
				return url;
			}
			cellURL = fixupURL(cellURL);
			wifiURL = fixupURL(wifiURL);
			
			this._callMethod("playMovie", {cellURL: cellURL, wifiURL: wifiURL}, callback);
		},
		sendEmail: function(fromAddress, template, callback) {
			this._callMethod("sendEmail", {fromAddress: fromAddress, template: template}, function(results) { callback(results.success) });
		},
		fetchURL: function(args, callback) {
			var paramsStr = "";
			for (var n in args.params) {
				if (paramsStr != "") {
					paramsStr += "&";
				}
				paramsStr += encodeURIComponent(n) + "=" + encodeURIComponent(args.params[n]);
			}
			args.params = paramsStr;
			this._callMethod("fetchURL", args, function (results) { this._callCallback(callback, [results.responseText]) });
		},
		registerImpression: function(url) {
			this.fetchURL({url: url}, function() {});
		},
        recordAdClick: function(url) {
			this._callMethod("recordAdClick", {url: url});
		},
		createStationFromStationId: function(stationId) {
			this._callMethod("createStationFromStationId", {stationId: stationId});
		},
        createStationFromMusicId: function(musicId) {
            this._callMethod("createStationFromMusicId", {musicId: musicId});
        },
		openSafari: function(url) {
			this._callMethod("openSafari", {url: url});
		},
		canOpenURL: function(url, callback) {
			this._callMethod("canOpenURL", {url: url}, function(results) { this._callCallback(callback, [results.canOpenURL]) });
		},
		setViewportHeight: function(height) {
			this._callMethod("setViewportHeight", {height: String(height)});
		},
		hideViewport: function() {
			this.setViewportHeight(0);
        },
        setStationCreationFollowOnURL: function(url) {
            this._callMethod("setStationCreationFollowOnURL", {url: url});
        },
        disableVideoAdsUntilNextStationChange: function() {
            this._callMethod("disableVideoAdsUntilNextStationChange", {});
        },
		openLandingPage: function(pageURLorHTML, backgroundColor, transitionType, chromeType, loadHandler, closeHandler) {
			var params = { r:1, g:1, b:1, a:1 };
			if (String(pageURLorHTML).match(/^[\w]+:/)) {
				params.pageURL = pageURLorHTML;
			} else {
				params.pageHTML = pageURLorHTML;
			}
			if (backgroundColor) {
				backgroundColor = backgroundColor.replace(/\s/g, "");
				if (backgroundColor.match(/^#(\w\w)(\w\w)(\w\w)$/)) {
					params.r = parseInt(RegExp.$1, 16) / 255;
					params.g = parseInt(RegExp.$2, 16) / 255;
					params.b = parseInt(RegExp.$3, 16) / 255;
				} else if (backgroundColor.match(/^#(\w)(\w)(\w)$/)) {
					params.r = parseInt(RegExp.$1+""+RegExp.$1, 16) / 255;
					params.g = parseInt(RegExp.$2+""+RegExp.$2, 16) / 255;
					params.b = parseInt(RegExp.$3+""+RegExp.$3, 16) / 255;
				} else {
					if (backgroundColor.match(/^rgba\(([\d.]+),([\d\.]+),([\d\.]+),([\d\.]+)\)$/)) {
						params.r = Number(RegExp.$1) / 255;
						params.g = Number(RegExp.$2) / 255;
						params.b = Number(RegExp.$3) / 255;
						params.a = Number(RegExp.$4);
					}
				}
			}
			if (backgroundColor) params.backgroundColor = String(backgroundColor);
			if (transitionType) params.transitionType   = String(transitionType);
			if (chromeType)     params.chromeType       = String(chromeType);
			this._callMethod("openLandingPage", params);
			
			if (loadHandler) {
				this._onLandingPageLoad = function() {
					loadHandler(true);
				}
				this._onLandingPageLoadError = function() {
					loadHandler(false);
				}
			} else {
				this._onLandingPageLoad = this._onLandingPageLoadError = function() {};
			}
			
			if (closeHandler) {
				this._onLandingPageClose = closeHandler;
			} else {
				this._onLandingPageClose = function() {};
			}
		},
		closeLandingPage: function(transitionType) {
			this._callMethod("closeLandingPage", transitionType ? { transitionType: String(transitionType) } : null);
		},
        pause: function() {
            this._callMethod("pauseTrack");
        },
        play: function() {
            this._callMethod("playTrack");
        },
		_onLandingPageLoad: function() {
		},
		_onLandingPageLoadError: function() {
		},
		_onLandingPageClose: function() {
		},
		offerUpgrade: function() {
			this._callMethod("offerUpgrade", {});
		},
		restoreSubscription: function() { // iOS-specific
			this._callMethod("restoreSubscription", {});
		},
        offerTrial: function(sponsor, callback) {
			this._callMethod("offerTrial", {complimentarySponsor: sponsor}, callback);
		},
		// fields is an array of strings which may be "email", "zip", "age" and/or "gender"
		getPersonalInfo: function(who, why, fields, callback) {
			this._callMethod(
				"getPersonalInfo", 
				{
					who: who,
					why: why,
					fields: fields.join(",")
				},
				function (result) {
					var confirmed = result.confirmed;
					delete result.confirmed;
					this._callCallback(callback, [confirmed, result]);
				}
			);
		},
		alert: function(message) {
			this._callMethod("alert", {message: message});
		},
        // Used to close the web view when it's in a custom/non-standard container
        closeCustomWebViewContainer: function() {
			this._callMethod("closeCustomWebViewContainer", { });
        },
		whyAdsPressed: function() {
			this._callMethod("whyAdsPressed", {});
		}
	};
}
