var Pocket = (function() {

var hasCanvas = false;

var testCanvas = document.createElement("canvas");

if (testCanvas.getContext && testCanvas.getContext("2d"))
	hasCanvas = true;

if (!hasCanvas)
	return;


var options = {
	w : 360,
	h : 360,
	demoMode : false
};


var displayWidth = 0;
var displayHeight = 0;

var displayQuality = 1;

var screenWidth = 0;
var screenHeight = 0;

var canvas, ctx;		// the display canvas
var tmpCanvas, tmpCtx;		// temporary canvas used for various things
var bufferCanvas, bufferCtx;	// the buffer canvas where stuff happens before being painted to the display
var textCanvas, textCtx;	// canvas used for rendering text before drawing it to buffer

var presetObject;
var presetCode;

var mouseX = 0.5;
var mouseY = 0.5;
var mouseIsDown = false;
var mouseDownX = 0;
var mouseDownY = 0;
var mouseDownTime = 0;
var mouseOver = false;

var frameNum = 0;

var validPreset = true;
var pause = false;
var isRunning = false;

var soundManagerLoaded = false;

var images = [];
var videos = [];
var fonts = [];
var sounds = [];
var activeSound;
var startTime = new Date().getTime()
var pauseTime = 0;
var pauseTimeOffset = 0;
var now;

var processList = [];	// list of Pixastic action for postprocessing the frame

// helper functions
var dc = function(tag) { return document.createElement(tag); };
var $ = function(id) { return document.getElementById(id); };

var prepareFrame;
var compositeMode;
var compositeAlpha = 1;

var hasImageData = false;	// does browser support imagedata, needed for Pixastic processing as well as for compositing effects
var hasLighter = false;		// does browser support "lighter" compositing op (faster than Pixastic)


/* Pre3D stuff */
var Pre3dactive = false;
var Pre3dRenderer;
function Pre3dCamera(ix, iy, iz, tx, ty, tz) {
      var ct = Pre3dRenderer.camera.transform;
      ct.reset();
      ct.rotateZ(tz);
      ct.rotateY(ty);
      ct.rotateX(tx);
      ct.translate(ix, iy, iz);
}
function Pre3dInit() {
	Pre3dRenderer = new Pre3d.Renderer(bufferCanvas);
	Pre3dactive = true;
}
/* end of Pre3D stuff */


function init(screen, opt) {
	options = opt || defaultOptions;
	displayWidth = options.w;
	displayHeight = options.h;
	screenWidth = displayWidth * displayQuality;
	screenHeight = displayHeight * displayQuality;

	// test for imagedata and "lighter" operation
	var testCanvas = dc("canvas");
	testCanvas.width = testCanvas.height = 1;
	var testCtx = testCanvas.getContext("2d");
	if (testCtx.getImageData) {
		hasImageData = true;
		testCtx.fillStyle = "rgb(50,50,50)";
		testCtx.fillRect(0,0,1,1);
		testCtx.globalCompositeOperation = "lighter";
		var lightCanvas = dc("canvas");
		lightCanvas.width = lightCanvas.height = 1;
		var lightCtx = lightCanvas.getContext("2d");
		lightCtx.fillStyle = "rgb(75,75,75)";
		lightCtx.fillRect(0,0,1,1);
		testCtx.drawImage(lightCanvas,0,0);
		var data = testCtx.getImageData(0,0,1,1).data;
		if (data[0] == 125) {
			hasLighter = true;
		}
	}

	pre3dCanvas = dc("canvas");
	pre3dCanvas.width = screenWidth;
	pre3dCanvas.height = screenHeight;

	tmpCanvas = dc("canvas");
	tmpCanvas.width = screenWidth;
	tmpCanvas.height = screenHeight;
	tmpCtx = tmpCanvas.getContext("2d")

	bufferCanvas = dc("canvas");
	bufferCanvas.width = screenWidth;
	bufferCanvas.height = screenHeight;
	bufferCtx = bufferCanvas.getContext("2d")

	textCanvas = dc("canvas");
	textCtx = textCanvas.getContext("2d")

	canvas = dc("canvas");
	canvas.id = "screencanvas";
	canvas.width = screenWidth;
	canvas.height = screenHeight;

	screen.appendChild(canvas);

	var inputLayer = dc("div");
	inputLayer.id = "inputlayer";
	screen.appendChild(inputLayer);

	ctx = canvas.getContext("2d")

	inputLayer.onmousemove = function(e) {
		if (pause) return;
		var canvasPos = getElementPos(inputLayer);
		mouseX = (e.clientX - canvasPos.x) / canvas.offsetWidth;
		mouseY = (e.clientY - canvasPos.y) / canvas.offsetHeight;
                mouseOver = true;
		presetObject.mousemove(mouseX, mouseY);
	}
	inputLayer.onmousedown = function(e) {
		if (pause) return;
		mouseIsDown = true;
		mouseDownTime = new Date().getTime() - pauseTimeOffset;
		var canvasPos = getElementPos(inputLayer);
		mouseDownX = (e.clientX - canvasPos.x) / inputLayer.offsetWidth;
		mouseDownY = (e.clientY - canvasPos.y) / inputLayer.offsetHeight;
		presetObject.mousedown(mouseDownX, mouseDownY);
	}
	inputLayer.onmouseup = function(e) {
		if (pause) return;
		mouseIsDown = false;

		var canvasPos = getElementPos(inputLayer);
		var mouseX = (e.clientX - canvasPos.x) / inputLayer.offsetWidth;
		var mouseY = (e.clientY - canvasPos.y) / inputLayer.offsetHeight;
		presetObject.mouseup(mouseX, mouseY);
	}

	inputLayer.onclick = function(e) {
		if (pause) return;
		var canvasPos = getElementPos(inputLayer);
		var mouseX = (e.clientX - canvasPos.x) / inputLayer.offsetWidth;
		var mouseY = (e.clientY - canvasPos.y) / inputLayer.offsetHeight;
		presetObject.mouseclick(mouseX, mouseY);
	}

	inputLayer.onmouseout = function() {
		if (pause) return;
		mouseX = 0.5;
		mouseY = 0.5;
                mouseOver = false;
	}


	var fontctr = $("fonts");
	var fontimgs = fontctr.getElementsByTagName("img");
	for (var i=0;i<fontimgs.length;i++) {
		fonts[fontimgs[i].id] = fontimgs[i];
	}

	setupSoundManager();

	if (options.demoMode) {
		startDemo(options.demoPresets);
	} else {
		if (options.watchHash) {
			if (window.location.hash)
				loadScript(window.location.hash.substring(1));
		
			var hash = window.location.hash;
		
			setInterval(function() {
				if (location.hash !== hash) {
					stopSound();
					hash = window.location.hash;
					loadScript(hash.substring(1));
				}
			}, 100);
		}
	}

}

var demoQueue;
var demoStartTime;
var demoNextDelay;
var demoDelays;

function startDemo(presets) {
	demoQueue = presets;
	demoStartTime = new Date().getTime();
	demoNextDelay = 0;
	demoDelays = 0;
	setTimeout(nextDemoPart, 200);
}

function nextDemoPart() {
	if (demoQueue.length == 0)
		return;
	var preset = demoQueue[0];
	demoQueue.shift();
	var now = new Date().getTime();
	var sinceStart = now - demoStartTime;
	var lag = sinceStart - demoDelays;

	demoNextDelay = preset[1] * 1000;
	var realDelay = Math.max(0, demoNextDelay - lag);
	loadScript(preset[0], function() {
		setTimeout(nextDemoPart, realDelay);
	});
	demoDelays += demoNextDelay;
}

function togglePause() {
	pause = !pause;
	if (pause)
		pauseTime = new Date().getTime();
	else
		pauseTimeOffset += new Date().getTime() - pauseTime;

	//update();
	if (activeSound)
		activeSound.togglePause();
}


function loadScript(url, callback) {
	var loaded = function(script) {
		/*
		if (activeSound && !options.demoMode) {
			activeSound.stop();
			activeSound = null;
		}
		*/

		if ($("presetcode"))
			$("presetcode").value = script;
		if (!options.demoMode)
			startTime = new Date().getTime();
		update(script);
		if (!isRunning) {
			nextFrame();
		}
		if (callback)
			callback();
	}

	if (options.demoMode) {
		loaded("return (" + demoFiles[url] + ")();");
	} else {
		xhr(url, 
			function(http) {
				loaded(http.responseText);
			}
		);
	}
}

var sm2setup = false;

function setupSoundManager(callback) {
	if (sm2setup) return;
	sm2setup = true;
	var script = dc('script');
	script.type = "text/javascript";
	script.src = "soundmanager/script/soundmanager2.js";
	script.onload = function() {
		soundManager.flashVersion = 9;
		soundManager.flash9Options.useEQData = true;
		soundManager.flash9Options.useWaveformData = true;
		soundManager.useHighPerformance = true;
		soundManager.allowPolling = true;
		soundManager.url = './'; // path to directory containing SoundManager2 .SWF file
		soundManager.onload = function() {
			soundManagerLoaded = true;
			if (callback)
				callback();
		};
		soundManager.debugMode = false;
		soundManager.beginDelayedInit();
		this.onload = null;
	};
	document.getElementsByTagName("head")[0].appendChild(script);
}


function analyzeSound() {
	var data = this.soundData;

	data.waveData = this.waveformData;
	if (typeof data.waveData[0] == "undefined") {
		for (var i=0;i<data.waveData.length;i++)
			data.waveData[i] = 0;
	}

	if (data.waveData.length == 512) {
		data.waveDataL = data.waveData.slice(0, 256);
		data.waveDataR = data.waveData.slice(256);
	} else {
		data.waveDataL = data.waveDataR = data.waveData;
	}


	data.eqData = this.eqData;
	if (data.eqData.length == 512) {
		data.eqDataL = data.eqData.slice(0, 256);
		data.eqDataR = data.eqData.slice(256);
	} else {
		data.eqDataL = data.eqDataR = data.eqData;
	}

	for (var i=0;i<data.numFreqBands;i++)
		data.freqBands[i] = 0;

	for (var i=0;i<128;i++)
		data.freqBands[(i/data.freqBandInterval*2)>>0] += data.eqData[i];

	data.frameCount++;

	for (var i=0;i<data.numFreqBands;i++) {
		if (data.freqBands[i] > data.avgFreqBands[i])
			data.avgFreqBands[i] = data.avgFreqBands[i]*0.2 + data.freqBands[i]*0.8;
		else
			data.avgFreqBands[i] = data.avgFreqBands[i]*0.5 + data.freqBands[i]*0.5;

		if (data.frameCount < 50)
			data.longAvgFreqBands[i] = data.longAvgFreqBands[i]*0.900 + data.freqBands[i]*0.100;
		else
			data.longAvgFreqBands[i] = data.longAvgFreqBands[i]*0.992 + data.freqBands[i]*0.008;


		if (Math.abs(data.longAvgFreqBands[i]) < 0.001)
			data.relFreqBands[i] = 1.0;
		else
			data.relFreqBands[i]  = data.freqBands[i] / data.longAvgFreqBands[i];

		if (Math.abs(data.longAvgFreqBands[i]) < 0.001)
			data.avgRelFreqBands[i] = 1.0;
		else
			data.avgRelFreqBands[i]  = data.avgFreqBands[i] / data.longAvgFreqBands[i];
	}
}

function initSound(sound) {
	sound.soundData = {
		waveData : [],
		waveDataL : [],
		waveDataR : [],
		eqData : [],
		eqDataL : [],
		eqDataR : [],
		freqBands : [],
		avgFreqBands : [],
		longAvgFreqBands : [],
		relFreqBands : [],
		avgRelFreqBands : [],
		numFreqBands : 3,
		freqBandInterval : 256 / 3,
		frameCount : 0
	}
	for (var i=0;i<512;i++) {
		sound.soundData.waveData[i] = 0;
		sound.soundData.eqData[i] = 0;
	}
	for (var i=0;i<256;i++) {
		sound.soundData.waveDataL[i] = 0;
		sound.soundData.waveDataR[i] = 0;
		sound.soundData.eqDataL[i] = 0;
		sound.soundData.eqDataR[i] = 0;
	}
	for (var i=0;i<sound.soundData.numFreqBands;i++) {
		sound.soundData.avgFreqBands[i] = 0;
		sound.soundData.relFreqBands[i] = 0;
		sound.soundData.longAvgFreqBands[i] = 0;
	}
}


function xhr(url, callback, error, method) {
	var http = null;
	if (window.XMLHttpRequest) {
		http = new XMLHttpRequest();
	} else if (window.ActiveXObject) {
		http = new ActiveXObject("Microsoft.XMLHTTP");
	}
	if (http) {
		if (callback) {
			if (typeof(http.onload) != "undefined")
				http.onload = function() {
					callback(http);
					http = null;
				};
			else {
				http.onreadystatechange = function() {
					if (http.readyState == 4) {
						callback(http);
						http = null;
					}
				};
			}
		}
		http.open(method || "GET", url + "?time=" + new Date().getTime(), true);
		http.send(null);
	} else {
		if (error) error();
	}
}

function playSound(filename) {
	if (!soundManagerLoaded) {
		setTimeout(function() {
			playSound(filename);
		}, 100);
		return;
	}
	if (!sounds[filename]) {
		if (activeSound)
			activeSound.stop();

		activeSound = sounds[filename] = soundManager.createSound(
			{
				id:filename,
				url:filename,
				autoLoad : true,
				stream : true,
				autoPlay : true,
				whileplaying : analyzeSound
			}
		);
		initSound(activeSound)
	}
	if (sounds[filename].playState != 1) {
		if (activeSound) {
			activeSound.stop();
		}
		sounds[filename].play();
		activeSound = sounds[filename];
		initSound(activeSound);
	}
}

function stopSound() {
	if (activeSound) {
		activeSound.stop();
		activeSound = null;
	}
}


function parseFunctions(code) {
	var sqrt = Math.sqrt;
	var pow = Math.pow;
	var exp = Math.exp;
	var sin = Math.sin;
	var cos = Math.cos;
	var tan = Math.tan;
	var atan = Math.atan;
	var atan2 = Math.atan2;
	var min = Math.min;
	var max = Math.max;
	var abs = Math.abs;
	var random = Math.random;
	var log = Math.log;
	var floor = Math.floor;
	var ceil = Math.ceil;
	var round = Math.round;
	var log10 = function(n) { return log(n) / log(10); };
	var sign = function(n) { return n < 0 ? -1 : (n == 0 ? 0 : 1); };
	var pi = Math.PI;
	var pi2 = Math.PI*2;


	function drawRect(x, y, width, height, fillStyle, strokeStyle, lineWidth) {
		bufferCtx.save();
		if (fillStyle) {
			bufferCtx.fillStyle = fillStyle;
			bufferCtx.fillRect(x * screenWidth, y * screenHeight, width * screenWidth, height * screenHeight);
		}
		if (strokeStyle) {
			bufferCtx.strokeStyle = strokeStyle;
			bufferCtx.lineWidth = (lineWidth || 1) * displayQuality;
			bufferCtx.strokeRect(x * screenWidth, y * screenHeight, width * screenWidth, height * screenHeight);
		}

		bufferCtx.restore();
	}

	function drawPath(points, close, fillStyle, strokeStyle, strokeWidth) {
		if (points.length < 2) return;

		bufferCtx.save();
		bufferCtx.beginPath();
		for (var i=0;i<points.length;i++) {
			if (typeof points[i][0] == "number" && typeof points[i][1] == "number") {
				if (i == 0)
					bufferCtx.moveTo(points[0][0]*screenWidth, points[0][1]*screenHeight);
				else
					bufferCtx.lineTo(points[i][0]*screenWidth, points[i][1]*screenHeight);
			}
		}
		if (close)
			bufferCtx.closePath();
		if (fillStyle) {
			bufferCtx.fillStyle = fillStyle;
			bufferCtx.fill();
		}
		if (strokeStyle) {
			if (strokeWidth)
				bufferCtx.lineWidth = strokeWidth * displayQuality;
			else
				bufferCtx.lineWidth = 1 * displayQuality;
			bufferCtx.strokeStyle = strokeStyle;
			bufferCtx.stroke();
		}
		bufferCtx.restore();
	}

	function drawCircle(x, y, radius, fillStyle, strokeStyle, strokeWidth) {
		if (radius <= 0) return;
		bufferCtx.save();
		bufferCtx.beginPath();
		bufferCtx.arc(x*screenWidth, y*screenHeight, radius*screenWidth, 0, Math.PI*2, true);
		bufferCtx.closePath();
		if (fillStyle) {
			bufferCtx.fillStyle = fillStyle;
			bufferCtx.fill();

		}

		if (strokeStyle) {
			if (strokeWidth)
				bufferCtx.lineWidth = strokeWidth * displayQuality;
			else
				bufferCtx.lineWidth = 1 * displayQuality;
			bufferCtx.strokeStyle = strokeStyle;
			bufferCtx.stroke();
		}
		bufferCtx.restore();
	}

	function drawEllipse(x, y, a, b, angle, fillStyle, strokeStyle, strokeWidth) {
		bufferCtx.save();
		bufferCtx.beginPath();
		a *= 2;
		b *= 2;
		var m = Math.max(a,b);
		bufferCtx.translate(x*screenWidth, y*screenHeight);
		bufferCtx.rotate(angle);
		bufferCtx.scale(a/m, b/m);
		bufferCtx.arc(0, 0, m*0.5*screenWidth, 0, Math.PI*2, true);
		bufferCtx.closePath();
		if (fillStyle) {
			bufferCtx.fillStyle = fillStyle;
			bufferCtx.fill();
		}
		if (strokeStyle) {
			if (strokeWidth)
				bufferCtx.lineWidth = strokeWidth * displayQuality;
			else
				bufferCtx.lineWidth = 1 * displayQuality;
			bufferCtx.strokeStyle = strokeStyle;
			bufferCtx.stroke();
		}

		bufferCtx.restore();
	}

	function drawLine(x1, y1, x2, y2, strokeColor, strokeWidth) {
		bufferCtx.save();
		bufferCtx.beginPath();
		bufferCtx.moveTo(x1*screenWidth, y1*screenHeight);
		bufferCtx.lineTo(x2*screenWidth, y2*screenHeight);
		bufferCtx.closePath();
		if (strokeWidth)
			bufferCtx.lineWidth = strokeWidth * displayQuality;
		else
			bufferCtx.lineWidth = 1 * displayQuality;
		bufferCtx.strokeStyle = strokeColor;
		bufferCtx.stroke();
		bufferCtx.restore();
	}

	function drawImage(src, x, y, width, height, angle) {
		bufferCtx.save();
		if (!images[src]) {
			images[src] = new Image();
			images[src].src = src;
		}
		if (images[src].complete) {
			bufferCtx.translate(x*screenWidth, y*screenHeight);
			if (angle)
				bufferCtx.rotate(angle);
			bufferCtx.drawImage(
				images[src],
				0, 0,
				width*screenWidth, height*screenHeight
			);
		}
		bufferCtx.restore();
	}

	var charSize = 8;

	function drawText(str, fontname, size, x, y, alignx, aligny) {
		if (!alignx) alignx = "center";
		if (!aligny) aligny = "center";

		str = (str+"").replace(/[^(\x20-\x7E)]*/,"");
		var len = str.length;

		var font = fonts[fontname];
		if (!font) return;

		var charSizeSrc = charSize;
		var charSizeDst = charSize * size * displayQuality;

		var charRatioW = charSizeDst / screenWidth;
		var charRatioH = charSizeDst / screenHeight;

		if (alignx == "left")
			var textoffsetx = 0;
		else if (alignx == "right")
			var textoffsetx = (len * charRatioW);
		else
			var textoffsetx = (len * 0.5 * charRatioW);

		if (aligny == "top")
			var textoffsety = 0;
		else if (aligny == "bottom")
			var textoffsety = charRatioH;
		else
			var textoffsety = charRatioH * 0.5;

		textCanvas.width = charSize * len;
		textCanvas.height = charSize;

		for (var i=0;i<len;i++) {
			var char = str.charCodeAt(i) - 32;
			var srcpos = char * charSizeSrc;
			textCtx.drawImage(
				font, 
				srcpos, 0, charSizeSrc, charSizeSrc,
				i*charSize, 0, charSize, charSize
			);
		}

		bufferCtx.drawImage(textCanvas, 
			(x - textoffsetx) * screenWidth, (y - textoffsety) * screenHeight, 
			charSizeDst * len, charSizeDst
		);
	}

	function scrollText(str, fontname, size, scrollTime, y) {
		str = str+"";
		var time = now;
		var charSizeDst = charSize * size * displayQuality;
		var scrollLength = (str.length * charSizeDst) / screenWidth + 1;
		var x = 1 - (time%scrollTime/scrollTime) * scrollLength + (str.length * charSizeDst * 0.5 / screenWidth);
		drawText(
			str, fontname, size,
			x, y
		);
	
	}

	function decay(amount) {
		drawRect(0,0,1,1,"rgba(0,0,0," + amount + ")");
	}

	function quality(q) {
		if (!q) return;
		if (q <= 0) return;

		if (q != quality) {
			displayQuality = q;

			resetDisplay();
		}
	}

	var validCompositeOps = ["source-over", "source-in", "source-out", "source-atop", 
		"destination-over", "destination-in", "destination-out", "destination-atop",
		"lighter", "copy", "xor"];

	function composite(op, alpha) {
		/*
		if (validCompositeOps.indexOf(op) == -1)
			return;
		ctx.globalCompositeOperation = compositeMode = op;
		*/
		compositeMode = op;

		if (typeof alpha != "undefined")
			compositeAlpha = alpha;
		else
			compositeAlpha = 1;
	}

	function stretch2(x, y) {
		deform(function() {
			return {
				stretchX : x, stretchY : y
			}
		},1,1);
	}

	function zoom(z) {
		stretch(z,z);
	}

	function stretch(x, y) {
		tmpCtx.clearRect(0,0,screenWidth,screenHeight);
		tmpCtx.drawImage(bufferCanvas,0,0);
		bufferCtx.save();
		bufferCtx.globalCompositeOperation = "copy";
		bufferCtx.clearRect(0,0,screenWidth,screenHeight);
		bufferCtx.scale(x,y);
		bufferCtx.drawImage(tmpCanvas,-(x-1)*screenWidth*0.5,-(y-1)*screenHeight*0.5);
		bufferCtx.restore();
		bufferCtx.globalCompositeOperation = "source-over";
	}

	function rotate(angle) {
		tmpCtx.clearRect(0,0,screenWidth,screenHeight);
		tmpCtx.drawImage(bufferCanvas,0,0);
		bufferCtx.save();
		bufferCtx.globalCompositeOperation = "copy";
		bufferCtx.clearRect(0,0,screenWidth,screenHeight);
		bufferCtx.translate(screenWidth/2, screenHeight/2);
		bufferCtx.rotate(angle);
		bufferCtx.drawImage(tmpCanvas,0,0);
		bufferCtx.restore();
		bufferCtx.globalCompositeOperation = "source-over";
	}

        function rotozoom(angle, zoom) {
		tmpCtx.clearRect(0,0,screenWidth,screenHeight);
		tmpCtx.drawImage(bufferCanvas,0,0);
		bufferCtx.save();
		bufferCtx.globalCompositeOperation = "copy";
		bufferCtx.clearRect(0,0,screenWidth,screenHeight);
		bufferCtx.translate(screenWidth/2, screenHeight/2);
		bufferCtx.rotate(angle);
		bufferCtx.scale(zoom,zoom);
		bufferCtx.drawImage(tmpCanvas,-screenWidth/2,-screenHeight/2);
		bufferCtx.restore();
		bufferCtx.globalCompositeOperation = "source-over";
        }

	function move(x, y) {
		deform(function() {
			return {
				moveX : x, moveY : y
			}
		},1,1);
	}

	function process(action, options, defer) {
		if (!hasImageData) 
			return;

		options = options || {};
		if (defer) {
			processList.push([action, options]);
		} else {
			Pixastic.process(bufferCtx.canvas, action, options);
			bufferCtx.globalCompositeOperation = "copy";
			bufferCtx.drawImage(options.resultCanvas, 0, 0);
			bufferCtx.globalCompositeOperation = "source-over";
		}
	}

	function rgb2hsl(r, g, b) {
		var h, s, l;
		r /= 255;
		g /= 255;
		b /= 255;
		var max = Math.max(Math.max(r, g), b);
		var min = Math.min(Math.min(r, g), b);
		if (max == min) {
			h = 0;
		} else if (r == max && g >= b) {
			h = 60 * (g - b) / (max - min);
		} else if (r == max && g < b) {
			h = 60 * (g - b) / (max - min) + 360;
		} else if (g == max) {
			h = 60 * (b - r) / (max - min) + 120;
		} else if (b == max) {
			h = 60 * (r - g) / (max - min) + 240;
		}
		l = 0.5 * (max + min);
		if (max == min) {
			s = 0;
		} else if (l <= 0.5) {
			s = (max - min) / (max + min);
		} else {
			s = (max - min) / (2-(max + min));
		}
		return { h : h, s : s, l : l };
	}

	function hsl2rgb(hue, sat, light) {
		light /= 100;
		sat /= 100;
		if (light < 0.5) {
			var q = light * (1 + sat);
		} else { 
			var q = light + sat - (light * sat);
		}
		var p = 2 * light - q;
	
		hue %= 360;
		if (hue < 0) hue += 360;
	
		var hk = hue / 360;
	
		var tr = hk + 1/3;
		var tg = hk;
		var tb = hk - 1/3;
	
		if (tr < 0) tr += 1;
		if (tr > 1) tr -= 1;
	
		if (tg < 0) tg += 1;
		if (tg > 1) tg -= 1;
	
		if (tb < 0) tb += 1;
		if (tb > 1) tb -= 1;
	

		var r, g, b;
	
		if (tr < 1/6) {
			r = p + ((q - p) * 6 * tr);
		} else if (tr < 1/2) {
			r = q;
		} else if (tr < 2/3) {
			r = p + ((q - p) * 6 * (2/3 - tr))
		} else {
			r = p;
		}


		if (tg < 1/6) {
			g = p + ((q - p) * 6 * tg);
		} else if (tg < 1/2) {
			g = q;
		} else if (tg < 2/3) {
			g = p + ((q - p) * 6 * (2/3 - tg))
		} else {
			g = p;
		}
	
		if (tb < 1/6) {
			b = p + ((q - p) * 6 * tb);
		} else if (tb < 1/2) {
			b = q;
		} else if (tb < 2/3) {
			b = p + ((q - p) * 6 * (2/3 - tb))
		} else {
			b = p;
		}
	
		return {r : (r * 255)>>0, g : (g * 255)>>0, b : (b * 255)>>0 };
	}

	function hsv2rgb(hue, sat, variance) {
		if (sat > 0) {
			sat /= 100;
			variance /= 100;

			hue %= 360;
			if (hue < 0) hue += 360;
	
			var h = (hue / 60)>>0;
	
			var hPart = (hue / 60) - h;
	
			if (!(h & 1)) hPart = 1 - hPart;
	
			var c1 = variance * (1 - sat);
			var c2 = variance * (1 - sat * hPart);
	
			var r,g,b;
	
			switch (h) {
				case 0:
					r = variance; g = c2; b = c1;
					break;
				case 1:
					r = c2; g = variance; b = c1;
					break;
				case 2:
					r = c1; g = variance; b = c2;
					break;
				case 3:
					r = c1; g = c2; b = variance;
					break;
				case 4:
					r = c2; g = c1; b = variance;
					break;
				case 5:
					r = variance; g = c1; b = c2;
					break;
			}
			return {r:r*255, g:g*255, b:b*255};
		} else {
			var c = variance / 100 * 255;
			return {r:c, g:c, b:c};
		}

	}

	function resetTime() {
		startTime = new Date().getTime();
	}

	var mouse, time, soundData;

	prepareFrame = function() {
		now = (new Date().getTime() - startTime - pauseTimeOffset) / 1000
		time = now;	

		var dmx = (mouseX - 0.5) * 2;
		var dmy = (mouseY - 0.5) * 2;

		var dmdx = (mouseDownX - 0.5) * 2;
		var dmdy = (mouseDownY - 0.5) * 2;

		mouse = {
			x : mouseX,
			y : mouseY,
			radius : Math.sqrt(dmx*dmx+dmy*dmy),
			angle : Math.atan2(dmy,dmx),
			down : mouseIsDown,
			downTime : (mouseDownTime - startTime) / 1000,
			downX : dmdx,
			downY : dmdy,
			over : mouseOver
		};
		if (activeSound) {
			soundData = {
				bass : activeSound.soundData.relFreqBands[0],
				mid : activeSound.soundData.relFreqBands[1],
				treb : activeSound.soundData.relFreqBands[2],
				waveDataL : activeSound.soundData.waveDataL,
				waveDataR : activeSound.soundData.waveDataR,
				eqDataL : activeSound.soundData.eqDataL,
				eqDataR : activeSound.soundData.eqDataR
			}
		} else {
			soundData = { 
				bass : 0, mid : 0, treb : 0,
				waveDataL : [], waveDataR : [],
				eqDataL : [], eqDataR : []
			};
			for (var i=0;i<256;i++) {
				soundData.waveDataL[i] 
				= soundData.waveDataR[i] 
				= soundData.eqDataL[i] 
				= soundData.eqDataR[i] = 0;
			}
		}
	}

	presetObject = eval(
		"(function(){"
		+ (code || $("presetcode").value)
		+ "})()"
	);

	if (!presetObject)
		presetObject = {};

	if (!presetObject.init)
		presetObject.init = function() {};
	if (!presetObject.stop)
		presetObject.stop = function() {};
	if (!presetObject.render)
		presetObject.render = function() {};

	if (!presetObject.mouseclick)
		presetObject.mouseclick = function() {};
	if (!presetObject.mousedown)
		presetObject.mousedown = function() {};
	if (!presetObject.mouseup)
		presetObject.mouseup = function() {};
	if (!presetObject.mousemove)
		presetObject.mousemove = function() {};


}

function update(code) {
	var oldPresetObject = presetObject;

	var err = "";
	try {
		parseFunctions(code);

		if (oldPresetObject)
			oldPresetObject.stop();

		reset();

		presetObject.init();
		render();

		validPreset = true;
		presetCode = code || $("presetcode").value;
	} catch(e) {
		err = e;
		presetObject = oldPresetObject;
		validPreset = false;
	}

	if ($("status")) {
		$("status").innerHTML = 
			validPreset ? 
				(pause ? "Paused..." : "Rendering...")
			:	"Tighten up that syntax, yo!<br/><br/>" + err;
		$("status").className = validPreset ? "status-ok" : "status-error";
	}
}

function reset() {
	frameNum = 0;
	pause = false;
	/*
	if (!options.demoMode) {
		if (activeSound) {
			activeSound.stop();
			activeSound = null;
		}
	}
	*/
	pre3dactive = false;
	displayQuality = 1;
	resetDisplay();
}

function resetDisplay() {
	screenWidth = displayWidth * displayQuality;
	screenHeight = displayHeight * displayQuality;
	tmpCanvas.width = screenWidth;
	tmpCanvas.height = screenHeight;

	tmpCtx.drawImage(bufferCanvas,0,0,screenWidth,screenHeight);
	bufferCanvas.width = screenWidth;
	bufferCanvas.height = screenHeight;
	bufferCtx.drawImage(tmpCanvas,0,0);

	tmpCtx.clearRect(0,0,screenWidth,screenHeight);
	tmpCtx.drawImage(canvas,0,0,screenWidth,screenHeight);
	canvas.width = screenWidth;
	canvas.height = screenHeight;
	ctx.drawImage(tmpCanvas,0,0);

	if (Pre3dactive) { // couldn't find a nice way to tell pre3d that the canvas dimensions have changed
		Pre3dRenderer.width_ = screenWidth;
		Pre3dRenderer.height_ = screenHeight;
		Pre3dRenderer.scale_ = screenHeight / 2;
		Pre3dRenderer.xoff_ = screenWidth / 2;
	}

}

function getElementPos(element) {
	var x = -(document.body.scrollLeft+document.documentElement.scrollLeft);
	var y = -(document.body.scrollTop+document.documentElement.scrollTop);
	while (element && element.nodeName != "BODY") {
		x += element.offsetLeft;
		y += element.offsetTop;
		element = element.offsetParent;
	}
	return {
		x : x,
		y : y
	}
}


var frameTimer;
var lastTime = 0;
var fps = 20;

function nextFrame() {
	isRunning = true;
	if (!pause) {
		frameNum++;
		render();
	}
	var frameTime = 1000 / fps;
	var now = new Date().getTime();
	var sinceLast = now - lastTime;
	var lag = sinceLast - frameTime;
	var untilNext = Math.max(1, frameTime - lag);
	if (isRunning)
		frameTimer = setTimeout(nextFrame, untilNext);
	lastTime = now;
}

function stop() {
	if (frameTimer) {
		clearTimeout(frameTimer);
	}
	isRunning = false;
	frameTimer = 0;

	frameNum = 0;
	pause = false;
	if (activeSound) {
		activeSound.stop();
		activeSound = null;
	}
	pre3dactive = false;
}


function clear() {
	if (arguments.length == 4) {
		bufferCtx.clearRect(arguments[0]*screenWidth, arguments[1]*screenHeight, arguments[2]*screenWidth, arguments[3]*screenHeight);
	} else {
		bufferCtx.clearRect(0,0,screenWidth,screenHeight);
	}
}

function commit(keepBuffer) {
	if ((compositeMode == "lineardodge") && hasLighter) {
		ctx.globalAlpha = compositeAlpha;
		ctx.globalCompositeOperation = "lighter";
		ctx.drawImage(bufferCanvas,0,0);
		ctx.globalAlpha = 1;
	} else {
		if (compositeMode == "normal" && compositeAlpha == 1) {
			ctx.globalCompositeOperation = "source-over";
			ctx.drawImage(bufferCanvas,0,0);
		} else {
			var resCanvas = Pixastic.process(canvas, "blend", {mode : compositeMode, image : bufferCanvas, amount : compositeAlpha, leaveDOM : true});
			ctx.clearRect(0,0,canvas.width,canvas.height);
			ctx.globalCompositeOperation = "source-over";
			ctx.drawImage(resCanvas, 0, 0);
		}
	}
	if (!keepBuffer)
		bufferCtx.clearRect(0,0,screenWidth,screenHeight);

}	

function copy() {
	bufferCtx.globalCompositeOperation = "source-over";
	bufferCtx.clearRect(0,0,screenWidth,screenHeight);
	bufferCtx.drawImage(canvas,0,0);
}


function render() {
	if (!(presetObject && presetObject.render))
		return;

	prepareFrame();

	bufferCtx.clearRect(0,0,screenWidth,screenHeight);

	ctx.globalCompositeOperation = "source-over";
	bufferCtx.globalCompositeOperation = "source-over";
	compositeMode = "normal";
	compositeAlpha = 1;

	ctx.save();

	bufferCtx.drawImage(canvas,0,0);
	bufferCtx.save();

	ctx.clearRect(0,0,screenWidth,screenHeight);

	presetObject.render();

	bufferCtx.restore();

	ctx.restore();

	var processCanvas = $("processcanvas");
	var processCtx = processCanvas.getContext("2d");
	if (processList.length > 0) {
		processCanvas.width = canvas.width;
		processCanvas.height = canvas.height;
		processCtx.globalCompositeOperation = "copy";
		processCtx.drawImage(ctx.canvas, 0, 0);
		processCtx.globalCompositeOperation = "source-over";
		for (var i=0;i<processList.length;i++) {
			var action = processList[i][0];
			var options = processList[i][1];
			options.leaveDOM = true;
			Pixastic.process(processCanvas, action, options);
			processCtx.globalCompositeOperation = "copy";
			processCtx.drawImage(options.resultCanvas, 0, 0);
			processCtx.globalCompositeOperation = "source-over";
		}
		processList = [];
		processCanvas.style.display = "block";
		canvas.style.display = "none";
	} else {
		canvas.style.display = "block";
		processCanvas.style.display = "none";
	}
}

function deform(deformFunction, gridSizeX, gridSizeY, paintGrid, gridColor, gridLineWidth) {
	tmpCtx.clearRect(0,0,screenWidth,screenHeight);

	var width = screenWidth;
	var height = screenHeight;
	var pixelMeshSizeX = Math.max(1, Math.min(32, gridSizeX || 7));
	var pixelMeshSizeY = Math.max(1, Math.min(32, gridSizeY || 7));

	var mesh = [];

	for (var x=0;x<=pixelMeshSizeX;x++) {
		mesh[x] = [];
		for (var y=0;y<=pixelMeshSizeY;y++) {
			var fx = x / pixelMeshSizeX;
			var fy = y / pixelMeshSizeY;

			var px = (fx - 0.5) * 2;
			var py = (fy - 0.5) * 2;

			var rad = Math.sqrt(px*px+py*py);
			var ang = Math.atan2(py,px);

			var pixelVars = deformFunction(rad, ang, fx, fy) || {};

			var cx = pixelVars.centerX;
			var cy = pixelVars.centerY;
			var sx = pixelVars.stretchX;
			var sy = pixelVars.stretchY;
			var dx = pixelVars.moveX;
			var dy = pixelVars.moveY;
			var zoom = pixelVars.zoom;
			var rot = pixelVars.rotate;

			if (typeof cx == "undefined" || isNaN(cx))
				cx = 0;
			if (typeof cy == "undefined" || isNaN(cy))
				cy = 0;
			if (typeof sx == "undefined" || isNaN(sx))
				sx = 1;
			if (typeof sy == "undefined" || isNaN(sy))
				sy = 1;
			if (typeof dx == "undefined" || isNaN(dx))
				dx = 0;
			if (typeof dy == "undefined" || isNaN(dy))
				dy = 0;
			if (typeof zoom == "undefined" || isNaN(zoom))
				zoom = 1;
			if (typeof rot == "undefined" || isNaN(rot))
				rot = 0;


			cx = (cx / 2 + 0.5);
			cy = (cy / 2 + 0.5);

			var u = px * 0.5 * zoom + 0.5;
			var v = py * 0.5 * zoom + 0.5;

			// stretch on X, Y:
			u = (u - cx) * sx + cx;
			v = (v - cy) * sy + cy;

			// rotation:
			var u2 = u - cx;
			var v2 = v - cy;

			var cos_rot = Math.cos(rot);
			var sin_rot = Math.sin(rot);
			u = u2*cos_rot - v2*sin_rot + cx;
			v = u2*sin_rot + v2*cos_rot + cy;

			// translation:
			u += dx;
			v += dy;

			mesh[x][y] = {
				x : u * width,
				y : v * height
			};
		}
	}

	var cellWidth = 1 / pixelMeshSizeX * width;
	var cellHeight = 1 / pixelMeshSizeY * height;


	for (var y=0;y<pixelMeshSizeY;y++) {
		var py = y / pixelMeshSizeY * height;
		for (var x=0;x<pixelMeshSizeX;x++) {
			var p00 = mesh[x][y];
			var p10 = mesh[x+1][y];
			var p01 = mesh[x][y+1];
			var p11 = mesh[x+1][y+1];

			var px = x / pixelMeshSizeX * width;

			var isIn00 = (p00.x > 0 || p00.x < 1 || p00.y > 0 || p00.y < 1);
			var isIn10 = (p10.x > 0 || p10.x < 1 || p10.y > 0 || p10.y < 1);
			var isIn01 = (p01.x > 0 || p01.x < 1 || p01.y > 0 || p01.y < 1);
			var isIn11 = (p11.x > 0 || p11.x < 1 || p11.y > 0 || p11.y < 1);

			if (isIn00 && isIn10 && isIn11) {
				renderTriangle(
					tmpCtx,  
					p00, p10, p11, 
					bufferCanvas,
					{ x : px, y : py },
					{ x : px+cellWidth, y : py },
					{ x : px+cellWidth, y : py+cellHeight }
				)
			}
			if (isIn00 && isIn01 && isIn11) {
				renderTriangle(
					tmpCtx, 
					p00, p01, p11,
					bufferCanvas,
					{ x : px, y : py },
					{ x : px, y : py+cellHeight },
					{ x : px+cellWidth, y : py+cellHeight }
				)
			}
		}
	}

	bufferCtx.clearRect(0,0,width,height);
	bufferCtx.drawImage(tmpCtx.canvas, 0, 0);

	if (paintGrid) {
		bufferCtx.strokeStyle = gridColor || "rgb(0,255,0)";
		bufferCtx.lineWidth = gridLineWidth || 2 * displayQuality;
		bufferCtx.beginPath();
		for (var x=0;x<=pixelMeshSizeX-1;x++) {
			for (var y=0;y<=pixelMeshSizeY-1;y++) {
				var p1 = mesh[x][y];
				var p2 = mesh[x+1][y];
				var p3 = mesh[x+1][y+1];
				var p4 = mesh[x][y+1];

				bufferCtx.moveTo(p1.x, p1.y);
				bufferCtx.lineTo(p2.x, p2.y);
				bufferCtx.lineTo(p3.x, p3.y);
				bufferCtx.lineTo(p4.x, p4.y);
				bufferCtx.lineTo(p1.x, p1.y);
			}
		}
		bufferCtx.stroke();
	}
}

function renderTriangle(dstCtx, d0, d1, d2, srcCanvas, s0, s1, s2) {
	var sax = s1.x - s0.x;
	var say = s1.y - s0.y;
	var sbx = s2.x - s0.x;
	var sby = s2.y - s0.y;

	var dinv = 1 / (sax * sby - say * sbx);

	var i11 = sby * dinv;
	var i22 = sax * dinv;
	var i12 = -say * dinv;
	var i21 = -sbx * dinv;

	var dax = d1.x - d0.x;
	var day = d1.y - d0.y;
	var dbx = d2.x - d0.x;
	var dby = d2.y - d0.y;

	var m11 = i11 * dax + i12 * dbx;
	var m12 = i11 * day + i12 * dby;
	var m21 = i21 * dax + i22 * dbx;
	var m22 = i21 * day + i22 * dby;

	dstCtx.save();
	dstCtx.beginPath();
	dstCtx.moveTo(d0.x, d0.y);
	dstCtx.lineTo(d1.x, d1.y);
	dstCtx.lineTo(d2.x, d2.y);
	dstCtx.clip();

	dstCtx.transform(m11, m12, m21, m22,
		d0.x - (m11 * s0.x + m21 * s0.y),
		d0.y - (m12 * s0.x + m22 * s0.y)
	);
	dstCtx.drawImage(srcCanvas, 0, 0);
	dstCtx.restore();
}



return {
	preloadMusic : function(filename) {
		var onload = function() {
			if (!sounds[filename]) {
				sounds[filename] = soundManager.createSound({
					id:filename,
					url:filename,
					autoLoad : true,
					stream : true,
					autoPlay : false,
					whileplaying : analyzeSound
				});
				initSound(sounds[filename]);
			}
		}
		setupSoundManager(onload);
	},
	init : init,
	loadScript : loadScript,
	update : function() {
		if (!isRunning) return;
		update();
	},
	pause : function() {
		if (!isRunning) return;
		togglePause();
	},
	playMusic : function(file) {
		if (pause || !isRunning) return;
		playSound(file);
	},
	stopMusic : function() {
		if (pause || !isRunning) return;
		stopSound();
	}

}

})();
