/**
 *
 * Zoomimage
 * Author: Stefan Petre www.eyecon.ro
 * 
 */
(function($){
	EYE.extend({
		
		zoomimage: {
			libs: {},
			types: /\.jpg|\.jpeg|\.png|\.gif|\.bmp/g,
			current: null,
			moved: false,
			pointer: {x:0,y:0},
			diff: {x:0, y:0},
			trackKey: false,
			
			//default options (many options are controled via CSS)
			defaults: {
				opacity: 0.3, //caption opacity
				border: 0, // border arround the image
				shadow: 6, // shadow size
				duration: 300, // animation duration
				prevent: 14, // pixels to move the mouse before the image is dragged
				controls: true, // display controls
				caption: true, // display caption
				hideSource: false,
				centered: false,
				className: false,
				onLoad: function(){return false},
				beforeZoomIn: function(){return false},
				onZoomIn: function(){return false},
				beforeZoomOut: function(){return false},
				onZoomOut: function(){return false},
				onFocus: function(){return false},
				controlsTrigger: 'focus',
				easing: 'linear',
				preload: 'click'
			},
			
			// the template for the image's box
			template: [
				'
',
					'
',
					'
![]()
',
					'
',
					'
',
					'
',
				'
 '
			],
			
			// handle click on the trigger
			click: function(e) {
				var el = this;
				el.blur();
				// if the image was not preloaded yet then wait
				if (el.zoomimageCfg.loading === true) {
					return false;
				}
				//zoom it in if not zoomed already
				if (el.zoomimageCfg.zoomed == false) {
					EYE.zoomimage.zoomIn(el);
				//else zoom it out
				} else {
					EYE.zoomimage.zoomOut(el, false);
				}
				return false;
			},
			
			// zoom in the image
			zoomIn: function(el) {
				//if the image was not loaded yet then wait
				if (el.zoomimageCfg.loaded === false) {
					//if the image is not preloading then start preloading
					if (el.zoomimageCfg.loading != true) {
						el.zoomimageCfg.loading = true;
						EYE.zoomimage.preload(el);
					}
					return;
				}
				//if the image is zoomed in then just focus it
				if (el.zoomimageCfg.zoomed == true) {
					EYE.zoomimage.focus(el);
					return;
				}
				el.zoomimageCfg.beforeZoomIn.apply(el,[el.zoomimageCfg.box]);
				
				var elPos = EYE.getPosition(el, true);
				var elHeight = el.offsetHeight;
				var elWidth = el.offsetWidth;
				var pos = EYE.getScroll();
				var borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow;
				var width = el.zoomimageCfg.width + borderAndShadow * 2; 
				var height = el.zoomimageCfg.height + borderAndShadow * 2;
				var screenRatio = pos.iw/pos.ih;
				var imageRatio = el.zoomimageCfg.width/el.zoomimageCfg.height;
				
				// if the image is bigger then the viewport then resize the image to fit
				if (screenRatio > imageRatio) {
					if (height > pos.ih) {
						height = pos.ih;
						width = parseInt(height * imageRatio,10);
					}
				} else if (width > pos.iw) {
					width = pos.iw;
					height = parseInt(width / imageRatio, 10);
				}
				//if the image should be centered then do that, else center to trigger's position but do not leave the viewport
				var top = el.zoomimageCfg.centered ? 
							pos.t + parseInt((pos.ih - height)/2, 10)
							: 
							Math.min(
								Math.max(
									pos.t, 
									elPos.y + (elHeight - height)/2 - borderAndShadow
								), 
								pos.t + pos.ih - height
							);
				var left = el.zoomimageCfg.centered ? 
							pos.l + parseInt((pos.iw - width)/2, 10)
							:
							Math.min(
								Math.max(
									pos.l, 
									elPos.x + (elWidth - width)/2 - borderAndShadow 
								), 
								pos.l + pos.iw - width
							);
				var imgWidth = width - borderAndShadow * 2;
				var imgHeight = height - borderAndShadow * 2;
				
				if(el.zoomimageCfg.hideSource === true) {
					el.style.visibility = 'hidden';
				}
				
				//move the image's box and animated it
				$('#' + el.zoomimageCfg.box)
					.css({
						top: elPos.y + 'px',
						left: elPos.x + 'px',
						width: elWidth + 'px',
						height: elHeight + 'px'
					})
					.find('>div')
						.hide()
						.end()
					.find('img')
						.attr('src', el.zoomimageCfg.src)
						.css({
							top: 0,
							left: 0,
							width: '100%',
							height: '100%',
							display: 'block',
							borderWidth: '0px'
						})
						.end()
					.animate({
							width: imgWidth,
							height: imgHeight,
							top: top + borderAndShadow,
							left: left + borderAndShadow
						}, 
						el.zoomimageCfg.duration,
						el.zoomimageCfg.easing, 
						function(){
							$(this)
								.css({
									top: top + 'px',
									left: left + 'px',
									width: width + 'px',
									height: height + 'px'
								})
								.find('img')
									.css({
										top: el.zoomimageCfg.shadow + 'px',
										left: el.zoomimageCfg.shadow + 'px',
										width: imgWidth + 'px',
										height: imgHeight + 'px',
										borderWidth: el.zoomimageCfg.border + 'px'
									})
									.end()
								.find('>div:first')
									.find('div.zoomimage_sc')
										.css('height', height - el.zoomimageCfg.shadow*2 + 'px')
										.end()
									.show();
								el.zoomimageCfg.zoomed = true;
								EYE.zoomimage.focus(el);
								el.zoomimageCfg.onZoomIn.apply(el,[el.zoomimageCfg.box]);
						});
			},
			
			//focus image and show gallery controls if it is part of a gallery
			showControls: function(el) {
				if(el == undefined)
					return;
				if (el.zoomimageCfg == undefined) {
					el = $('#' + $(el).attr('zoomimage')).get(0);
				}
				var height,
					imgWidth,
					borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow;
				$('#' + el.zoomimageCfg.box)
					.find('img')
						.each(function(){
							imgWidth = parseInt($.curCSS(this, 'width'),10);
						})
						.end()
					.get(0).zoomimageControls = true;
				// if it has caption then display it
				if(el.zoomimageCfg.caption) {
					$('#' + el.zoomimageCfg.box)
					.find('>div:eq(2)')
						.stop()
						.css({
							bottom: borderAndShadow + 'px',
							left: borderAndShadow + 'px',
							width: imgWidth + 'px'
						})
						.show()
						.each(function() {
							this.style.height = 'auto';
							height = this.offsetHeight;
							this.style.height = '0';
						})
						.animate({height: height}, el.zoomimageCfg.duration);
				}
				//if it has controls then show them
				if(el.zoomimageCfg.controls) {
					// show controls only if it is part of a gallery
					if (EYE.zoomimage.libs[el.zoomimageCfg.lib] > 1) {
						$('#' + el.zoomimageCfg.box)
							.find('>div:eq(1)')
								.show()
								.each(function(){
									if (!el.zoomimageCfg.controlsHeight) {
										el.zoomimageCfg.controlsHeight = this.offsetHeight;
									}
									this.style.height = '0';
								})
								.css({
									top: borderAndShadow + 'px',
									left: borderAndShadow + 'px',
									width: imgWidth + 'px'
								})
								.animate({height: el.zoomimageCfg.controlsHeight}, el.zoomimageCfg.duration);
					}
				}
			},
			
			//zoom out the image and go to the next/previous if any
			zoomOut: function(el, goToNext) {
				var boxEl, elPos, borderAndShadow, elSize;
				// if the action was started by the trigger
				if (el.zoomimageCfg) {
					if (el.zoomimageCfg.zoomed === false) {
						return;
					}
					el.zoomimageCfg.beforeZoomOut.apply(el,[el.zoomimageCfg.box]);
					boxEl = document.getElementById(el.zoomimageCfg.box);
					
				// else try to find a link that has the same href as the image src
				} else {
					boxEl = el;
					el = $('a[href=' + $('img',boxEl).attr('src') + ']').get(0);
				}
				// the trigger was found so scale to image to trigger's size
				if (el) {
					elPos = EYE.getPosition(el, true);
					el.zoomimageCfg.zoomed = false;
					borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow;
					elSize = {
						width: el.offsetWidth,
						height: el.offsetHeight
					};
				// the trigger was not found so scale the image to its center
				} else {
					borderAndShadow = EYE.zoomimage.defaults.border + EYE.zoomimage.defaults.shadow;
					elSize = {
						width: 0,
						height: 0
					};
					elPos = EYE.getPosition(boxEl, true);
					elPos.y += parseInt(boxEl.offsetHeight/2, 10);
					elPos.x += parseInt(boxEl.offsetWidth/2, 10);
				}
				$(boxEl)
					.css({
						top: boxEl.offsetTop + borderAndShadow + 'px',
						left: boxEl.offsetLeft + borderAndShadow + 'px',
						width: boxEl.offsetWidth - borderAndShadow*2 + 'px',
						height: boxEl.offsetHeight - borderAndShadow*2 + 'px'
					})
					.find('>div')
						.stop()
						.hide()
						.end()
					.find('img')
						.css({
							top: 0,
							left: 0,
							width: '100%',
							height: '100%',
							borderWidth: '0px'
						})
						.end()
					.animate(
						{
							top: elPos.y + 'px',
							left: elPos.x + 'px',
							width: elSize.width + 'px',
							height: elSize.height + 'px'
						}, 
						// if the trigger was not found the use the default duration
						el ? el.zoomimageCfg.duration : EYE.zoomimage.defaults.duration,
						el.zoomimageCfg.easing,
						function() {
							//hide image and remove focus
							EYE.zoomimage.blur();
							$(this).hide();
							// if the trigger was found then aply callback and try to focus the next one zoomed
							if (el) {
								if(el.zoomimageCfg.hideSource === true) {
									el.style.visibility = 'visible';
								}
								el.zoomimageCfg.onZoomOut.apply(el,[el.zoomimageCfg.box]);
								if (!goToNext) {
									EYE.zoomimage.focus($('div.zoomimage:visible:last').not(':animated').get(0));
								}
							//the trigger was not found so remove the image since no trigger is present in the page
							} else {
								$(boxEl).stop().remove();
							}
						}
					);
			},
			
			mouseOver: function(e) {
				var triggerEl = document.getElementById($(this).attr('zoomimage'));
				if (triggerEl.zoomimageCfg.zoomed === true && this.zoomimageControls == false) {
					EYE.zoomimage.showControls(triggerEl);
				}
				return false;
			},
			
			mouseOut: function(e) {
				if ( !EYE.isChildOf(this, e.relatedTarget, this) ) {
					$(this)
						.find('>div:not(:first)')
							.stop()
							.hide();
					this.zoomimageControls  = false;
				}
				return false;
			},
			
			// prepare for possible drag
			mouseDown: function(e) {
				// find the trigger
				var triggerEl = document.getElementById($(this).attr('zoomimage'));
				//if the trigger was found then prepare informations for drag
				if (triggerEl) {
					$.extend(EYE.zoomimage,{
						current: this,
						prevent: triggerEl.zoomimageCfg.prevent,
						moved: false,
						diff: {
							x: e.pageX - this.offsetLeft,
							y: e.pageY - this.offsetTop
						},
						pointer: {
							x: e.pageX ,
							y: e.pageY
						}
					});
					$(document)
						.bind('mousemove', EYE.zoomimage.mouseMove)
						.bind('mouseup', EYE.zoomimage.mouseUp);
				// if the trigger was not found then it is an orphan and zoom it out
				} else {
					$(this).zoomimageClear();
				}
				return false;
			},
			
			//do the drag if prevent distance was overtake
			mouseMove: function(e) {
				var diffX = Math.abs(EYE.zoomimage.pointer.x - e.pageX);
				var diffY = Math.abs(EYE.zoomimage.pointer.y - e.pageY);
				//the prevent distance was not reached yet so we check if it is reached already
				if (EYE.zoomimage.moved === false) {
					if ( diffX > EYE.zoomimage.prevent|| diffY > EYE.zoomimage.prevent) {
						EYE.zoomimage.moved = true;
						$(EYE.zoomimage.current).addClass('zoomimage_move');
						if (!$(EYE.zoomimage.current).is('.zoomimage_focused')) {
							EYE.zoomimage.focus(EYE.zoomimage.current);
						}
					}
				// the prevent distance was overtake so the element can be moved
				} else {
					EYE.zoomimage.current.style.top = e.pageY - EYE.zoomimage.diff.y + 'px';
					EYE.zoomimage.current.style.left = e.pageX - EYE.zoomimage.diff.x + 'px';
				}
				return false;
			},
			
			//the drag stops
			mouseUp: function (e) {
				$(EYE.zoomimage.current).removeClass('zoomimage_move');
				EYE.zoomimage.current = null;
				$(document)
					.unbind('mousemove', EYE.zoomimage.mouseMove)
					.unbind('mouseup', EYE.zoomimage.mouseUp);
				return false;
			},
			
			// click on image
			imageClick: function(e) {
				$(document)
					.unbind('mousemove', EYE.zoomimage.mouseMove)
					.unbind('mouseup', EYE.zoomimage.mouseUp);
				var el = document.getElementById($(this).attr('zoomimage'));
				// if the trigger was found
				if (el) {
					//if the image was not moved but was focused
					if (EYE.zoomimage.moved === false && $(this).is('.zoomimage_focused')) {
						// if the event target is a link then it was a click on one of the controls and go to the next image
						if ($(e.target).is('a')) {
							EYE.zoomimage.zoomNext(el, e.target.className == 'zoomimage_next' ? 1 : -1);
							var goToNext = true;
						// else just zoom it out
						} else {
							EYE.zoomimage.zoomOut(el, goToNext||false);
						}
					// just focus the image
					} else if(!$(this).is('.zoomimage_focused')) {
						EYE.zoomimage.focus(this);
					}
				//the trigger was not found so the image is orphan and zoom it out
				} else {
					$(this).zoomimageClear();
				}
				return false;
			},
			
			//zoom out any opened image and clear orphan images
			clear: function() {
				var subject = this;
				if (subject.size() == 0) {
					subject = $('div.zoomimage');
				}
				return subject.each(function(){
					var triggerEl = document.getElementById($(this).attr('zoomimage'));
					if (triggerEl) {
						EYE.zoomimage.zoomOut(triggerEl, false);
					} else {
						EYE.zoomimage.zoomOut(this, false);
					}
				});
			},
			
			// zoom the next image in gallery
			zoomNext: function(el, dir) {
				if(el.zoomimageCfg.zoomed === false) {
					return;
				}
				EYE.zoomimage.zoomOut(el, true);
				var nextImg = el.zoomimageCfg.iteration + dir;
				var lib = $(el).attr('zoomimage');
				var maxImg = EYE.zoomimage.libs[lib];
				if (nextImg < 0) {
					nextImg = maxImg - 1;
				} else if (nextImg >= maxImg) {
					nextImg = 0;
				}
				EYE.zoomimage.zoomIn($('a[zoomimage="' + lib + '"]').get(nextImg));
			},
			
			//hande any key pressed
			keyPressed: function(e) {
				var el = $('div.zoomimage_focused');
				if (el.size() == 1) {
					var pressedKey = e.charCode || e.keyCode || -1;
					el = $('#' + $(el).attr('zoomimage')).get(0);
					var lib = $(el).attr('zoomimage');
					switch (pressedKey)
					{
						//end
						case 35:
							// go to the last image in the gallery
							if (EYE.zoomimage.libs[lib] > 1 &&  EYE.zoomimage.libs[lib] - 1 != el.zoomimageCfg.iteration) {
								EYE.zoomimage.zoomNext(el, EYE.zoomimage.libs[lib] - el.zoomimageCfg.iteration - 1);
								return false;
							}
						break;
						//home
						case 36:
							// go to the first image in the gallery
							if (EYE.zoomimage.libs[lib] > 1 && el.zoomimageCfg.iteration != 0) {
								EYE.zoomimage.zoomNext(el, - el.zoomimageCfg.iteration);
								return false;
							}
						break;
						//down;
						case 40:
						//left
						case 37:
						//backspace
						case 8:
						//page up
						case 33:
						//p
						case 80:
						case 112:
							// go to the previous image in the gallery
							if (EYE.zoomimage.libs[lib] > 1) {
								EYE.zoomimage.zoomNext(el, -1);
								return false;
							}
						break;
						//up
						case 38:
						//right
						case 39:
						//page down
						case 34:
						//space
						case 32:
						//n
						case 110:
						case 78:
							// go to the next image in the gallery
							if (EYE.zoomimage.libs[lib] > 1) {
								EYE.zoomimage.zoomNext(el, 1);
								return false;
							}
						break;
						//escape
						case 27:
							// well zoome out the curent image
							EYE.zoomimage.zoomOut(el, false);
							return false;
						break;
					}
				}
			},
			
			
			// focus on image
			focus: function(el) {
				if(el == undefined)
					return;
				if (el.zoomimageCfg == undefined) {
					el = $('#' + $(el).attr('zoomimage')).get(0);
				} else {
					var showControls = true;
				}
				// if another image is focused then remove focus
				EYE.zoomimage.blur(el);
						
				$('#' + el.zoomimageCfg.box)
					.not('.zoomimage_focused')
					.addClass('zoomimage_focused');
				el.zoomimageCfg.onFocus.apply(el,[el.zoomimageCfg.box]);
				if (el.zoomimageCfg.controlsTrigger == 'focus' || showControls) {
					EYE.zoomimage.showControls(el);
				}
			},
			
			//blur image
			blur: function(el) {
				$('div.zoomimage_focused')
					.not('#' + (el == undefined ? 'fakezoomimage' : el.zoomimageCfg.box))
					.removeClass('zoomimage_focused')
					.each(function(){
						this.zoomimageControls = false;
					})
					.find('>div:not(:first)')
						.stop()
						.hide();
			},
			
			preload: function(el) {
				// place the loading aimation on top
				var boxEl = $('#' + el.zoomimageCfg.box).show();
				boxEl.find('>div, img').hide();
				var elPos = EYE.getPosition(el, true);
				boxEl
					.find('>div:last')
						.show()
						.end()
					.css({
						top: elPos.y + 'px',
						left: elPos.x + 'px',
						width: el.offsetWidth + 'px',
						height: el.offsetHeight + 'px'
					});
				// preload the image
				var preld= new Image();
				preld.src = el.href;
				//if the image was laoded already
				if (preld.complete) {
					EYE.zoomimage.markPreloaded(preld, el);
				// else place a callback
				} else {
					preld.onload = function() {
						EYE.zoomimage.markPreloaded(preld, el);
					};
				}
			},
			
			markPreloaded: function(preld, el) {
				//mark image as loaded and remember the size and source
				$.extend(el.zoomimageCfg,{
					loaded: true,
					width: preld.width,
					height: preld.height,
					src: preld.src
				});
				// hide loading animation
				$('#' + el.zoomimageCfg.box)
					.find('div.zoomimage_loading')
					.hide();
				//if the image waits to be enlarged then zoom in
				if (el.zoomimageCfg.loading) {
					el.zoomimageCfg.loading = false;
					EYE.zoomimage.zoomIn(el);
				}
				el.zoomimageCfg.onLoad.apply(el,[el.zoomimageCfg.box]);
			},
			
			//constructor
			init: function(opt) {
				//generate a library key
				var libKey = parseInt(Math.random()*2000,10);
				//store the number of images in the library
				EYE.zoomimage.libs[libKey] = 0;
				opt = $.extend({lib:libKey}, EYE.zoomimage.defaults, opt||{});
				return this.each(function(){
					var jQEl = $(this);
					var el = this;
					//consider only the links pointing to an image
					if (el.href && el.href.toLowerCase().match(EYE.zoomimage.types) != null) {
						//store library options
						el.zoomimageCfg = $.extend({}, opt, {
							zoomed: false,
							loading: false,
							loaded: false,
							animated: false,
							src: el.href,
							iteration: EYE.zoomimage.libs[libKey],
							box: 'zoomimage_' + parseInt(Math.random() * 2000, 10) + ''
						});
						//increment the number of images in the library
						EYE.zoomimage.libs[libKey]++;
						jQEl
							.bind('click', EYE.zoomimage.click)
							.attr('zoomimage', libKey)
							.attr('zoomimageBox', el.zoomimageCfg.box);
						var currId = jQEl.attr('id');
						if (!currId) {
							currId = el.zoomimageCfg.box + '_trigger';
							jQEl.attr('id', currId);
						}
						var titleAttr = $(el).attr('title');
						if (titleAttr == '' || titleAttr == false) {
							el.zoomimageCfg.caption = false;
						}
						// generate the HTML for the image's box
						$(EYE.zoomimage.template.join(''))
							.attr('id', el.zoomimageCfg.box)
							.attr('zoomimage', currId)
							.addClass(el.zoomimageCfg.className)
							.appendTo(document.body)
							.bind('mousedown', EYE.zoomimage.mouseDown)
							.bind('click', EYE.zoomimage.imageClick)
							.each(function(){
								this.zoomimageControls = false;
								if (el.zoomimageCfg.controlsTrigger != 'focus') {
									$(this)
										.bind('mouseover', EYE.zoomimage.mouseOver)
										.bind('mouseout', EYE.zoomimage.mouseOut);
								}
							})
							.find('>div')
								.not(':first')
									.css('opacity', el.zoomimageCfg.opacity)
									.end()
								.filter('div:eq(2)')
									.html('' + titleAttr + '
');
						if (el.zoomimageCfg.preload == 'load') {
							EYE.zoomimage.preload(el);
						}
						if (EYE.zoomimage.trackKey === false) {
							EYE.zoomimage.trackKey = true;
							$(document).bind('keydown', EYE.zoomimage.keyPressed);
						}
					}
				});
			}
		}
	});
	
	$.fn.extend({
	
		/**
		 * Open all images found in 'href' attribute from each element specified in the selection. The images are grouped in galleries. The images are preloaded before any user interation.
		 * @name zoomimage
		 * @description Open all images found in 'href' attribute from each element specified in the selection. The images are grouped in galleries
		 * @param 	Hash		options			A hash of parameters. All parameters are optional.
		 * @option	float 		opacity			The opacity for the caption and controls. Default: 0.3
		 * @option	int			border			Image's border. Default: 0
		 * @option	int			duration		Animation duration. Default 300
		 * @option	int			prevent			Pixes to move the mouse before the images is dragged (prevents accidental dragging). Default: 14
		 * @option	boolean		controls		Whatever if the controls are displayed (if the image is not part of an libriry then the controls are not displayed)
		 * @option	boolean		caption			Whatever if the caption is displayed (the caption text is the text from 'title' atribute. Default: true
		 * @option	boolean		centered		Whatever if the image should be centered in the viewport or to the trigger. Default: false
		 * @option	string		easing			Animation easing. Default: linear
		 * @option	boolean		hideSource		Whatever to hide source when the image is opened. Default: false
		 * @option	string		className		CSS class to add to image's box. Default: false
		 * @option	string		controlsTrigger	'focus' to show caption and controls when the box is focused or 'mouseover' to show controls and caption on mouse over. Default: 'focus'
		 * @option	string		preload			'click' to preload the image when the trigger is clicked or 'load' to preload the image on document load. Default: 'click'
		 * @option	function	onLoad			Callback function triggered when the image was loaded
		 * @option	function	beforeZoomIn	Callback function triggered before the image is zoomed in
		 * @option	function	onZoomIn		Callback function triggered when the image is zooms in
		 * @option	function	beforeZoomOut	Callback function triggered before the image is zoomed out
		 * @option	function	onZoomOut		Callback function triggered when the image is zooms out
		 * @option	function	onFocus			Callback function triggered when the image is focused
		 */
		zoomimage: EYE.zoomimage.init,
		
		/**
		 * Zooms out all opened images and removes orphans (when the trigger was not found)
		 * To clear specific images use for slector 'div.zooimage[whatever]', else all the images are processed
		 */
		zoomimageClear: EYE.zoomimage.clear
	});
})(jQuery);