var BoxObj = Class.create({
	p1:null, p2:null, w:null, h:null,
	
	initialize: function(p1, p2) {
		this.p1 = p1;
		this.p2 = p2;
		this.calculateLengths();
	},
	
	setP1: function(p1) {
		this.p1 = p1;
		this.calculateLengths();
	},
	
	setP2: function(p2) {
		this.p2 = p2;
		this.calculateLengths();
	},
	
	calculateLengths: function() {
		this.w = this.p2.x - this.p1.x;
		this.h = this.p2.y - this.p1.y;
	}
});






var Point = Class.create({
	x:null, y:null,
	
	initialize: function(x, y) {
		this.x = x;
		this.y = y;
	}
});























var Dims = Class.create({
	x:null, width:null,
	y:null, height:null,
	initialize: function(x, y) {
		this.x = x;
		this.width = x; 
		this.y = y; 
		this.height = y;
	},
	
	setDims: function(x, y) {
		this.x = x;
		this.width = x; 
		this.y = y; 
		this.height = y;
	},
	
	getLongestSide: function() {
			return (this.width > this.height ? this.width : this.height);
	},
	
	equals: function(dimOrX, y) {
		if(typeof(dimOrX) == "number" && typeof(y) == "number")
			return (this.x == dimOrX && this.y == y);
		else if(typeof(dimOrX) == "object")
			return (this.x == dimOrX.x && this.y == dimOrX.y);
	},
	
	toString: function() {
		return "[" + this.x + ", " + this.y + "]";
	}
});






























var ImageMorpher = Class.create({
	ani_time:100, ani_intervals: 15, ani_ms: '', ani_fadeInc: '', ani_status: 'idle',
	targetObj: '',
	loadingImg: null, tempImg: null,
	layer_1: '', layer_2: '', layer_top: '', layer_bottom: '',
	adjustContainerHeight:false,
	onResize: null, onDone: null, onDone_internal:null,
	borderObj: null, borderWidth:0, borderColor:"white", borderDensity:0.7,
	
	initialize: function(targetId) {
		// setup container
		this.targetObj = $(targetId);
		this.targetObj.setStyle({
			width:"0px",
			height:"0px"
		});
		
		// target container
		this.targetObj.setStyle({
			position:"relative"
		});
		
		this.setAniSettings();
		
		// image 1
		this.layer_1 = new Element("img", { id:"layer_1", src:"http://www.anderweb.net/common/images/blank.gif" });
		this.layer_1.layerNum = 1;
		this.layer_1.setStyle({
			position:"absolute",
			zIndex:8,
			top:"0px",
			left:"0px"
		});
		this.targetObj.appendChild(this.layer_1);
		
		
		// image 2
		this.layer_2 = new Element("img", { id:"layer_2", src:"http://www.anderweb.net/common/images/blank.gif" });
		this.layer_2.layerNum = 2;
		this.layer_2.setStyle({
			position:"absolute",
			zIndex:7,
			top:"0px",
			left:"0px"
		});
		this.targetObj.appendChild(this.layer_2);
		
		this.layer_top = this.layer_1;
		this.layer_bottom = this.layer_2;
	},
	
	
	setAniSettings: function(ani_time, ani_intervals) {
		if(ani_time) this.ani_time = ani_time;
		if(ani_intervals) this.ani_intervals;
		
		this.ani_ms = Math.round(this.ani_time / this.ani_intervals);
		this.ani_fadeInc = 1 / this.ani_intervals;
	},
	
	
	activateContainerHeight: function(morphContainerId) {
		if(morphContainerId)
		{
			this.adjustContainerHeight = true;
			this.morphContainerObj = $(morphContainerId);
		}
		else
			this.adjustContainerHeight = false;
	},
	
	updateImage: function(imgSrc) {
		if(this.ani_status == "idle") {
			this.ani_status = "morphing";
			
			// remove border if option enabled
			if(this.borderWidth > 0 && this.borderObj)
				this.borderObj.parentNode.removeChild(this.borderObj);
			
			// flip image layers and assign new image file to it
			this.swapLayers();
			this.layer_top.src = imgSrc;
			
			// animated loading img
			this.loadingImg = new Element("img", { src:"http://www.anderweb.net/common/images/loading_big.gif" });
			this.loadingImg.setStyle({
				position: "absolute",
				zIndex: parseInt(this.layer_top.getStyle("zIndex"))-1
			});
			this.targetObj.appendChild(this.loadingImg);
			
			// load new image into tempImg element
			this.tempImg = new Element("img");
			this.tempImg.observe("load", this.updateImage_calculateSettings.bindAsEventListener(this));
			this.tempImg.src = imgSrc;
			
			return true
		}
		else
			return false;
	},
	
	
	/* updateImage_calculateSettings
	 *   - called when our temp image loads...we need to figure out the dimensions of the image before we can resize it
	*/
	updateImage_calculateSettings: function(ev) {
		var newDims = {
			x: this.tempImg.width,
			y: this.tempImg.height
		};
		
		var sizeInc = { 
			x: (this.tempImg.width - this.targetObj.getWidth()) / this.ani_intervals,
			y: (this.tempImg.height - this.targetObj.getHeight()) / this.ani_intervals
		};
		sizeInc.x = (sizeInc.x > 0) ? Math.ceil(sizeInc.x) : Math.floor(sizeInc.x);
		sizeInc.y = (sizeInc.y > 0) ? Math.ceil(sizeInc.y) : Math.floor(sizeInc.y);
		
		if(this.adjustContainerHeight)
		{
			if(newDims.y > this.morphContainerObj.getHeight())
				this.morphContainerObj.setStyle("height:" + newDims.y + "px");
			else if(newDims.y < this.morphContainerObj.getHeight())
			{
				var _self = this;
				this.onDone_internal = function() { _self.morphContainerObj.setStyle("height:" + _self.targetObj.getHeight() + "px"); };
			}
		}
		
		this.updateImage_do(newDims, sizeInc, 0); 
	},
	
	
	updateImage_do: function(newDims, sizeInc, opacity) {
		var xDiff = Math.abs(this.targetObj.getWidth() - newDims.x);
		var yDiff = Math.abs(this.targetObj.getHeight() - newDims.y);
		
		var inc_x_abs = Math.abs(sizeInc.x);
		var inc_y_abs = Math.abs(sizeInc.y);
		
		// still need to resize
		if(xDiff != 0 || yDiff != 0 || opacity != 1) {
			// resize the container div
			if(xDiff != 0 && sizeInc.x != 0)
			{
				if(xDiff >= inc_x_abs)
					this.targetObj.setStyle("width:" + (this.targetObj.getWidth() + sizeInc.x) + "px");
				else if(xDiff < inc_x_abs)
					this.targetObj.setStyle("width:" + newDims.x + "px");
			}
			if(yDiff != 0 && sizeInc.y != 0)
			{
				// image not big enough yet
				if(yDiff >= inc_y_abs)
					this.targetObj.setStyle("height:" + (this.targetObj.getHeight() + sizeInc.y) + "px");
				// nearly there...image is smaller than our increment amount
				else if(yDiff < inc_y_abs)
					this.targetObj.setStyle("height:" + newDims.y + "px");
			}
			
			if(opacity != 1)
			{
				if((1 - opacity) >= this.ani_fadeInc)
					opacity += this.ani_fadeInc;
				else
					opacity = 1
			}
				
			// resize the actual images
			this.fitImages();
			
			// make top layer a bit more opaque
			this.layer_top.setOpacity(opacity);
			
			// call resize handler if specified
			if(this.onResize)
				this.onResize();
			
			// delay and do again...
			var _self = this;
			setTimeout(function() { _self.updateImage_do(newDims, sizeInc, opacity) }, _self.ani_ms);
		}
		
		
		
		
		// finished resizing
		else {
			this.targetObj.setStyle("width:" + newDims.x + "px");
			this.targetObj.setStyle("height:" + newDims.y + "px");
			this.fitImages();
			this.layer_top.setOpacity(1);
			this.ani_status = "idle";
			
			// remove loading animation
			this.loadingImg.parentNode.removeChild(this.loadingImg);
			
			if(this.borderWidth > 0)
				this.applyBorder();
			
			// call resize handler if specified
			if(this.onDone)
				this.onDone();
			if(this.onDone_internal)
				this.onDone_internal();
		}
	},
	
	
	/* swapLayers 
	 *   - switches the z-index order of the images.  It also sets the opacity of the top layer
	 *     to 0 (transparent) in preparation of "fading in" the image later
	*/
	swapLayers: function() {
		this.layer_top = eval("this.layer_" + ((this.layer_top.layerNum%2)+1));
		this.layer_top.setStyle({zIndex: (parseInt(this.layer_top.getStyle("zIndex")) + 3)});
		this.layer_top.setOpacity(0.0);
		
		this.layer_bottom = eval("this.layer_" + ((this.layer_bottom.layerNum%2)+1));
	},
	
	
	fitImages: function() {
		this.layer_1.width = this.targetObj.getWidth();
		this.layer_1.height = this.targetObj.getHeight();
		this.layer_2.width = this.targetObj.getWidth();
		this.layer_2.height = this.targetObj.getHeight();
	},
	
	
	applyBorder: function(width, color, density) {
		if(width) this.borderWidth = width;
		if(color) this.borderColor = color;
		if(density) this.borderDensity = density;
		
		if(this.borderWidth > 0 && this.targetObj.getWidth() > 0)
		{
			borderObj = new Element("div");
			borderObj.setOpacity(this.borderDensity);
			borderObj.setStyle({
				position: "absolute",
				zIndex: morpher.getTopZIndex()+1,
				top: "0px",
				left: "0px",
				width: ($("img_container").getWidth() - (this.borderWidth * 2)) + "px",
				height: ($("img_container").getHeight() - (this.borderWidth * 2)) + "px",
				border: this.borderWidth + "px solid " + this.borderColor
			});
			$("img_container").appendChild(borderObj);
			this.borderObj = borderObj;
		}
	},
	
	
	getTopZIndex: function() {
		return parseInt(this.layer_top.getStyle("zIndex"));
	}
});



































var Filmstrip = Class.create({
	stripObj:null, containerObj:null, thumbTable:null,
	photoFolder:"",
	state_doDrag: false, state_mouse:"up",
	click_start:new Dims(), filmstripOffset_start:0, filmstripOffset_current:0,
	navButtonWidth:25,
	thumbSize:null,
	thumb_clickHandler:null,
	
	initialize: function(containerId, photoFolder, onClick) {
		this.containerObj = $(containerId);
		this.photoFolder = photoFolder;
		this.thumbSize = this.containerObj.clientHeight;
		this.thumb_clickHandler = onClick;
		
		// build filmstrip container
		this.containerObj.setStyle("position:relative");
		this.stripObj = new Element("div", { style:"position:absolute; top:0px; left:" + this.navButtonWidth + "px; width:" + (this.containerObj.clientWidth - (this.navButtonWidth*2)) + "px; margin:auto; height:" + this.thumbSize + "px; clip:rect(0px " + (this.containerObj.clientWidth - this.navButtonWidth) + "px " + this.thumbSize + "px " + "0px); background-color:black; cursor:pointer;" });
			// build filmstrip table
			this.thumbTable = new Element("table", { id:(containerId + "_thumbTable"), cellpadding:0, cellspacing:0, style:"position:absolute; top:0px; left:0px"});
			this.stripObj.appendChild(this.thumbTable);
		this.containerObj.appendChild(this.stripObj);
		
		// navigation buttons
		var button_previous = new Element("input", { type:"button", value:"<", title:"Slide To Previous Images In Gallery", style:"position:absolute; top:0px; left:0px; width:25px; height:" + this.thumbSize + "px" });
		this.containerObj.appendChild(button_previous);
		button_previous.observe("click", this.navigation_previous_onClick.bindAsEventListener(this));
		var button_next = new Element("input", { type:"button", value:">", title:"Slide To More Images In Gallery", style:"position:absolute; top:0px; right:0px; width:25px; height:" + this.thumbSize + "px" });
		button_next.observe("click", this.navigation_next_onClick.bindAsEventListener(this));
		this.containerObj.appendChild(button_next);
		
		// assign event handlers
		document.observe("mousedown", this.global_onMouseDown.bindAsEventListener(this));
		document.observe("mouseup", this.global_onMouseUp.bindAsEventListener(this));
		this.stripObj.observe("mousedown", this.filmstrip_onMouseDown.bindAsEventListener(this));
		// globally observe this event so user can move slider even when they're not directly over it
		document.observe("mousemove", this.filmstrip_onMouseMove.bindAsEventListener(this));		
		this.stripObj.observe("mouseup", this.filmstrip_onMouseUp.bindAsEventListener(this));
	},
	
	
	loadImages: function(json_photos)
	{
		// remove any previous rows (images)
		while(this.thumbTable.rows.length >= 1)
			this.thumbTable.deleteRow(this.thumbTable.rows.length-1); 
		
		this.filmstripOffset_current = 0;
		this.thumbTable.setStyle("left:" + (this.filmstripOffset_current) + "px");
		
		// start inserting pictures
		var thumbRow = this.thumbTable.insertRow(-1);
		for(var i=0; i<json_photos.length; i++)
		{
			var photo = new Element("img", { 
					src:this.photoFolder + json_photos[i].photoFile, 
					
					style:("position:absolute; top:0px; left:0px; clip:rect(0px " + this.thumbSize + "px " + this.thumbSize + "px 0px)")
				}
			);
			photo.addClassName("filmstrip_thumb_img");
			photo.info = json_photos[i];
			
			// disable any draggin events
			photo.observe("dragstart", function() { return false; });
			photo.observe("drag", function() { return false; });
			photo.ondrag = function() { return false; };
			
			// insert into table
			var cell = thumbRow.insertCell(-1);
				var divTemp = new Element("div",  { style:"position:relative; width:" + this.thumbSize + "px; height:" + this.thumbSize + "px;" });
				cell.appendChild(divTemp);
			divTemp.appendChild(photo);
		}
	},
	
	
	
	global_onMouseDown: function() {
		this.state_mouse = "down";
	},
	
	
	global_onMouseUp: function() {
		this.state_mouse = "up";
	},
	
	
	filmstrip_onMouseDown: function(ev) {
		if(ev == null) ev = window.event;
		this.state_doDrag = true;
		this.click_start.setDims(ev.clientX, ev.clientY);
		this.filmstripOffset_start = this.filmstripOffset_current;
		ev.preventDefault();
		return false;
	},
	
	
	filmstrip_onMouseMove: function(ev) {
		if(ev == null) ev = window.event;
		// must be in a drag state and current state_mouse must be down (mouse coule be "up" if user moved mouse 
		// out of filmstrip and let go of button)
		if(this.state_doDrag && (this.state_mouse == "down" || this.state_mouse == "move"))
		{
			this.state_mouse = "move";
			this.filmstrip_move(ev.clientX - this.click_start.x);
			var currentOffset = new Dims(ev.clientX, ev.clientY);
		}
		return false;
	},
	
	
	filmstrip_onMouseUp: function(ev) {
		if(ev == null) ev = window.event;
		this.state_doDrag = false;
		if(this.state_mouse != "move")
			if(ev.element().hasClassName("filmstrip_thumb_img"))
				this.thumb_clickHandler(ev.element().info);
		return false;
	},
	
	
	filmstrip_checkBounds: function(newOffset) {
		// don't go beyond beginning
		if(newOffset > 0)
			newOffset = 0;
		// don't go beyond end
		if(newOffset < (-1 * (this.thumbTable.getWidth() - this.stripObj.getWidth())))
			newOffset = (-1 * (this.thumbTable.getWidth() - this.stripObj.getWidth()));
		return newOffset;
	},
	
	
	filmstrip_move: function(distanceFromClick) {
		this.filmstripOffset_current = this.filmstrip_checkBounds(this.filmstripOffset_start + distanceFromClick);
		this.thumbTable.setStyle("left:" + (this.filmstripOffset_current) + "px");
		
		return false;
	},
	
	
	
	navigation_next_onClick: function(ev) {
		if(ev == null) ev = window.event;
		ev.element().blur();
		this.navigation_next(50);
	},
	
	navigation_previous_onClick: function(ev) {
		if(ev == null) ev = window.event;
		ev.element().blur();
		this.navigation_previous(50);
	},
	
	
	navigation_next: function(distance_step) {
		if(distance_step > 0)
		{
			this.filmstripOffset_current = this.filmstrip_checkBounds(this.filmstripOffset_current - distance_step);
			this.thumbTable.setStyle("left:" + (this.filmstripOffset_current) + "px");
			
			var _self = this;
			setTimeout(function() { _self.navigation_next(distance_step-2); }, 20);
		}
	},
	
	navigation_previous: function(distance_step) {
		if(distance_step > 0)
		{
			this.filmstripOffset_current = this.filmstrip_checkBounds(this.filmstripOffset_current + distance_step);
			this.thumbTable.setStyle("left:" + (this.filmstripOffset_current) + "px");
			
			var _self = this;
			setTimeout(function() { _self.navigation_previous(distance_step-2); }, 20);
		}
	}
});






































var SlideObj = Class.create({
	imgObj:null, slideDiv:null, overlapPercent:10, overlap:null,
	infoBarDiv:null, infoBar_href:null, infoBar_width:200,
	size_full:null, size_current:null, size_thumb:null, expanded:false,
	border_width:3, border_color:"white",
	slideContainer:null,
	onDone:null,
	currentPos:null, originalPos:null,
	
	initialize: function(onDone, imgSrc, infoBar_href, imgId, imgTitle) {
		this.onDone = onDone;
		imgTitle = (typeof(imgTitle) == "undefined") ? "" : imgTitle;
		this.currentPos = new Dims(0, 0);
		this.originalPos = new Dims(0, 0);
		this.randomizer = (new Date()).valueOf();
		
		// create span
		this.slideDiv = new Element("div", { "class":"slide", style:"position:absolute; width:1px; height:1px; top:" + this.currentPos.y + "px; left:" + this.currentPos.x + "px;" });
		this.slideDiv.slideObj = this;
		this.slideDiv.aTest = "hello there slideDiv!";
		
		// create image
		this.imgObj = new Element("img", { "class":"slide_img", style:"border:" + this.border_width + "px solid " + this.border_color + ";" });
		this.imgObj.slideObj = this;
		
		this.imgObj.observe("load", this.img_onLoad.bindAsEventListener(this, this.imgObj));
		this.imgObj.observe("dragstart", function() { return false; });
		this.imgObj.observe("drag", function() { return false; });
		this.imgObj.ondrag = function() { return false; };
		
		this.imgObj.src = imgSrc + "?dId=" + this.randomizer++;
		
		if(infoBar_href)
		{
			this.infoBar_href = infoBar_href;
			this.infoBarDiv = new Element("div", { }).hide();
			this.infoBarDiv.setStyle({
				position:"absolute",
				top:"0px",
				width:this.infoBar_width + "px",
				backgroundColor:"white"
			});
			this.infoBarDiv.setOpacity(0.8);
			this.infoBarDiv.slideObj = this;
			this.slideDiv.appendChild(this.infoBarDiv);
		}
	},
	
	
	img_onLoad: function(ev, imgObj) {
		this.size_full = new Dims(imgObj.width, imgObj.height);
		this.size_current = this.size_full;
		
		this.slideDiv.appendChild(this.imgObj);
		this.updateSlideDivDims();
		
		this.onDone(this);
	},
	
	
	setDims: function(maxLength) {
		var newDims;
		
		// calculate thumb size
		if(this.size_full.width > this.size_full.height)
			newDims = new Dims(maxLength, Math.round(this.size_full.height * (maxLength / this.size_full.width)));
		else
			newDims = new Dims(Math.round(this.size_full.width * (maxLength / this.size_full.height)), maxLength);
		
		this.imgObj.setStyle({
			width:newDims.width + "px",
			height:newDims.height + "px"
		});
		// calculate new overlap amounts
		this.overlap = new Dims(Math.round(newDims.width * (this.overlapPercent / 100)), Math.round(newDims.height * (this.overlapPercent / 100)));
		
		// calculate new left offset for infoBar if needed
		if(this.infoBarDiv)
			this.infoBarDiv.setStyle({ left:this.imgObj.getWidth() + "px" });
		
		// our new current size
		this.size_current = newDims;
		
		// calculate slideDiv width/height
		this.updateSlideDivDims();
		
		return newDims;
	},
	
	
	setThumbSize: function(maxLength) {
		this.size_thumb = this.setDims(maxLength);
	},
	
	
	moveSlide: function(x, y) {
		this.currentPos.setDims(x, y);
		this.slideDiv.setStyle({ 
			top:y + "px",
			left:x + "px"
		});
	},
	
	moveSlide_relative: function(x, y) {
		this.currentPos.setDims(this.currentPos.x + x, this.currentPos.y + y);
		this.slideDiv.setStyle({ 
			top:this.currentPos.y + "px",
			left:this.currentPos.x + "px"
		});
	},
	
	setOriginalPos: function(dims) {
		if(dims)
			this.originalPos.setDims(dims.x, dims.y);
		else
			this.originalPos.setDims(this.slideDiv.positionedOffset().left, this.slideDiv.positionedOffset().top);
	},
	
	updateSlideDivDims: function() {
		this.slideDiv.setStyle({
			width:this.getImgWidth_offset() + (this.infoBar && this.infoBarDiv.visible() ? this.infoBar_width : 0) + "px",
			height:this.getImgHeight_offset() + "px"
		});
		
	},
	
	
	ani_numSteps:10, ani_delay:20/1000,
	ani_incSize:5, ani_state:"idle",
	
	toggleSlide: function() {
		if(this.ani_state == "idle")
		{
			if(!this.expanded)
				this.expandSlide();
			else
			{
				this.contractSlide();
				this.restorePosition();
			}
		}
	},
	
	
	expandSlide: function(containBox, onDone) {
		this.ani_state = "expanding";
		this.setOriginalPos();
		this.ani_incSize = Math.ceil((this.size_full.getLongestSide() - this.size_thumb.getLongestSide()) / this.ani_numSteps);
		new PeriodicalExecuter(this.expandSlide_do.bind(this), this.ani_delay);
	},
	
	
	expandSlide_do: function(pe) {
		// keep expanding
		if(this.size_current.getLongestSide() + this.ani_incSize < this.size_full.getLongestSide())
		{
			this.setDims(this.size_current.getLongestSide() + this.ani_incSize);
			this.containSlide();
		}
		// resize to target size and stop timer
		else
		{
			this.setDims(this.size_full.getLongestSide());
			this.containSlide();
			this.ani_state = "idle";
			this.expanded = true;
			if(this.infoBarDiv)
				this.toggleInfoBar(true);
			pe.stop();
		}
	},
	
	
	contractSlide: function() {
		this.ani_state = "contracting";
		if(this.infoBarDiv)
			this.toggleInfoBar(false);
		
		this.ani_incSize = Math.floor((this.size_full.getLongestSide() - this.size_thumb.getLongestSide()) / this.ani_numSteps);
		new PeriodicalExecuter(this.contractSlide_do.bind(this), this.ani_delay);
	},
	
	
	contractSlide_do: function(pe) {
		// keep shrinking
		if(this.size_current.getLongestSide() - this.ani_incSize > this.size_thumb.getLongestSide())
			this.setDims(this.size_current.getLongestSide() - this.ani_incSize);
		// resize to target size and stop timer
		else
		{
			this.setDims(this.size_thumb.getLongestSide());
			this.ani_state = "idle";
			this.expanded = false;
			pe.stop();
		}
	},
	
	ani_move_steps:10, ani_move_incSize:null,
	restorePosition: function() {	
		this.ani_move_incSize = new Dims(
			(this.originalPos.x - this.currentPos.x) / this.ani_move_steps, 
			(this.originalPos.y - this.currentPos.y) / this.ani_move_steps
		);
		// round in the correct direction, depending on if the number is negative or positive
		this.ani_move_incSize.setDims(
			this.ani_move_incSize.width < 0 ? Math.floor(this.ani_move_incSize.width) : Math.ceil(this.ani_move_incSize.width), 
			this.ani_move_incSize.height < 0 ? Math.floor(this.ani_move_incSize.height) : Math.ceil(this.ani_move_incSize.height)
		);
		new PeriodicalExecuter(this.restorePosition_do.bind(this), this.ani_delay);
	},
	
	
	restorePosition_do: function(pe) {
		if(Math.abs(this.originalPos.width - this.currentPos.width) > Math.abs(this.ani_move_incSize.width) || Math.abs(this.originalPos.height - this.currentPos.height) > Math.abs(this.ani_move_incSize.height))
		{
			this.moveSlide_relative(this.ani_move_incSize.width, this.ani_move_incSize.height);
		}
		else
		{
			this.moveSlide(this.originalPos.x, this.originalPos.y);
			pe.stop();
		}
	},
	
	
	containSlide: function() {
		var move_x = (document.viewport.getWidth() + document.viewport.getScrollOffsets().left) - (this.slideDiv.cumulativeOffset().left + this.slideDiv.getWidth());
		var move_y = (document.viewport.getHeight() + document.viewport.getScrollOffsets().top) - (this.slideDiv.cumulativeOffset().top + this.slideDiv.getHeight());
		
		this.moveSlide_relative(move_x < 0 ? move_x : 0, move_y < 0 ? move_y : 0);
	},
	
	
	getImgWidth: function() {
		return this.size_current.width;
	},
	
	
	getImgWidth_offset: function() {
		return this.size_current.width + (this.border_width * 2);
	},
	
	
	getImgHeight: function() {
		return this.size_current.height;
	},
	
	
	getImgHeight_offset: function() {
		return this.size_current.height + (this.border_width * 2);
	},
	
	updateInfoBar: function(transport) {
		this.infoBarDiv.update(transport.responseText);
		this.updateSlideDivDims();
		this.containSlide();
	},
	
	toggleInfoBar: function(display) {
		display ? this.infoBarDiv.show() : this.infoBarDiv.hide();
		this.updateSlideDivDims();
		
		// load content if it's now visible
		if(this.infoBarDiv.visible())
		{
			this.infoBarDiv.setStyle({
				left:this.getImgWidth_offset() + "px"
			});
			this.infoBarDiv.update("<span style='font-size:24px; font-weight:bold; font-style:italic;'><img src='http://www.anderweb.net/common/images/loading_small.gif'> Loading Slide Info...</span>");
			this.containSlide();
			new Ajax.Request(this.infoBar_href,   
				{
					method:'get',
					onSuccess: this.updateInfoBar.bind(this),
					onFailure: function() { alert("Error loading slide data.  Request aborted."); }
				}
			); 
		}
		this.updateSlideDivDims();
	}
});





































var Lightbox = Class.create({
	// settings and options
	thumb_maxSize:200, thumb_overlap:10,
	ani_resize_speed:15, ani_resize_increment:15, 
	ani_move_speed:15, ani_move_increment:25,
	op_infoBar_show:true, op_infoBar_width:200, op_containSlides:true, //op_maximizeLightbox:true,
	
	// internal settings
	lightboxDiv:null, loaderDiv:null,
	slideCarousel:null,
	thumb_currentSize:null,
	usedAreas:null, zIndex_current:100,
	state_load:"idle", numLoadedImages:0, showingLoader:false,
	click_start:null, click_current:null,
	
	initialize: function(lightboxDivId) {
		this.click_start = new Dims(0, 0);
		this.click_current = new Dims(0, 0);
		this.slideCarousel = new Array();
		this.usedAreas = new Array();
		
		// setup lightbox
		this.lightboxDiv = $(lightboxDivId);
		if(!this.lightboxDiv) {
			alert("Invalid Lightbox ID! Aborting Initialization.");
			return;
		}
		this.lightboxDiv.setStyle({ position:"relative" });
		document.observe("dom:loaded", this.initLightbox.bindAsEventListener(this));
		document.observe("mousedown", this.lightbox_onMouseDown.bindAsEventListener(this));
		document.observe("mousemove", this.lightbox_onMouseMove.bindAsEventListener(this));
		document.observe("mouseup", this.lightbox_onMouseUp.bindAsEventListener(this));
		
		
		// create loader box
		this.loaderDiv = new Element("div", { style:"position:absolute; top:0px; left:0px; width:" + this.lightboxDiv.clientWidth + "px; height:" + this.lightboxDiv.clientHeight + "px; background-color:white; font-size:24px; font-style:italic;"}).setOpacity(0.9);
			this.loaderDiv.appendChild(new Element("div", { "class":"loading_text", style:"text-align:center; padding-top:" + Math.round((this.lightboxDiv.getHeight() / 2 - 12)) + "px;" }).update("<img src='http://www.anderweb.net/common/images/loading_small.gif'> Loading Slides..."));
		this.lightboxDiv.appendChild(this.loaderDiv.hide());
	},
	
	
	initLightbox: function() {
		// setup lightbox and apply styles
		
	},
	
	
	addSlide: function(imgSrc, infoBar_href, imgId, title) {
		var theSlide = new SlideObj(this.slide_imgLoaded.bind(this), imgSrc, infoBar_href, imgId, title);
		theSlide.lightboxObj = this;
		this.slideCarousel[this.slideCarousel.length] = theSlide;
	},
	
	
	slide_imgLoaded: function() {
		this.numLoadedImages++;
		this.loaderDiv.select("div.loading_text")[0].update("<img src='http://www.anderweb.net/common/images/loading_small.gif'> Loading Slides (" + this.numLoadedImages + " of " + this.slideCarousel.length + ")");
		if(this.state_load == "awaiting_imgLoads" && this.numLoadedImages == this.slideCarousel.length)
		{
			this.shuffleSlides();
		}
	},
	
	
	shuffleSlides: function() {
		if(this.state_load == "idle" || this.state_load == "awaiting_imgLoads")
		{
			// clear lightbox and add "Loading Slides" overlay
			this.toggleLoader(true);
			
			// don't do the actual shuffle until all the images have loaded
			if(this.numLoadedImages == this.slideCarousel.length)
			{
				// don't allow any loading until we're done shuffling
				this.state_load = "loading";
				
				if(this.slideCarousel.length > 0)
				{
					this.loaderDiv.setStyle({ zIndex:this.zIndex_current++ + this.slideCarousel.length + 1 });
					
					this.clearLightbox();
					
					// calculate thumbnail size
					this.calculateThumbSize();
					
					// build img's and "throw" onto the lightbox
					for(var i=0; i<this.slideCarousel.length; i++)
					{
						var new_x = 0, new_y = 0;
						var lightbox_width = this.lightboxDiv.clientWidth, lightbox_height = this.lightboxDiv.clientHeight;
						var slide = this.slideCarousel[i];
						
						// set size to calculated thumb size
						slide.setThumbSize(this.thumb_currentSize);
						//slide.setDims(this.thumb_currentSize);
						slide.slideDiv.setStyle({zIndex:this.zIndex_current++});
						
						// find a good unoccupied spot for our new image
						var overload_counter = 0, overload_max = 200;	// prevent the program from looping endlessly because of used up real estate
						do
						{
							new_x = Math.round(Math.random() * (lightbox_width - slide.getImgWidth_offset()));
							new_y = Math.round(Math.random() * (lightbox_height - slide.getImgHeight_offset()));
							var coreArea = new BoxObj(new Dims(new_x + slide.overlap.x, new_y + slide.overlap.y), new Point(new_x + slide.getImgWidth() - slide.overlap.x, new_y + slide.getImgHeight() - slide.overlap.y));
						}
						while(this.positionOccupied(coreArea) && ++overload_counter < overload_max);
						this.usedAreas[this.usedAreas.length] = coreArea;
						
						// load image
						this.lightboxDiv.appendChild(slide.slideDiv);
						
						// move slide to new position
						slide.moveSlide(new_x, new_y);
						
						// record this original position
						slide.posOriginal = new Point(new_x, new_y);
						
						this.zIndex_current++;
					}
					
					this.toggleLoader(false);
				}
				this.state_load = "idle";
			}
			else if(this.numLoadedImages < this.slideCarousel.length)
				this.state_load = "awaiting_imgLoads";
		}
	},
	
	
	positionOccupied: function(box1) {
		isOutside = true;
		for(var i=0; (i<this.usedAreas.length) && isOutside; i++)
		{
			var box2 = this.usedAreas[i];
			isOutside = (box1.p1.x + box1.w < box2.p1.x) || (box1.p1.x > box2.p1.x + box2.w) || (box1.p1.y + box1.h < box2.p1.y) || (box1.p1.y > box2.p1.y + box2.h);
		}
		isOccupied = !isOutside;
		return isOccupied;
	},
	
	
	toggleLoader: function(display) {
		if(typeof(display) == "undefined" || display == null)
			display = !this.loaderDiv.visible();
		
		if(display)
			this.loaderDiv.show();
		else  {
			this.loaderDiv.select("div.loading_text")[0].update();
			this.loaderDiv.hide();
		}
			
	},
	
	
	calculateThumbSize: function() {
		var totalArea = (this.lightboxDiv.clientWidth * this.lightboxDiv.clientHeight) * .5;
		var slideArea = totalArea / this.slideCarousel.length;
		this.thumb_currentSize = Math.round(Math.sqrt(slideArea));
		// don't make thumb bigger than the lightbox itself
		if(this.thumb_currentSize >= this.lightboxDiv.clientHeight)
			this.thumb_currentSize = this.lightboxDiv.clientHeight - 1;
		if(this.thumb_currentSize >= this.lightboxDiv.clientWidth)
			this.thumb_currentSize = this.lightboxDiv.clientWidth - 1;
		// constrain thumb to thumb_maxSize
		if(this.thumb_currentSize > this.thumb_maxSize)
			this.thumb_currentSize = this.thumb_maxSize;
		
	},
	
	
	resetLightbox: function() {
		this.deleteSlides();
		this.toggleLoader(false);
		this.state_load = "idle";
	},
	
	
	deleteSlides: function() {
		this.clearLightbox();
		this.slideCarousel = [];
		this.numLoadedImages = 0;
	},
	
	
	clearLightbox: function() {
		var slides = this.lightboxDiv.select("div.slide");
		
		for(var i=0; i<slides.length; i++)
		{
			slides[i].slideObj.imgObj.stopObserving("load");
			slides[i].parentNode.removeChild(slides[i]);
		}
		
		this.usedAreas = [];
	},
	
	
	
	state_mouse:"idle", clickedObj:null, mouseMoved:false,
	
	lightbox_onMouseDown: function(ev) {
		this.clickedObj = ev.element();
		
		if(this.clickedObj && this.clickedObj.slideObj && this.clickedObj.slideObj.lightboxObj == this)
		{
			this.clickedObj.up().setStyle({ zIndex:this.zIndex_current++ });
			
			// clicked on image
			if(this.clickedObj.hasClassName("slide_img")) {
				this.state_mouse = "drag";
				this.mouseMoved = false;
				this.click_start.setDims(ev.clientX, ev.clientY);
				this.click_current.setDims(ev.clientX, ev.clientY);
			}
			
			ev.preventDefault();
			return false;
		}
	},
	
	
	lightbox_onMouseMove: function(ev) {
		if(this.clickedObj && this.clickedObj.slideObj && this.clickedObj.slideObj.lightboxObj == this) {
			this.mouseMoved = true;
			this.clickedObj.slideObj.moveSlide_relative(ev.clientX - this.click_current.x, ev.clientY - this.click_current.y);
			this.clickedObj.slideObj.setOriginalPos();
			this.click_current.setDims(ev.clientX, ev.clientY);
		}
		
		ev.preventDefault();
		return false;
	},
	
	
	lightbox_onMouseUp: function(ev) {
		if(this.clickedObj && this.clickedObj.slideObj && this.clickedObj.slideObj.lightboxObj == this)
		{
			if(this.clickedObj.hasClassName("slide_img")) {
				if(!this.mouseMoved) {
					this.clickedObj = ev.element();
					this.clickedObj.slideObj.toggleSlide();
					//this.clickedObj.slideObj.expandSlide();
				}
				
				this.state_mouse = "idle";
			}
			
			
			this.clickedObj = null;
		}
		
		ev.preventDefault();
		return false;
	}
});
