(function() {
	var MAGIC_PEAK = 0x5045414b;

	var WIDTH = [632.0, 734.0, 446.0];
	var HEIGHT = [96.0, 32.0, 32.0];
	var BUTTON_WIDTH = [78.0, 32.0, 32.0];
	var BUTTON_HEIGHT = [78.0, 32.0, 32.0];

	var TEXT_WIDTH = 78.0;
	var TEXT_HEIGHT = 17.0;
	var TEXT_COLOR = '#e5e5e5';
	var STYLE_BACKGROUND = 'rgb(40,40,40)';
	var STYLE_PLAYED = 'rgba(255,127,0,0.1)';
	var CIRCLE_INNER = 'rgb(229,229,229)';
	var CIRCLE_OUTER = 'rgb(255,106,0)';
	var WAVEFORM_LOADED = 'rgb(204,204,204)';
	var WAVEFORM_LOADING = 'rgb(90,90,90)';

	/**
	 * Embeds a player element into the given HTML.
	 *
	 * E.g.
	 *
	 * $(this).player(0, "publicKey", -1, 1.0, null, { ogg: "myogg.ogg", mp3: null, pks: null })
	 * $(this).player(1, "publicKey", 0, 1334.0, "session-hash", { ogg: null, mp3: "my.mp3", pks: "my.pks" })
	 *
	 * @param type The type of the player. 0 is single track page, 1 is browse list.
	 */
	var $$ = $.fn.player = function(type, key, version, swf, duration, hash, params) {
		var targetWidth = WIDTH[type];
		var targetHeight = HEIGHT[type];
		if($$.useHTML5()) {
			var html = '<canvas width="'+targetWidth+'" height="'+targetHeight+'"></canvas><audio></audio>';
			var appended = false;

			this.html(html);

			var audio = this.find('audio').get(0);
			var canvas = this.find('canvas').get(0);
			var peaks = null;
			var ctx = canvas.getContext('2d');

			ctx.font = '11px arial, sans-serif';
			ctx.fillStyle = STYLE_BACKGROUND;
			ctx.fillRect(0.0, 0.0, targetWidth, targetHeight);

			var o = this;

			o.audio = audio;
			o.peakRes = null;
			o.canvas = canvas;
			o.peaks = peaks;
			o.ctx = ctx;
			o.type = type;

			$$.drawPlay(type, ctx);

			var reset = function() {
				try { audio.currentTime = 0.0; } catch(e) {}
				audio.pause();
				$$.drawWaveform(type, ctx, audio, o.peaks, o.peakRes);
				$$.drawText(type, ctx, audio, duration);
				$$.drawPlay(type, ctx);
			};

			var ignorePause = false;
			var playlistObject = {
				publicKey: key,

				onPlay: function() {
					if(!appended) {
						try {
							if(null != params.ogg) { $(audio).append('<source src="'+params.ogg+'" type="audio/ogg"></source>'); }
							if(null != params.mp3) { $(audio).append('<source src="'+params.mp3+'" type="audio/mpeg"></source>'); }
						} finally {
							appended = true;
						}
					}
					try { audio.currentTime = 0.0; } catch(e) {}
					audio.play();
				},
				onStop: function() { ignorePause = true; reset(); }
			};

			$(audio).bind('play',function() {
				$$.drawPause(type, ctx);
				cular.playlist.onPlay(playlistObject);
			}).bind('pause', function() {
				$$.drawPlay(type, ctx);
				if(!ignorePause) {
					cular.playlist.onPause();
				} else {
					ignorePause = false;
				}
			}).bind('ended', function() {
				reset();
				cular.playlist.onComplete();
			}).bind('progress timeupdate suspend', function() {
				if(!$$.fairlyUnderpoweredDevice()) {
					$$.drawWaveform(type, ctx, audio, o.peaks, o.peakRes);
				}
				$$.drawText(type, ctx, audio, duration);
			});


			$(window).bind('beforeunload', function() {
				if(appended && !audio.paused) {
					audio.pause();
					try {
						if(audio.stop) {
							audio.stop();
						}
					} catch(e) {}
				}
			});

			$(canvas).click(function(e) {
				if(!appended) {
					try {
						if(null != params.ogg) { $(audio).append('<source src="'+params.ogg+'" type="audio/ogg"></source>'); }
						if(null != params.mp3) { $(audio).append('<source src="'+params.mp3+'" type="audio/mpeg"></source>'); }
					} finally {
						appended = true;
					}
				}

				var xPosition = (e.offsetX == undefined ? e.layerX : e.offsetX);

				if(navigator.userAgent.toLowerCase().indexOf('firefox/4') != -1) {
					xPosition -= this.offsetLeft;
				}

				if(xPosition < BUTTON_WIDTH[type]) {
					if(audio.paused) {
						audio.play();
					} else {
						audio.pause();
					}
				} else {
					if(audio.paused) {
						audio.play();
					}
					
					//TODO listen for buffered
					try {
						audio.currentTime = (duration / 1000.0) * ((xPosition - BUTTON_WIDTH[type]) / (targetWidth - BUTTON_WIDTH[type]));
					} catch(e) {}
				}
			});

			if(null != params.pks) {
				$.get(params.pks, function(buffer) {
					var data = o.peaks = new jDataView(buffer);
					var byteOffset = 4;
					var header = data.getUint32(0);
					var samplesEachPixel = duration * 44.1 / (targetWidth - BUTTON_WIDTH[type]);

					if(header == MAGIC_PEAK) {
						var peakData = [];
						var n = data.getUint8( byteOffset++ );
						var dataOffset = 5 + 5 * n;

						for(var i = 0; i < n; ++i) {
							var shift = data.getUint8(byteOffset++);
							var numPeaks = data.getUint32(byteOffset);

							byteOffset += 4;

							peakData[i] = {
								shift: shift,
								numPeaks: numPeaks,
								readOffset: dataOffset,
								samplesEachPixel: samplesEachPixel
							};

							dataOffset += numPeaks;
						}

						o.peakRes = $$.getBestPeakData(samplesEachPixel, peakData);
						$$.drawWaveform(type, o.ctx, o.audio, o.peaks, o.peakRes);
					} else {
						alert('ERROR: Not a valid peak file.');
					}

				}, 'binary');
			}
			$$.drawText(type, ctx, audio, duration);
			cular.playlist.append(playlistObject);
		} else {
			var id = 'p-'+key+'-'+version+'-'+((Math.random()*100000.0)|0).toString();

			this.flash({
					src: swf,
					id: id,
					width: WIDTH[type],
					height: HEIGHT[type],
					hasPriority: false,
					menu: false,
					bgcolor: '#282828',
					allowScriptAccess: 'always',
					base: '/',
					quality: 'high',
					wmode: 'opaque',
					flashvars: {
						environment: 'audiotool.com',
						cularSessionId: hash,
						key: key,
						version: version,
						pks: params.pks,
						mp3: params.mp3,
						ogg: params.ogg,
						duration: duration,
						id: id,
						type: type,
						width: WIDTH[type],
						height: HEIGHT[type]
					}
				},{
					version: '10.2.154.25',
					expressInstall: false,
					update: false
				},
				null,
				function(){$(this).append('<div class="no-player">You need Flash or HTML5 to listen to audio tracks.</div>')}
			);

			cular.playlist.append(id, key);
		}
	};

	/**
	 * Finds the best peak shift.
	 *
	 * @param resolution The number of samples per pixel.
	 * @param peakData Array of available peak shifts.
	 * @return The best shift for the samples per pixel.
	 */
	$$.getBestPeakData = function(resolution, peakData) {
		var shift = Math.log( Math.abs( resolution ) ) / Math.LN2;
		var i = 0;
		var n = peakData.length;

		for(; i < n; ++i) {
			if(shift < peakData[i].shift) {
				break;
			}
		}

		if(i == 0) {
			return null;
		}

		return peakData[--i];
	};

	$$.drawText = function(type, ctx, audio, durInMs) {
		if(0 != type) { return; }
		var duration = durInMs / 1000.0;
		var elapsed = isNaN(audio.currentTime) ? 0.0 : Math.floor(audio.currentTime);
		var dmin = Math.floor(duration / 60.0);
		var dsec = Math.floor(duration - dmin * 60.0);
		var emin = Math.floor(elapsed / 60.0);
		var esec = Math.floor(elapsed - emin * 60.0);
		ctx.clearRect(0.0, HEIGHT[type] - TEXT_HEIGHT, TEXT_WIDTH, TEXT_HEIGHT);
		ctx.fillStyle = STYLE_BACKGROUND;
		ctx.fillRect(0.0, HEIGHT[type] - TEXT_HEIGHT, TEXT_WIDTH, TEXT_HEIGHT);
		ctx.fillStyle = TEXT_COLOR;
		var text = emin+'.'+(esec < 10.0 ? '0' : '')+esec+' | '+dmin+'.'+dsec;
		var measure = ctx.measureText(text);
		ctx.fillText(text, (TEXT_WIDTH - measure.width) * 0.5, HEIGHT[type] - TEXT_HEIGHT + 8.0, TEXT_WIDTH)
	};

	/**
	 * Renders the waveform and overlay.
	 *
	 * @param ctx Canvas 2D context.
	 * @param audio Audio element.
	 * @param peaks jDataView bytes.
	 * @param peakRes The chosen peak shift.
	 */
	$$.drawWaveform = function(type, ctx, audio, peaks, peakRes) {
		var xOffset = BUTTON_WIDTH[type] + (type == 1 || type == 2 ? 2.0 : 0.0);
		var width = WIDTH[type] - xOffset;
		var played = (audio.currentTime / audio.duration);
		var loaded = 1.0;

		if ((audio.buffered != undefined) && (audio.buffered.length != 0)) {
			loaded = (audio.buffered.end(0) / audio.duration);
		}

		var playedInPx = Math.round(played * width);
		var loadedInPx = Math.round(loaded * width);
		
		ctx.clearRect(xOffset, 0.0, width, HEIGHT[type]);

		if( peakRes ) {
			var waveFormHeight = HEIGHT[type] - (type == 0 ? 21.0 : 2.0);
			var h2 = (type == 0 ? 8.0 : 0.0) + (waveFormHeight * 0.5);
			var numPeaks = peakRes.numPeaks;
			var readOffset = peakRes.readOffset;
			var ratio = peakRes.samplesEachPixel / (1 << peakRes.shift);
			var lh;
			var p0 = 0.0;
			var p1;
			var pn;
			var amp;
			var max;
			var position;

			for(var x = 0; x < width ; ++x) {
				p1 = x * ratio;
				position = readOffset + p0;
				pn = p1 - p0;

				if( p1 > numPeaks ) {
					pn = numPeaks - p0;
				}

				max = 0;

				while(--pn > -1) {
					amp = peaks.getUint8(position++);

					if(max < amp) {
						max = amp;
					}
				}

				lh = max * 0.003921568627451 * waveFormHeight + 0.5;//0.003921568627451 = 1/0xff

				if(lh <= 1.0) {
					lh = 1.0;
				}

				lh *= 0.5;
				p0 = p1;

				if(x > 0.0) {
					ctx.strokeStyle = (x > loadedInPx) ? WAVEFORM_LOADING : WAVEFORM_LOADED;
					ctx.beginPath();
					ctx.moveTo(xOffset + x + 0.5, h2 - lh );
					ctx.lineTo(xOffset + x + 0.5, h2 + lh );
					ctx.stroke();
				}
			}
		}

		if(playedInPx > 1.0) {
			ctx.fillStyle = STYLE_PLAYED;
			ctx.fillRect(xOffset, 0.0, playedInPx, HEIGHT[type]);

			ctx.strokeStyle = CIRCLE_OUTER;
			ctx.beginPath();
			ctx.moveTo(xOffset + playedInPx + 0.5, 0.0);
			ctx.lineTo(xOffset + playedInPx + 0.5, HEIGHT[type]);
			ctx.stroke();
		}
	};

	/**
	 * Draws the pause button.
	 *
	 * @param ctx Canvas 2D context.
	 */
	$$.drawPause = function(type, ctx) {
		$$.drawCircle(type, ctx);
		if(type == 0) {
			ctx.fillStyle = CIRCLE_INNER;
			ctx.fillRect(30.0, 30.0, 7.0, 18.0);
			ctx.fillStyle = CIRCLE_INNER;
			ctx.fillRect(41.0, 30.0, 7.0, 18.0);
		} else if(type == 1 || type == 2) {
			ctx.fillStyle = CIRCLE_INNER;
			ctx.fillRect(11.0, 11.0, 4.0, 10.0);
			ctx.fillStyle = CIRCLE_INNER;
			ctx.fillRect(17.0, 11.0, 4.0, 10.0);
		}

	};

	/**
	 * Draws the play button.
	 * 
	 * @param ctx Canvas 2D context.
	 */
	$$.drawPlay = function(type, ctx) {
		$$.drawCircle(type, ctx);
		ctx.fillStyle = CIRCLE_INNER;
		ctx.beginPath();
		if(type == 0) {
			ctx.moveTo(33.0, 28.0);
			ctx.lineTo(49.0, 38.5);
			ctx.lineTo(33.0, 49.0);
			ctx.lineTo(33.0, 28.0);
		} else if(type == 1 || type == 2) {
			ctx.moveTo(13.0, 11.0);
			ctx.lineTo(21.0, 15.5);
			ctx.lineTo(13.0, 20.0);
			ctx.lineTo(13.0, 11.0);
		}
		ctx.fill();
	};

	/**
	 * Draws the underlying circle for pause or play button.
	 *
	 * @param ctx Canvas 2D context.
	 */
	$$.drawCircle = function(type, ctx) {
		ctx.clearRect(0.0, 0.0, BUTTON_WIDTH[type], BUTTON_HEIGHT[type]);
		ctx.fillStyle = STYLE_BACKGROUND;
		ctx.fillRect(0.0, 0.0, BUTTON_WIDTH[type], BUTTON_HEIGHT[type]);
		ctx.fillStyle = CIRCLE_OUTER;
		ctx.beginPath();
		if(type == 0) {
			ctx.arc(39.0, 39.0, 27.0, 0.0, Math.PI * 2.0, true);
		} else {
			ctx.arc(16.0, 16.0, 14.0, 0.0, Math.PI * 2.0, true);
		}
		ctx.fill();
	};
	
	/**
	 * Whether or not HTML5 audio is supported.
	 */
	$$.useHTML5 = function() {
		if(
			jQuery.fn.flash.hasFlash(10,2,159,1) ||
			navigator.userAgent.indexOf('MSIE') != -1 ||
			navigator.userAgent.indexOf('Android 2.2') != -1 ||
			navigator.userAgent.indexOf('Android 1.') != -1
		) {
			//
			// Android < 2.3 tells that it supports <audio> and audio/ogg but in fact
			// it is not able to play it and this is a known bug -- bummer :\
			//
			
			//
			// Do not even bother trying if it is IE9 or lower.
			// 
			return false;
		} else {
			return (!!document.createElement('canvas').getContext) && (!!document.createElement('audio').canPlayType)
		}
	};

	/**
	 * Whether or not OGG/Vorbis is supported.
	 */
	$$.useVorbis = function() {
		var a = document.createElement('audio');
		return !!(a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
	};

	$$.fairlyUnderpoweredDevice = function() { return navigator.userAgent.indexOf('iPad') != -1; };
})();


