(function($){
        $.crSpline = {};
        // Catmull-Rom interpolation between p0 and p1 for previous point p_1 and later point p2
        // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline
        var interpolate = function (t, p_1, p0, p1, p2) {
                return Math.floor((t * ((2 - t) * t - 1) * p_1 +
                        (t * t * (3 * t - 5) + 2) * p0 +
                        t * ((4 - 3 * t) * t + 1) * p1 +
                        (t - 1) * t * t * p2
                        ) / 2);
        };
        // Extend this p1,p2 sequence linearly to a new p3
        var generateExtension = function (p1, p2) {
                return [
                        p2[0] + (p2[0] - p1[0]),
                        p2[1] + (p2[1] - p1[1]),
                        p2[2] + (p2[2] - p1[2])
                        ];

        };
        // Return an animation object based on a sequence of points
        // pointList must be an array of [x,y] pairs
        $.crSpline.buildSequence = function(pointList) {
                var res = {};
                var seq = [];
                var numSegments;
                if (pointList.length < 2) {
                        throw "crSpline.buildSequence requires at least two points";
                }
                // Generate the first p_1 so the caller doesn't need to provide it
                seq.push(generateExtension(pointList[2], pointList[1], pointList[0]));
                // Throw provided points on the list
                for (var i = 0; i < pointList.length; i++) {
                        seq.push(pointList[i]);
                }
                // Generate the last p2 so the caller doesn't need to provide it
                seq.push(generateExtension(seq[seq.length-2], seq[seq.length-1]));
                numSegments = seq.length - 3;
                res.getPos = function (t) {
                        // XXX For now, assume all segments take equal time
                        var segNum = Math.floor(t * numSegments);
                        if (segNum === numSegments) {
                                return {
                                        left: seq[seq.length-2][0],
                                        top: seq[seq.length-2][1],
                                        width: seq[seq.length-2][2]
                                        };
                        }
                        var microT = (t - segNum/numSegments) * numSegments;
                        var result = {
                                left: interpolate(microT,
                                                seq[segNum][0],
                                                seq[segNum+1][0],
                                                seq[segNum+2][0],
                                                seq[segNum+3][0]) + "px",
                                top: interpolate(microT,
                                                seq[segNum][1],
                                                seq[segNum+1][1],
                                                seq[segNum+2][1],
                                                seq[segNum+3][1]) + "px",
                                width: interpolate(microT,
                                                seq[segNum][2],
                                                seq[segNum+1][2],
                                                seq[segNum+2][2],
                                                seq[segNum+3][2]) + "px"
                                };
                        return result;
                };
                return res;
        };

        $.fx.step.crSpline = function (fx) {
                var css = fx.end.getPos(fx.pos);
                for (var i in css) {
                        fx.elem.style[i] = css[i];						
                }
        };
})(jQuery);


var a;
var dO={
	diameter:36,focalLength:200,aInt:100,aFrame:50,
	yCtrl:'mouse',xCtrl:'mouse',zCtrl:{u:117},zoomCtrl:'wheel',
	xLim:[0,0],yLim:[0,0],zLim:[0,0],zoomLim:[0,0],
	showStatus:true, autoInit:true
};
function startCamera(o){return new Camera(o)};
function Object3D(b){
	this.jq_object=$(b);
	this.initialWidth=this.jq_object.width();
	this.initialHeight=this.jq_object.height();
	this.initialFontSize=parseFloat(this.jq_object.css("font-size"));
	this.pos=new Vector3D(this.jq_object.position().left,this.jq_object.position().top,parseInt(this.jq_object.css("z-index"),10))
}
function initObjects3D(){
	for(var b=$(".object3d"),d=[],c=0;c<b.length;c++){var e=new Object3D(b[c]);/*e.jq_object.text("z="+e.pos.z)*/;d.push(e)}
	b=createThickness(d);
	for(c=0;c<b.length;c++)d.push(b[c]);
	return d
}
function initViewport(b){
	var d=$("#viewport");
	/*d.bind("mousewheel",function(c,e){
		c=e*25;if(b.focalLength+c>=0)b.focalLength+=c;b.update()
	});*/
	d.resize(function(){	
		var k = d.width/b.initWidth;		
	});	
	d.bind("mousemove",function(c){
		if(!b.lock){
			var xLmin = b.o.xLim[0]+(d.width()-1000)/2;
			var xLmax = b.o.xLim[1]+(d.width()-1000)/2;
			var modF = (b.jq_viewport.width()-xLmin-(xLmax>xLmin?b.jq_viewport.width()-xLmax:0))/b.jq_viewport.width();
			var newX = xLmin + (c.clientX-b.jq_viewport.offset().left)*modF;
			//var newZ = (c.clientY-b.jq_viewport.offset().top)*70/400;
			var diff = Math.abs(b.pos.x-newX);
			//b.pos.x=newX;
			if(diff<b.o.aFrame){
				b.pos.x=newX;
			}else{				
				b.animateCamera({x:newX,y:b.pos.y,z:b.pos.z},Math.round(diff*b.o.aInt/b.o.aFrame),true);
			}
			//b.pos.z = 1200+newZ;
			if(b.o.yCtrl=='mouse'){b.pos.y=c.clientY;}
			b.update();
		}
	});
	$(document).keypress(function(c){
		switch(c.charCode){
			case 119:b.move(new Vector3D(0,0,-10));b.update();break;
			case 115:b.move(new Vector3D(0,0,10));b.update();break;
			
			case 114:b.move(new Vector3D(0,-10,0));b.update();break;
			case 102:b.move(new Vector3D(0,10,0));b.update();break;
			
			case 97:b.move(new Vector3D(-10,0,0));b.update();break;
			case 100:b.move(new Vector3D(10,0,0));b.update();break;
		}
	});
	createStatusPanel(d[0],b);
	return d
}
function createStatusPanel(b,c){var d=document.createElement("div");d.setAttribute("id","status");d.setAttribute("style","position: absolute; z-index: 99999"+(!c.o.showStatus?';display:none':''));b.appendChild(d)}function updateStatus(b){$("#status").html(b)}function Vector3D(b,d,c){this.x=b;this.y=d;this.z=c}
function createThickness(b){for(var d=[],c=0;c<b.length;c++){var e=b[c];if(e.jq_object.hasClass("thickness"))for(var f=5;f>0;f--){var g=e.jq_object.attr("id")+"_thickness"+f,h=e.pos.z-f;g=$(document.createElement("div")).attr("id",g).addClass("object3d thickness").css("z-index",h).css("width",e.initialWidth).css("height",e.initialHeight).css("top",e.pos.y).css("left",e.pos.x).insertAfter(e.jq_object);d.push(new Object3D($(g)))}}return d}
function Camera(o){
	this.o=$.extend(true,dO,o);	
	this.jq_viewport=initViewport(this);
	this.jq_viewport.css('visibility','hidden');	
	this.initWidth=this.jq_viewport.width();
	this.targets=initObjects3D();
	this.diameter=this.o.diameter;this.focalLength=this.o.focalLength;
	this.lock = true;
	this.preset='stage';
	this.animating=false;
	this.animationInterval = false;
	if(this.o.autoInit) this.initCamera();
}
a=Camera.prototype;a.distance=function(){return parseInt(this.pos.z,10)};a.fov=function(){return 2*Math.atan2(this.diameter/2,this.focalLength)};
a.displacement=function(){return new Vector3D(this.pos.x-this.jq_viewport.width()/2,this.pos.y-this.jq_viewport.height()/2,0)};
a.project=function(b){
	var d=this.distance()-b.z==0 ? 5239 : this.focalLength/(this.distance()-b.z),c=this.displacement();
	return new Vector3D(d*(b.x-c.x),d*(b.y-c.y),this.pos.z)
};
a.move=function(b){this.pos.x+=b.x;this.pos.y+=b.y;this.pos.z+=b.z};
a.getEl = function(el){for(var b=0;b<this.targets.length;b++){if(this.targets[b].jq_object[0]==el[0])return this.targets[b]}}
a.initCamera=function(pos){	
	this.pos=new Vector3D(
		this.o.xCtrl=='mouse'?this.jq_viewport.position().left+this.jq_viewport.width()/2:this.o.xCtrl,
		this.o.yCtrl=='mouse'?this.jq_viewport.position().top:this.o.yCtrl,
		parseInt(this.jq_viewport.css("z-index"),10));
	this.update();
	this.spos = this.pos;
	if(pos)this.setPos(pos);
	this.jq_viewport.css('visibility','visible');
	this.releaseLock();	
}
a.animateEl = function(el,target,dur,lock,callback){
	var c = this;
	var tu = false;	
	var e = c.getEl(el);
	lock=lock && !c.lock;
	if(lock)c.setLock();
	//setInterval(function(){c.update();},30);
	$('<div/>').css({'position':'fixed','left':e.pos.x,'top':e.pos.y,'width':e.pos.z})
			.animate(
				{'left':target.x,'top':target.y,'width':target.z},
				{
					duration:dur,
					step: function(v,o){						
						e.pos = new Vector3D(o.prop=='left'?v:e.pos.x, o.prop=='top'?v:e.pos.y, o.prop=='width'?v:e.pos.z);
						if(o.prop=='width') e.jq_object.css('z-index',v);
						if(!c.updating){tu=true;c.updating=true;}
						if(tu)c.update();
					},
					complete: function(){
						if(lock)c.releaseLock();
						if(tu)c.updating=false;
						if(callback) callback();
					}
				}
			);
}
a.animateCamera = function(target,dur,lock,callback,path){
	var c = this;
	var tu = false;	
	var wPath = path && path[0];
	var cDur = path && path[1] ? path[1] : dur;
	lock = lock && !c.lock;
	if(lock) c.setLock();	
	if(wPath){		
		var cPath = path[0].slice(0);
		cPath.push(target);
		var splinePath = [[c.pos.x, c.pos.z, c.pos.y]];
		var k3d = (c.jq_viewport.width()-1268)/2;
		for(var i=0;i<cPath.length;i++){splinePath.push([i==cPath.length-1 ? cPath[i].x : cPath[i].x+k3d, cPath[i].z, cPath[i].y]);}		
	}
	$('<div/>').css({'position':'fixed','left':c.pos.x,'top':c.pos.y,'width':c.pos.z})
			.animate(
				wPath ? {crSpline: $.crSpline.buildSequence(splinePath)} : {'left':target.x,'top':target.y,'width':target.z},
				{
					duration: cDur,
					step: function(v,o){
						c.pos = wPath ? new Vector3D($(this).css('left').replace(/px/,'')*1, $(this).css('width').replace(/px/,'')*1, $(this).css('top').replace(/px/,'')*1) : new Vector3D(o.prop=='left'?v:c.pos.x, o.prop=='top'?v:c.pos.y, o.prop=='width'?v:c.pos.z);
						if(!c.updating){tu=true;c.updating=true;}						
						if(tu && !c.animationInterval) c.animationInterval = setInterval(function(){c.update()},45);
					},
					complete: function(){						
						if(lock) c.releaseLock();
						if(tu){
							c.updating=false;
							clearInterval(c.animationInterval);
							c.animationInterval = false;
						}
						c.pos = target;
						c.update();
						if(callback) callback();
					}
				}
			);
}
a.update=function(){
	for(var b=0;b<this.targets.length;b++){
		var d=this.targets[b],c=d.jq_object;
		if(this.pos.z<d.pos.z)c.css("display","none");else{
			c.css("display","block");
			var e=this.project(d.pos),f=this.project(new Vector3D(d.pos.x+d.initialWidth,d.pos.y+d.initialHeight,d.pos.z));
			c.css("left",this.jq_viewport.width()/2+e.x);
			c.css("top",this.jq_viewport.height()/2+e.y);
			if(!c.hasClass("fixedsize")){
				c.width(f.x-e.x);
				c.height(f.y-e.y);
				d=d.initialFontSize*c.width()/d.initialWidth;
				c.css("font-size",d)
			}
		}
	}
	if(this.o.showStatus) updateStatus(this.toHTML());
};
a.toHTML=function(){return"Z: "+this.pos.z+"<br>Y: "+this.pos.y+"<br>X: "+this.pos.x+"<br>Focal length: "+this.focalLength+"mm<br>FOV: "+Math.round(180/Math.PI*this.fov())+"deg"};
a.setLock=function(){this.lock = true;}
a.releaseLock=function(){this.lock = false;}
a.setPos=function(pos){var c=this;c.pos=new Vector3D(pos.x?pos.x:c.pos.x, pos.y?pos.y:c.pos.y, pos.z?pos.z:c.pos.z);c.update();}
