(function() {
	if ( !self.Prism || !self.document || !document.querySelectorAll || ![].filter) return;

	var adapters = [];
	function registerAdapter(adapter) {
		if (typeof adapter === "function" && !getAdapter(adapter)) {
			adapters.push(adapter);
		}
	}
	function getAdapter(adapter) {
		if (typeof adapter === "function") {
			return adapters.filter(function(fn) { return fn.valueOf() === adapter.valueOf()})[0];
		}
		else if (typeof adapter === "string" && adapter.length > 0) {
			return adapters.filter(function(fn) { return fn.name === adapter})[0];
		}
		return null;
	}
	function removeAdapter(adapter) {
		if (typeof adapter === "string")
			adapter = getAdapter(adapter);
		if (typeof adapter === "function") {
			var index = adapters.indexOf(adapter);
			if (index >=0) {
				adapters.splice(index,1);
			}
		}
	}

	Prism.plugins.jsonphighlight = {
		registerAdapter: registerAdapter,
		removeAdapter: removeAdapter,
		highlight: highlight
	};
	registerAdapter(function github(rsp, el) {
		if ( rsp && rsp.meta && rsp.data ) {
			if ( rsp.meta.status && rsp.meta.status >= 400 ) {
				return "Error: " + ( rsp.data.message || rsp.meta.status );
			}
			else if ( typeof(rsp.data.content) === "string" ) {
				return typeof(atob) === "function"
					? atob(rsp.data.content.replace(/\s/g, ""))
					: "Your browser cannot decode base64";
			}
		}
		return null;
	});
	registerAdapter(function gist(rsp, el) {
		if ( rsp && rsp.meta && rsp.data && rsp.data.files ) {
			if ( rsp.meta.status && rsp.meta.status >= 400 ) {
				return "Error: " + ( rsp.data.message || rsp.meta.status );
			}
			else {
				var filename = el.getAttribute("data-filename");
				if (filename == null) {
					// Maybe in the future we can somehow render all files
					// But the standard <script> include for gists does that nicely already,
					// so that might be getting beyond the scope of this plugin
					for (var key in rsp.data.files) {
						if (rsp.data.files.hasOwnProperty(key)) {
							filename = key;
							break;
						}
					}
				}
				if (rsp.data.files[filename] !== undefined) {
					return rsp.data.files[filename].content;
				}
				else {
					return "Error: unknown or missing gist file " + filename;
				}
			}
		}
		return null;
	});
	registerAdapter(function bitbucket(rsp, el) {
		return rsp && rsp.node && typeof(rsp.data) === "string"
			? rsp.data
			: null;
	});

	var jsonpcb = 0,
	    loadstr = "Loading…";

	function highlight() {
		Array.prototype.slice.call(document.querySelectorAll("pre[data-jsonp]")).forEach(function(pre) {
			pre.textContent = "";

			var code = document.createElement("code");
			code.textContent = loadstr;
			pre.appendChild(code);

			var adapterfn = pre.getAttribute("data-adapter");
			var adapter = null;
			if ( adapterfn ) {
				if ( typeof(window[adapterfn]) === "function" ) {
					adapter = window[adapterfn];
				}
				else {
					code.textContent = "JSONP adapter function '" + adapterfn + "' doesn't exist";
					return;
				}
			}

			var cb = "prismjsonp" + ( jsonpcb++ );
			
			var uri = document.createElement("a");
			var src = uri.href = pre.getAttribute("data-jsonp");
			uri.href += ( uri.search ? "&" : "?" ) + ( pre.getAttribute("data-callback") || "callback" ) + "=" + cb;

			var timeout = setTimeout(function() {
				// we could clean up window[cb], but if the request finally succeeds, keeping it around is a good thing
				if ( code.textContent === loadstr )
					code.textContent = "Timeout loading '" + src + "'";
			}, 5000);
			
			var script = document.createElement("script");
			script.src = uri.href;

			window[cb] = function(rsp) {
				document.head.removeChild(script);
				clearTimeout(timeout);
				delete window[cb];

				var data = "";
				
				if ( adapter ) {
					data = adapter(rsp, pre);
				}
				else {
					for ( var p in adapters ) {
						data = adapters[p](rsp, pre);
						if ( data !== null ) break;
					}
				}

				if (data === null) {
					code.textContent = "Cannot parse response (perhaps you need an adapter function?)";
				}
				else {
					code.textContent = data;
					Prism.highlightElement(code);
				}
			};

			document.head.appendChild(script);
		});
	}

	highlight();
})();