var silverlightVersion = '2.0.0.0';
var reallyHasSilverlight = "Silverlight" in window && Silverlight.isInstalled(silverlightVersion);
var hasSilverlight = reallyHasSilverlight;
var suppressErrors = false;
var viewer = null;
var zoomIn = true;
var maxZoom;
var minZoom;
var center = new Seadragon.Point(); 
var originalCenter;
var zooming = false;
var zoomedIn = false;
var imageURL = '';
var imageXML = '';
var imageKey = '';
var defaultZoomInTime = 20000; //8333;	
var defaultZoomOutTime = 15000; //5000;	
var zoomInTime = defaultZoomInTime;	
var zoomOutTime = defaultZoomOutTime;	
var zoomStart;
var zoomEnd; 
var zoomEndTime;       
var zoomStartTime;
var zoomDuration;
var zoomStartLog;
var zoomEndLog;
var zoomDiffLog;
var containerName = '';
var $silverlight = null; 
var slCtl = null;    
var $container;
var containerWidth;
var containerHeight;
var containerAspect;
var imageAspect;
var useClickLocation = true;
var hasDragCursor = false;
var $standin = null;
var usingStandin = false;
var standinSpecial = false;
var lastClickTime = 0;
var autoStart = 0;

//------------------------------------------------------------------------------
function initSeadragon(in_containerName) {
	containerName = in_containerName;
	$container = jQuery('#' + containerName);
	containerWidth = $container.width();
	containerHeight = $container.height();
	containerAspect = containerHeight / containerWidth;
	
	var sl = getURLParam('sl');
	if(sl == 'false')
		hasSilverlight = false;

	// ___ standin
	var html = '<img style="position:absolute;">';
	$standin = jQuery(html)
		.css({
			left: 0,
			top: 0,
			width: containerWidth,
			height: containerHeight, 
			position: 'absolute'
		})
		.click(function(event) {
			var p = $container.offset();
			handleClick({x: event.clientX - p.left, y: event.clientY - p.top});
		})
		.appendTo($container.parent())
		.hide();
}
			
//------------------------------------------------------------------------------
function openDZI(info, standin) {
	imageURL = info.url;
	if(!imageURL)
		imageURL = 'http://chrisjordan.com/dzi/' + info.key + '.xml';	
		
	imageXML = info.xml;	

	var xml = asXML(imageXML);			
	var node = xml.getElementsByTagName('Size').item(0);
	var width = parseInt(node.getAttribute('Width'));
	var height = parseInt(node.getAttribute('Height'));

	imageAspect = height / width;
	
	if(info.center) {
		center.x = info.center.x;
		center.y = info.center.y * imageAspect;
	} else {
		// TODO is this actually a good default?
		center.x = 0.5;
		center.y = 0.5 * imageAspect; 
	}
	
	zoomInTime = info.zoomInTime || defaultZoomInTime;
	zoomOutTime = info.zoomOutTime || defaultZoomOutTime;

    maxZoom = (width / containerWidth) * (info.zoomScale || 1); 
    
    zooming = false;
    zoomIn = (info.zoomOut ? false : true);
    
    autoStart = info.autoStart || 0;

	usingStandin = false;
	$standin.fadeOut(400, function() {
		standinSpecial = (standin.indexOf('-special') !=  -1);
		if(standin) {
			$standin.attr('src', standin).load(function() {
				usingStandin = true;
				
				var w;
				var h; 
				if(imageAspect > containerAspect) {
					h = containerHeight;
					w = h * (1 / imageAspect);
				} else {
					w = containerWidth;
					h = w * imageAspect;
				}
				
				var p = $container.position();
				p.left += (containerWidth - w) / 2;
				p.top += (containerHeight - h) / 2;
				$standin.css({
					left: p.left,
					top: p.top,
					width: w,
					height: h
				});
				
				if(!zoomedIn && !standinSpecial)
					$standin.fadeIn();
			});
		}
	});
	
	function openSeajax() {
		if(viewer) 
			viewer.openDzi(imageURL, imageXML);
		else {
	        jQuery(document).ready(function() {
	        	startSeajax();
	        });
		}
	}

	if(!hasSilverlight) {
		openSeajax();
    } else {
    	if(slCtl) {
			// We have to do this because Silverlight needs a couple of ticks to wake up after being hidden
			function loadSL() {			
				if(!slCtl.content || !slCtl.content.Page) {
					setTimeout(loadSL, 1);
				} else {
				    slCtl.content.Page.openDZI(imageURL, center.x, center.y, maxZoom, 
				    	zoomInTime, zoomOutTime, zoomIn);
				    	
				    autoStartCheck();
				}
			}
			
			loadSL();
    	} else {
	    	var properties = {
                width: containerWidth + 'px',
                height: containerHeight + 'px', 
                background: "white", 
                version: silverlightVersion, 
                windowless: 'true'
            };
            
            var events = { 
            	onError: onSilverlightError, 
            	onLoad: pluginLoaded
            };

	    	var initParams = 'zoomEnd='
				+ maxZoom 
				+ ',zoomInTime='
				+ zoomInTime
				+ ',zoomOutTime='
				+ zoomOutTime
				+ ',zoomPointX='
				+ center.x
				+ ',zoomPointY='
				+ center.y 
				+ ',path='
				+ imageURL
				+ ',useClickLocation='
				+ useClickLocation
				+ ',onZoomedOut=onZoomedOut,onZoomStarted=onZoomStarted'
				+ ',zoomIn='
				+ zoomIn;
				
	        Silverlight.createObject(
	            "/ClientBin/DeepZoomProject.xap",  // source
	            $container.get(0),  // parent element
	            "slPlugin",  // id for generated object element
				properties, 
				events,
				initParams,
	            "context"    // context helper for onLoad handler.
	        );
	        
	        var ua = navigator.userAgent;
	        if (ua.search(/firefox/i) != -1 && ua.search(/mac/i) != -1) {
		        jQuery(document).ready(function() {
			        setTimeout(function() {
			        	if (!slCtl) {
			        		hasSilverlight = false;
			        		openSeajax();
			        	}
			        }, 1000);
			    });
			}
		}
    }
}

//------------------------------------------------------------------------------
function pluginLoaded(sender, args) {
    slCtl = sender;
    autoStartCheck();
}

//------------------------------------------------------------------------------
function startSeajax() {
	Seadragon.Config.visibilityRatio = 1.0;
	Seadragon.Config.clickDistThreshold = 10;
	Seadragon.Config.clickTimeThreshold = 3000;
	
	viewer = new Seadragon.Viewer(containerName);
	viewer.clearControls();
	viewer.setMouseNavEnabled(false);
	viewer.addEventListener('open', handleOpen); 
	viewer.openDzi(imageURL, imageXML);
	
	// ___ mouse tracking
	var tracker = new Seadragon.MouseTracker(viewer.elmt);
	
	tracker.clickHandler = function(tracker, position, quick, shift) {
		if (quick) 
			handleClick(position);	
	};

	tracker.dragHandler = function(tracker, position, delta, shift) {
		if(zooming) {
	    	zooming = false;
	    	setZoomIn(!zoomIn);
	    }

		var vp = viewer.viewport;
		if(canDrag()) {
			vp.panBy(vp.deltaPointsFromPixels(delta, true).times(-1), true);
			vp.applyConstraints(true);
		}
	};
	
	tracker.setTracking(true);
}

//------------------------------------------------------------------------------
function handleOpen() {
	var vp = viewer.viewport;
	minZoom = vp.getZoom();
	originalCenter = vp.getCenter();
	
	if (!zoomIn)
		vp.zoomTo(maxZoom, center, true);
		
    autoStartCheck();
	updateDragCursor();
}

//------------------------------------------------------------------------------
function autoStartCheck() {
	if (autoStart) {
		setTimeout(function() {
			if (!zooming) {
				handleClick({
					x: containerWidth / 2, 
					y: containerHeight / 2
				});
			}
		}, autoStart);
	}
}

//------------------------------------------------------------------------------
function canDrag() {
	var vp = viewer.viewport;
	var vpb = vp.getBounds();
	return (vpb.width < 1 && vpb.height < imageAspect);
}

//------------------------------------------------------------------------------
function updateDragCursor() {
	if(canDrag() != hasDragCursor) {
		hasDragCursor = !hasDragCursor;
		$container.css({cursor: (hasDragCursor ? 'move' : 'pointer')});
	}
}

//------------------------------------------------------------------------------
function handleClick(position) {	
	if(hasSilverlight) {
    	if(slCtl && slCtl.content && slCtl.content.Page) 
		    slCtl.content.Page.handleClick(position.x, position.y);
	} else {
		var now = getMilliseconds();
		if(now - lastClickTime < 500) 
			return;
			
		lastClickTime = now;
		
	  	if(!zooming) {		 
	       	var vp = viewer.viewport;
	        var zoomTimeFactor = (Math.log(vp.getZoom()) - Math.log(minZoom)) / 
	        		(Math.log(maxZoom) - Math.log(minZoom));
	
	        zoomStart = vp.getZoom();
	        zoomEnd = maxZoom;
	
	        if (zoomIn) {
	            zoomTimeFactor = 1 - zoomTimeFactor;
	            
				if(useClickLocation && zoomStart == minZoom) {
					var x;
					var y;
					if(containerAspect > imageAspect) {
						var meat = imageAspect / containerAspect;
						var extra = 1 - meat;
						var h = containerHeight * meat;
						var h2 = containerHeight * extra;
						y = ((position.y - (h2 / 2)) / h);
						
						x = position.x / containerWidth;
					} else {
						var meat = containerAspect / imageAspect;
						var extra = 1 - meat;
						var w = containerWidth * meat;
						var w2 = containerWidth * extra;
						x = ((position.x - (w2 / 2)) / w);
						
						y = position.y / containerHeight;
					}
						
					center.x = Math.min(1, Math.max(0, x));
					center.y = Math.min(1, Math.max(0, y)) * imageAspect;
				} 
			} else {
	            zoomEnd = minZoom;
	            
	            var vpb = vp.getBounds();            
				var x;
				var y;
				if(containerAspect > imageAspect) {
					var meat = imageAspect / containerAspect;
					var extra = 1 - meat;			
					var a = 1 / meat;
		            x = vpb.x / (1 - vpb.width);
		            y = ((vpb.y + (vpb.height * extra * 0.5)) * a) / ((imageAspect * a) - vpb.height);
				} else {
					var meat = containerAspect / imageAspect;
					var extra = 1 - meat;			
					var a = 1 / meat;
		            x = ((vpb.x + (vpb.width * extra * 0.5)) * a) / (a - vpb.width);
		            y = vpb.y / (imageAspect - vpb.height);
				}
					
				center.x = x;
				center.y = y * imageAspect;
	        }
	            
	        zoomStartLog = Math.log(zoomStart);
	        zoomEndLog = Math.log(zoomEnd);
	        zoomDiffLog = zoomEndLog - zoomStartLog;
	            
	        zoomDuration = (zoomIn ? zoomInTime : zoomOutTime) * zoomTimeFactor;
			zoomStartTime = getMilliseconds();
	        zoomEndTime = zoomStartTime + zoomDuration;
	
			startTimer();
			zooming = true;
			onZoomStarted();
	    } else {
	    	zooming = false;
	    	setZoomIn(!zoomIn);
	    }
	}
}

//------------------------------------------------------------------------------
function setZoomIn(value) {
	zoomIn = value;
}

//------------------------------------------------------------------------------
function startTimer() {
	window.setTimeout(update, 5);            					
}

//------------------------------------------------------------------------------
function onZoomStarted() {
	zoomedIn = true;
	
	$standin.stop().hide().css({
		opacity: 1
	});
}

//------------------------------------------------------------------------------
function onZoomedOut() {
	zoomedIn = false;
	
	if(usingStandin)
		$standin.fadeIn(standinSpecial ? 3000 : '');
}

//------------------------------------------------------------------------------
function update() {
	if(!zooming)
		return;
		
	var vp = viewer.viewport;
	var now = getMilliseconds();
	if(now >= zoomEndTime) {
		vp.zoomTo(zoomEnd, center, true);
		setZoomIn(!zoomIn);
		zooming = false;
		
		if(zoomIn)
			onZoomedOut();
	} else {
		var completion = (now - zoomStartTime) / zoomDuration;
		completion = CubicBezierAtTime(completion, 0, 0, 0.8, 1, 1);
        var newZoom = Math.exp(zoomStartLog + (zoomDiffLog * completion)); 
        vp.zoomTo(newZoom, center, true);
	    
        startTimer();
    }
	
	updateDragCursor();
}

//------------------------------------------------------------------------------
function setSilverlight(value) {
	window.location = '?image='
		+ imageKey
		+ '&sl='
		+ value;
}

//------------------------------------------------------------------------------
function setImage(value) {
	window.location = '?image='
		+ value
		+ '&sl='
		+ hasSilverlight;
}

//------------------------------------------------------------------------------
// From: http://www.netzgesta.de/dev/cubic-bezier-timing-function.html (Public Domain)
// currently used function to determine time
// 1:1 conversion to js from webkit source files
// UnitBezier.h, WebCore_animation_AnimationBase.cpp
function CubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
	var ax=0,bx=0,cx=0,ay=0,by=0,cy=0;
	// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
    function sampleCurveX(t) {return ((ax*t+bx)*t+cx)*t;};
    function sampleCurveY(t) {return ((ay*t+by)*t+cy)*t;};
    function sampleCurveDerivativeX(t) {return (3.0*ax*t+2.0*bx)*t+cx;};
	// The epsilon value to pass given that the animation is going to run over |dur| seconds. The longer the
	// animation, the more precision is needed in the timing function result to avoid ugly discontinuities.
	function solveEpsilon(duration) {return 1.0/(200.0*duration);};
    function solve(x,epsilon) {return sampleCurveY(solveCurveX(x,epsilon));};
	// Given an x value, find a parametric value it came from.
    function solveCurveX(x,epsilon) {var t0,t1,t2,x2,d2,i;
		function fabs(n) {if(n>=0) {return n;}else {return 0-n;}}; 
        // First try a few iterations of Newton's method -- normally very fast.
        for(t2=x, i=0; i<8; i++) {x2=sampleCurveX(t2)-x; if(fabs(x2)<epsilon) {return t2;} d2=sampleCurveDerivativeX(t2); if(fabs(d2)<1e-6) {break;} t2=t2-x2/d2;}
        // Fall back to the bisection method for reliability.
        t0=0.0; t1=1.0; t2=x; if(t2<t0) {return t0;} if(t2>t1) {return t1;}
        while(t0<t1) {x2=sampleCurveX(t2); if(fabs(x2-x)<epsilon) {return t2;} if(x>x2) {t0=t2;}else {t1=t2;} t2=(t1-t0)*.5+t0;}
        return t2; // Failure.
    };
	// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
	cx=3.0*p1x; bx=3.0*(p2x-p1x)-cx; ax=1.0-cx-bx; cy=3.0*p1y; by=3.0*(p2y-p1y)-cy; ay=1.0-cy-by;
	// Convert from input time to parametric value in curve, then from that to output time.
	return solve(t, solveEpsilon(duration));
};

//------------------------------------------------------------------------------	
function onSilverlightError(sender, args) {
	if(suppressErrors)
		return;
	
	var appSource = "";
	if (sender != null && sender != 0) {
	    appSource = sender.getHost().Source;
	} 
	var errorType = args.ErrorType;
	var iErrorCode = args.ErrorCode;
	
	var errMsg = "Unhandled Error in Silverlight 2 Application " +  appSource + "\n" ;
	
	errMsg += "Code: "+ iErrorCode + "    \n";
	errMsg += "Category: " + errorType + "       \n";
	errMsg += "Message: " + args.ErrorMessage + "     \n";
	
	if (errorType == "ParserError")
	{
	    errMsg += "File: " + args.xamlFile + "     \n";
	    errMsg += "Line: " + args.lineNumber + "     \n";
	    errMsg += "Position: " + args.charPosition + "     \n";
	}
	else if (errorType == "RuntimeError")
	{           
	    if (args.lineNumber != 0)
	    {
	        errMsg += "Line: " + args.lineNumber + "     \n";
	        errMsg += "Position: " +  args.charPosition + "     \n";
	    }
	    errMsg += "MethodName: " + args.methodName + "     \n";
	}
	
	throw new Error(errMsg);
}

