/*
Script: SmugLuv v0.0.1
	Easy pannable gallery view for SmugMug galleries.

License:
	BSD license.

jsSmug Copyright:
	copyright (c) 2007 Jesse Foster | jf26028, <jesse.foster@gmail.com>, <http://gravitycube.net/>

jsSmug Credits:
	* Heavily modified version of IIPImage Javascript Viewer v1 <http://iipimage.sourceforge.net> (c) 2007 Ruven Pillay <ruven@users.sourceforge.net>, License <http://creativecommons.org/licenses/LGPL/2.1/>
	* Uses mootools v1.1 <http://www.mootools.net/> (c) 2007 Valerio Proietti, <http://mad4milk.net>, License <http://www.opensource.org/licenses/mit-license.php>
	* Developed with consultation from CodePoint Labs, <http://www.codepointlabs.com/>
*/

/* Create our own class inherited from Drag.Move for constrained dragging of
	the main target window
 */
var TargetDrag = Drag.Move.extend(
{
	initialize: function(smugLuv, el, options)
	{
		this.smugLuv = smugLuv;
		this.setOptions(options);
		this.element = $(el);
		this.handle = $(this.options.handle) || this.element;
		this.mouse = {'now': {}, 'pos': {}};
		this.value = {'start': {}, 'now': {}};
		this.bound = 
		{
			'start': this.start.bindWithEvent(this),
			'check': this.check.bindWithEvent(this),
			'drag': this.drag.bindWithEvent(this),
			'stop': this.stop.bind(this)
		};
		this.attach();
		if (this.options.initialize)
		{
			this.options.initialize.call(this);
		}
	},

	drag: function(event)
	{
		this.out = false;
		this.mouse.now = event.page;
		
		for (var z in this.options.modifiers)
		{
			if (!this.options.modifiers[z])
			{
				continue;
			}
			
			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];

			if(z == 'x')
			{
				if(this.smugLuv.regionX - this.value.now[z] < 0)
				{
					this.value.now[z] = this.smugLuv.regionX;
					this.out = true;
				}

				if(this.smugLuv.currentImageWidth > this.smugLuv.regionWidth)
				{
					if(this.smugLuv.regionX - this.value.now[z] > this.smugLuv.currentImageWidth - this.smugLuv.regionWidth)
					{
						this.value.now[z] = -(this.smugLuv.currentImageWidth - this.smugLuv.regionWidth - this.smugLuv.regionX);
						this.out = true;
					}
				}
				else
				{
					this.value.now[z] = 0;
					this.out = true;
				}
			}
			if(z == 'y')
			{
				if(this.smugLuv.regionY - this.value.now[z] < 0)
				{
					this.value.now[z] = this.smugLuv.regionY;
					this.out = true;
				}
				
				if(this.smugLuv.currentImageHeight > this.smugLuv.regionHeight)
				{
					if(this.smugLuv.regionY - this.value.now[z] > this.smugLuv.currentImageHeight - this.smugLuv.regionHeight)
					{
						this.value.now[z] = -(this.smugLuv.currentImageHeight - this.smugLuv.regionHeight - this.smugLuv.regionY);
						this.out = true;
					}
				}
				else
				{
					this.value.now[z] = 0;
					this.out = true;
				} 
			}

			this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
		}
		this.fireEvent('onDrag', this.element);
		event.stop();
	}
});

/* SmugLuv Javascript Class
 */
var SmugLuv = new Class(
{
	/* Initialize some variables. The constructor takes 4 arguments:
	i) The id of the main div element in which to create the viewer window
	ii) The smugLuvsrv serverUrl full URL
	iii) The full image path on the serverUrl
	iv) An optional image copyright or information
	*/
	initialize: function(rootDivId, apiKey, tileHeight, tileWidth, albumId)
	{
		this.rootDivId = rootDivId;
		this.images = null;
		this.baseImageWidth = 0;
		this.baseImageHeight = 0;
		this.currentImageWidth = 0;
		this.currentImageHeight = 0;
		this.regionX = 0;
		this.regionY = 0;
		this.regionWidth = this.currentImageWidth;
		this.regionHeight = this.currentImageWidth;
		this.xfit = 0;
		this.yfit = 0;
		this.navpos = [0,0];
		this.tileHeight = tileHeight;
		this.tileWidth = tileWidth;
		this.resolution;
		this.refreshThread = null;
		
		this.smugMugApi = new jsSmug(this.rootDivId, apiKey, this, null, this.imagesCallback);
		this.smugMugApi.GetImages(albumId);
		
		window[rootDivId] = this;
	},
	
	imagesCallback: function(smugMugApi, imagesUrls)
	{
		//
		// Convert the returned images into something that the smugLuv can use.
		//		
		var imageArray = new Array();
		
		for (var counter = 0; counter < imagesUrls.length; counter++)
		{
			imageArray.push( [imagesUrls[counter].ThumbURL, imagesUrls[counter].AlbumURL] );
		}
		
		this.images = imageArray;
		this.load();
	}, 

	/* Create the appropriate CGI strings and change the image sources
	*/
	requestImages: function()
	{
		// Clear our tile refreshThread
		if(this.refreshThread)
		{
			$clear(this.refreshThread);
			this.refreshThread = null;
		}

		// Set our cursor and activate our loading animation
		$(this.rootDivId + 'target').style.cursor = 'wait';
		$(this.rootDivId + 'loading').setOpacity(1);
		$(this.rootDivId + 'loading').style.zIndex = '2';

		// Load our image mosaic
		this.loadGrid();

		// Create a tile refreshThread to check for unloaded tiles
		this.refreshThread = this.refresh.periodical(500, this);
	},

	/* Create a grid of tiles with the appropriate JTL request and positioning
	*/
	loadGrid: function()
	{
		// Delete our old image mosaic
		$(this.rootDivId + 'target').getChildren().each(function(el){ el.remove(); });
		$(this.rootDivId + 'target').setStyles(
		{
			left: '0px',
			top: '0px'
		});

		// Get the start points for our tiles
		var startx = Math.floor(this.regionX / this.tileWidth);
		var starty = Math.floor(this.regionY / this.tileHeight);

		// If our size is smaller than the display window, only get these tiles!
		var len = this.regionWidth;
		if(this.currentImageWidth < this.regionWidth)
		{
			len = this.currentImageWidth;
		}
		var endx =	Math.floor((len + this.regionX) / this.tileWidth);

		len = this.regionHeight;
		if(this.currentImageHeight < this.regionHeight)
		{
			len = this.currentImageHeight;
		}
		var endy = Math.floor((len + this.regionY) / this.tileHeight);

		// Number of tiles is dependent on view width and height
		var xtiles = Math.ceil(this.currentImageWidth / this.tileWidth);
		var ytiles = Math.ceil(this.currentImageHeight / this.tileHeight);

		/* Calculate the offset from the tile top left that we want to display.
			Also Center the image if our viewable image is smaller than the window
		*/
		var xoffset = Math.floor(this.regionX % this.tileWidth);
		if(this.currentImageWidth < this.regionWidth)
		{
			xoffset -=	(this.regionWidth - this.currentImageWidth) / 2;
		}

		var yoffset = Math.floor(this.regionY % this.tileHeight);
		if(this.currentImageHeight < this.regionHeight)
		{
			yoffset -= (this.regionHeight - this.currentImageHeight) / 2;
		}

		var tile;
		var i, j, k;
		var left, top;
		k = 0;

		// Create our image tile mosaic
		for(j=starty; j<=endy; j++)
		{
			for(i=startx; i<=endx; i++)
			{
				k = i + (j * xtiles);
				
				// var src = this.serverUrl + "?" + this.serverImagePath + "&jtl=" + this.resolution + "," + k;
				var imageSrc;
				var albumUrl;
				
				if (k > -1 && k < this.images.length)
				{
					imageSrc = this.images[k][0];
					albumUrl = this.images[k][1];
				}
				else
				{
					imageSrc = "http://www.gravitycube.net/SmugLuv/images/pixel.gif";
					albumUrl = "#";
				}

				tile = new Element('img', 
				{
					'src': imageSrc,
					'styles': 
					{
						'left': (i-startx)*this.tileWidth - xoffset,
						'top': (j-starty)*this.tileHeight - yoffset,
						'height': this.tileHeight, 
						'width': this.tileWidth, 
						'margin': '0',
						'padding': '0',
						'border': '0',
						'position': 'absolute', 
						'albumUrl': albumUrl
					}
				});

				tile.addEvent('dblclick', this.openAlbum.bindAsEventListener(tile));
 				tile.injectInside(this.rootDivId + 'target');				
			}
		}
	},
	
	openAlbum: function()
	{
		if (this.style.albumUrl != "#")
		{
			window.open(this.style.albumUrl);
		}
	}, 

	/* Refresh function to avoid the problem of tiles not loading
		properly in Firefox/Mozilla
	*/
	refresh: function()
	{
		var unloaded = 0;

		$(this.rootDivId + 'target').getChildren().each(function(el)
		{
			// If our tile has not yet been loaded, give it a prod ;-)
			if(el.width == 0 || el.height == 0)
			{
				el.src = el.src;
				unloaded = 1;
			}
		});

		/* If no tiles are missing, destroy our refreshThread timer, fade out our loading
			animation and and reset our cursor
		*/
		if(unloaded == 0)
		{
			$clear(this.refreshThread);
			this.refreshThread = null;

			// Fade out our loading animation
			var f = $(this.rootDivId + 'loading').effect('opacity', 
			{
				duration: 750,
				frames: 10,
				transition: Fx.Transitions.quadOut,
				monkey: true, 
				onComplete: function()
				{
					$(this.rootDivId + 'loading').style.zIndex = '-1';
				}.bind(this)
			});
			f.start(1,0);

			$(this.rootDivId + 'target').style.cursor = 'move';
		}
	},

	/* Allow us to navigate within the image via the keyboard arrow buttons
	*/
	key: function(e)
	{
		var d = 100;
		switch(e.keyCode){
		case 37: // left
			this.scrollTo(-d,0);
			break;
		case 38: // up
			this.scrollTo(0,-d);
			break;
		case 39: // right
			this.scrollTo(d,0);
			break;
		case 40: // down
			this.scrollTo(0,d);
			break;
		}
	},

	/* Scroll from a target drag event
	*/
	scroll: function()
	{
		var xmove =	- $(this.rootDivId + 'target').offsetLeft;
		var ymove =	- $(this.rootDivId + 'target').offsetTop;
		this.scrollTo(xmove, ymove);
	},

	/* Scroll to a particular position
	*/
	scrollTo: function(x, y)
	{
		if(x || y)
		{
			// To avoid unnecessary redrawing ...
			if((Math.abs(x) < 3) && (Math.abs(y) < 3))
			{
				return;
			}

			this.regionX += x;
			this.regionY += y;

			if(this.regionX > this.currentImageWidth - this.regionWidth) this.regionX = this.currentImageWidth - this.regionWidth;
			if(this.regionY > this.currentImageHeight - this.regionHeight) this.regionY = this.currentImageHeight - this.regionHeight;
			if(this.regionX < 0) this.regionX = 0;
			if(this.regionY < 0) this.regionY = 0;

			this.requestImages();
		}
	},

	/* Zoom in by a factor of 2
	*/
	zoomIn: function ()
	{
		if((this.currentImageWidth <= (this.baseImageWidth / 2)) && (this.currentImageHeight <= (this.baseImageHeight / 2)))
		{
			this.currentImageWidth = this.currentImageWidth * 2;
			this.currentImageHeight = this.currentImageHeight * 2;

			if(this.xfit == 1)
			{
				this.regionX = this.currentImageWidth / 2 - (this.regionWidth / 2);
			}
			else if(this.currentImageWidth > this.regionWidth)
			{
				this.regionX = 2*this.regionX + this.regionWidth / 2;
			}

			if(this.regionX > this.currentImageWidth) this.regionX = this.currentImageWidth - this.regionWidth;
			if(this.regionX < 0) this.regionX = 0;

			if(this.yfit == 1)
			{
				this.regionY = this.currentImageHeight / 2 - (this.regionHeight / 2);
			}
			else if(this.currentImageHeight > this.regionHeight)
			{
				this.regionY = this.regionY*2 + this.regionHeight / 2;
			}

			if(this.regionY > this.currentImageHeight) this.regionY = this.currentImageHeight - this.regionHeight;
			if(this.regionY < 0) this.regionY = 0;

			this.resolution++;
			this.requestImages();
		}
	},

	/* Zoom out by a factor of 2
	*/
	zoomOut: function()
	{
		if((this.currentImageWidth > this.regionWidth) || (this.currentImageHeight > this.regionHeight))
		{
			this.currentImageWidth = this.currentImageWidth / 2;
			this.currentImageHeight = this.currentImageHeight / 2;

			this.regionX = this.regionX / 2 - (this.regionWidth / 4);
			if(this.regionX + this.regionWidth > this.currentImageWidth)
			{
				this.regionX = this.currentImageWidth - this.regionWidth;
			}

			if(this.regionX < 0)
			{
				this.xfit = 1;
				this.regionX = 0;
			}
			else
			{
				this.xfit = 0;
			}

			this.regionY = this.regionY / 2 - (this.regionHeight / 4);
			if(this.regionY + this.regionHeight > this.currentImageHeight) this.regionY = this.currentImageHeight - this.regionHeight;
			if(this.regionY < 0)
			{
				this.yfit=1;
				this.regionY = 0;
			}
			else
			{
				this.yfit = 0;
			}

			this.resolution--;
			this.requestImages();
		}
	},

	/* Calculate some dimensions
	*/
	calculateMinSizes: function()
	{
		var tx = this.baseImageWidth;
		var ty = this.baseImageHeight;
		var thumb = 100;

		//var winWidth = Window.getWidth();
		//var winHeight = Window.getHeight();

		var winWidth = $(this.rootDivId).getSize().size.x; //  Window.getWidth();
		var winHeight = $(this.rootDivId).getSize().size.y; // Window.getHeight();

		if(winWidth > winHeight)
		{
			// For panoramic images, use a large navigation window
			if(tx > 2 * ty)
			{
				thumb = winWidth / 2;
			}
			else
			{
				thumb = winWidth / 4;
			}
		}
		else
		{
			thumb = winHeight / 4;
		}

		var r = this.resolution;
		while(tx > thumb)
		{
			tx = parseInt(tx / 2);
			ty = parseInt(ty / 2);
			// Make sure we don't set our navigation image too small!
			if(--r == 1)
			{
				break;
			}
		}

		// Determine the resolution for this image view
		tx = this.baseImageWidth;
		ty = this.baseImageHeight;
		//while(tx > winWidth && ty > winHeight)
		//{
		//	tx = parseInt(tx / 2);
		//	ty = parseInt(ty / 2);
		//	this.resolution--;
		//}

		this.currentImageWidth = tx;
		this.currentImageHeight = ty;
		this.resolution--;
	},

	/* Create our main window
	*/
	createWindows: function()
	{
		//
		// set some defaults
		//
		$(this.rootDivId).setStyles("overflow: hidden; background-color: black; position: relative; top: 0; left: 0; height: " + $(this.rootDivId).getStyle("height") + "; width: " + $(this.rootDivId).getStyle("width") + ";");
		
		//var winWidth = Window.getWidth() - 5;
		//var winHeight = Window.getHeight() - 5;
		var winWidth = $(this.rootDivId).getSize().size.x; //  Window.getWidth();
		var winHeight = $(this.rootDivId).getSize().size.y; // Window.getHeight();

		// Create our loading animated icon
		var loading = new Element('img',
		{
			'styles':
			{
				'left': (winWidth / 2) - 16 + 'px',
				'top':	(winHeight / 2) - 16 + 'px',
				'height': '7px', 
				'width': '95px',
				'position': 'absolute'
			},
			'id': this.rootDivId + 'loading',
			'src': 'images/loading.gif' 
		});
		
		loading.injectInside(this.rootDivId);

		// Calculate some sizes and create the navigation window
		this.calculateMinSizes();
		
		// Create our main window target div, add our events and inject inside the frame
		var el = new Element('div', 
		{
			'id': this.rootDivId + 'target', 
			'styles':
			{
				'left': '0',
				'top':	'0', 
				'width': 'auto', 
				'height': 'auto', 
				'position': 'absolute', 
				'z-index': '0', 
				'cursor': 'wait'			
			}
		});

		new TargetDrag(this, el, {onComplete: this.scroll.bindAsEventListener(this)}); // jes 
		el.injectInside(this.rootDivId);
		// el.addEvent('dblclick', this.zoom.bindAsEventListener(this));		

		//$(this.rootDivId).style.width = winWidth + "px";
		//$(this.rootDivId).style.height = winHeight + "px";
		
		this.regionWidth = winWidth;
		this.regionHeight = winHeight;

		this.reCenter();
		this.zoomIn();
		this.requestImages();
		
		// window.addEvent('resize', function(){ window.location=window.location; });
		document.addEvent('keydown', this.key.bindAsEventListener(this));
	},

	/* Use a AJAX request to get the image size, tile size and number of resolutions from the serverUrl
	*/
	load: function()
	{
		//
		// Pad image array with extra blank images.
		//
		var columns = Math.ceil(Math.sqrt(this.images.length));
		var rows = columns;
		
		if ((rows * rows) > (this.images.length + rows))
		{
			rows--;
		}
		
		while(this.images.length < (rows * columns))
		{
			this.images.push(["images/pixel.gif", ""]);
		}
					
		this.baseImageHeight = this.tileHeight * rows;
		this.baseImageWidth = this.tileWidth * columns;

		this.resolution = 5; // jes - what?
		this.createWindows();
	},

	/* Recenter the image view
	*/
	reCenter: function()
	{
		this.regionX = (this.currentImageWidth - this.regionWidth) / 2;
		this.regionY = (this.currentImageHeight - this.regionHeight) / 2;
	}
});
