window.PanZoom = (function(window, undefined) {
	// default values if not provided
	var CURSOR = 'zoom-out';
	var DIMMING = false;
	var MARGIN_COLOR = '#ffffff';
	var BORDER_RADIUS = 10;
	var ZOOM_DURATION = 200;
	var ZOOM_STEPS = 10;
	var OPENED_POPUP_WINDOWS = [];
	var SHADOW = { blur : 15,
	spread : 5,
	opacity : 0.5,
	color : '#000000' };

	/**
	 * Returns an unique random number
	 */
	function s4() {
		return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
	}

	// returns IE version
	var ie = (function() {
		var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i');
		while (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', all[0])
			;
		return v > 4 ? v : undef;
	}());

	/**
	 * Returns the the r,g,b values coresponding to the specified hex string
	 */
	function hexToRgb(hex) {
		// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
		var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
		hex = hex.replace(shorthandRegex, function(m, r, g, b) {
			return r + r + g + g + b + b;
		});

		var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
		return result ? { r : parseInt(result[1], 16),
		g : parseInt(result[2], 16),
		b : parseInt(result[3], 16) } : null;
	}

	/**
	 * Returns the object when is set, otherwise the provided default value
	 */
	function isNull(obj, value) {
		return (typeof obj === 'undefined' || obj === null) ? value : obj;
	}
	/**
	 * Cross-browser surogate for addEventListener
	 */
	function _addEventListener(object, type, listener, useCapture) {
		return 'function' == typeof object.addEventListener ? object.addEventListener(type, listener, useCapture) : object.attachEvent(type, listener);
	}
	/**
	 * Removes a popup from DOM and from internal stack
	 */
	function removePopup(popup) {
		if (popup) {
			var id = popup.id;
			document.body.removeChild(popup);
			popup = null;
			delete OPENED_POPUP_WINDOWS[id];
		}
	}
	/**
	 * Listen ESC globaly and remove the top popup, if any
	 */
	_addEventListener(document, (ie < 9 ? 'on' : '') + 'keydown', function(ev) {
		ev = ev || window.event;
		if (ev.keyCode == 27 && (divId = Object.keys(OPENED_POPUP_WINDOWS).pop()) && (top_div = document.getElementById(divId))) {
			zoomOut(top_div);
			if (ev.preventDefault)
				ev.preventDefault();
			else
				ev.returnValue = false;
			return false;
		}
	});

	/**
	 * Calculates and adjust the div position
	 */
	var popupCalcSize = function(id, hcentered, max_width, max_height) {
		hcentered = isNull(hcentered, true);
		var newdiv = document.getElementById(id);
		if (!newdiv)
			return false;
		var WPadminBar = document.getElementById('wpadminbar'), WPadminMenu = document.getElementById('adminmenuback'), newdiv = document.getElementById(id);
		var WPadminBarHeight = WPadminBar ? WPadminBar.clientHeight : 0, WPadminMenuWidth = WPadminMenu ? WPadminMenu.clientWidth : 0;
		var style = 'width:100%;';
		window_height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
		window_width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
		if (newdiv.clientHeight > window_height - 100 - WPadminBarHeight) {
			var h = window_height - WPadminBarHeight - 50;
			style += 'overflow-y:auto;max-height:' + (h - WPadminBarHeight - 24) + 'px;';
			newdiv.style.maxHeight = h + 'px';
		}
		if (newdiv.clientWidth > window_width - 100 - WPadminMenuWidth) {
			var w = window_width - 50;
			style += 'overflow:auto;max-width:' + (w - 10 - WPadminMenuWidth) + 'px;';
			newdiv.style.maxWidth = w + 'px';
		}

		if (hcentered) {
			newdiv.style.left = (window_width - newdiv.clientWidth + WPadminMenuWidth) / 2 + 'px';
			newdiv.style.top = (window_height - newdiv.clientHeight + WPadminBarHeight) / 2 + 'px';
		}

		if (style.length > 0)
			newdiv.innerHTML = newdiv.innerHTML.replace(/title=(['"]*)@INNER-STYLE@\1/, 'style="' + style + '"');
		if (null !== isNull(max_width, null))
			newdiv.style.maxWidth = isNull(max_width + 'px', newdiv.style.maxWidth);
		if (null !== isNull(max_height, null))
			newdiv.style.maxHeight = isNull(max_height + 'px', newdiv.style.maxHeight);
	};

	/**
	 * Creates the zoom panel and the div/image container for the div/image
	 * content from url
	 */
	function initPanel(url, caption, callback, width, height, zindex) {
		var uuid = s4() + s4() + s4() + s4(), divIdName = 'panZoom_' + uuid, inner_style = '', shadow_color = hexToRgb(PanZoom.SHADOW.color);
		newdiv = document.createElement('div');
		newdiv.setAttribute('id', divIdName);
		if (PanZoom.BORDER_RADIUS)
			inner_style = 'border-radius:' + PanZoom.BORDER_RADIUS + 'px;';
		var prefix = '<div style="background-color:' + PanZoom.MARGIN_COLOR + ';box-shadow:' + ('0px 0px ' + SHADOW.blur + 'px ' + SHADOW.spread + 'px ' + 'rgb' + (ie < 9 ? '' : 'a') + '(' + shadow_color.r + ',' + shadow_color.g + ',' + shadow_color.b + (ie < 9 ? '' : ',' + SHADOW.opacity) + ')') + ';' + inner_style + '">';
		var sufix = '</div>';
		if (caption)
			sufix = '<div style="text-align:center"></div>' + sufix;
		if (url)
			newdiv.innerHTML = prefix + '<img id="panimg_' + uuid + '" style="' + (PanZoom.CURSOR ? 'cursor:' + PanZoom.CURSOR + ';' : '') + 'border:10px;width:100%;height:100%;' + inner_style + '" src="' + url + '" onclick="PanZoom.zoomOut(this.parentElement.parentElement)">' + sufix;
		else
			newdiv.innerHTML = prefix + '<div id="div_' + uuid + '" style="' + (PanZoom.CURSOR ? 'cursor:' + PanZoom.CURSOR + ';' : '') + 'border:10px;width:100%;height:100%;' + inner_style + '">' + sufix + '</div>';
		newdiv.style.margin = 'auto';
		newdiv.style.position = 'absolute';
		newdiv.style.top = 0;
		newdiv.style.left = 0;
		newdiv.style.bottom = 0;
		newdiv.style.right = 0;
		newdiv.style.width = 0;
		newdiv.style.height = 0;
		newdiv.style.color = 'black';
		newdiv.style.select = 'none';
		newdiv.style.zIndex = zindex ? zindex : 1000;

		document.body.appendChild(newdiv);

		OPENED_POPUP_WINDOWS[divIdName] = false;

		popupCalcSize(divIdName, false, width, height);

		if (callback) {
			btn_prev = document.createElement('div');
			btn_prev.className = 'previmg';
			btn_prev.onclick = function() {
				shiftImage(this, callback);
			};
			newdiv.firstChild.appendChild(btn_prev);

			btn_next = document.createElement('div');
			btn_next.className = 'nextimg';
			btn_next.onclick = function() {
				shiftImage(this, callback);
			};
			newdiv.firstChild.appendChild(btn_next);

			updateNavBtn(url, btn_prev, callback);
		}

		return newdiv;
	}

	/**
	 * Update the visibility of gallery's navigation buttons
	 */
	function updateNavBtn(src, sender, callback) {
		var bname = src.substr(src.lastIndexOf('/') + 1), prev = callback(bname, -1), next = callback(bname, 1);
		var btn_prev = sender.parentNode.getElementsByClassName('previmg')[0];
		var btn_next = sender.parentNode.getElementsByClassName('nextimg')[0];
		btn_prev.style.visibility = prev ? 'visible' : 'hidden';
		btn_next.style.visibility = next ? 'visible' : 'hidden';
	}

	/**
	 * Shifts to the left|right the current image in gallery
	 */
	function shiftImage(sender, callback) {
		if (!sender || !callback)
			return false;

		var img = sender.parentNode.getElementsByTagName('img')[0];
		var caption = sender.parentNode.firstChild.nextSibling;
		if (!img)
			return false;

		var obj = callback(img.src.substr(img.src.lastIndexOf('/') + 1), 'previmg' == sender.className ? -1 : 1);
		if (obj && obj.img) {
			img.src = obj.img;
			caption.innerHTML = obj.caption;
			updateNavBtn(obj.img, sender, callback);
		}
		return true;
	}

	/**
	 * Attach a background dimmed panel to the parent
	 */
	function addBackWall(parent) {
		var uuid = s4() + s4() + s4() + s4(), divIdName = 'bakwall_' + uuid;
		var newdiv = document.createElement('div');

		parent = isNull(parent, document.body);

		newdiv.setAttribute('id', divIdName);
		newdiv.style.margin = 0;
		newdiv.style.padding = 0;
		newdiv.style.padding = 0;
		newdiv.style.border = 0;
		newdiv.style.position = 'fixed';
		newdiv.style.width = '100%';
		newdiv.style.height = '100%';
		newdiv.style.left = 0;
		newdiv.style.top = 0;
		newdiv.style.backgroundColor = '#000';
		newdiv.style.zIndex = -1;
		newdiv.style.opacity = 0.8;

		parent.appendChild(newdiv);

		return newdiv;
	}
	/**
	 * Set some style attributes for the specified style_el
	 */
	function mangleStyle(style_el, padding, position, top, left, transform) {
		if (!style_el)
			return;
		style_el.padding = padding;
		style_el.position = position;
		style_el.top = top;
		style_el.left = left;
		style_el.transform = transform;
	}
	/**
	 * Creates the zoom panel and loads the image from the url
	 * 
	 * @param url
	 *            The URL for the image
	 * @param caption
	 *            The string to be displayed as a caption; specify null to hide
	 *            the caption
	 * @param factor
	 *            1 when zoom-in;-1 when zoom out
	 * @param div
	 *            The DOM div element to zoom-in/out (especially usefull when
	 *            comes to zoom-out)
	 * @param dimming
	 *            true when you want the picture to be shielded by a dimmed
	 *            background panel, false otherwise
	 * @param callback
	 *            A callback function with 2 arguments: pos, offset. Pos is the
	 *            basename of the current image; offset is the navigation offset
	 *            from the current position (eg. +2 or -1). The callback returns
	 *            an object with the basename to the image specified by the
	 *            offset and its caption (or null if no caption defined).
	 */
	var zoomImage = function(url, caption, factor, div, dimming, callback) {
		factor = isNull(factor, 1);
		dimming = isNull(dimming, PanZoom.DIMMING);
		var panzoom_div = div ? div : initPanel(url, caption, callback);
		var img = document.getElementById(panzoom_div.id).firstChild.firstChild;
		if (dimming)
			var wall = addBackWall(panzoom_div);

		var child_style = panzoom_div.firstChild.style;

		// restore its default style
		if (factor < 0)
			mangleStyle(child_style, 0, 'absolute', 0, 0, '');

		var img_obj = new Image();
		img_obj.onload = function() {
			if (this.complete || this.readyState === 4) {
				if (caption)
					panzoom_div.firstChild.firstChild.nextSibling.innerHTML = caption;
				var steps = PanZoom.ZOOM_STEPS, inc_x = factor * (factor > 0 ? this.width - img.clientWidth : this.width) / steps, inc_y = factor * (factor > 0 ? this.height - img.clientHeight : this.height) / steps;
				var timer = setInterval(function() {
					panzoom_div.style.width = (panzoom_div.clientWidth + inc_x) + 'px';
					panzoom_div.style.height = (panzoom_div.clientHeight + inc_y) + 'px';
					steps--;
					if (steps < 1) {
						clearInterval(timer);
						if (factor < 0)
							removePopup(panzoom_div);
						else
							// mangle the default style
							mangleStyle(child_style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
					}

				}, PanZoom.ZOOM_DURATION / PanZoom.ZOOM_STEPS);
			}
		}
		img_obj.src = img.src;

		return panzoom_div;
	}

	/**
	 * Creates a logical image gallery and provides support for navigation
	 * 
	 * @param base_url
	 *            The absolute|relative path where the images are located.
	 * @param img_list
	 *            A comma-delimited list of image files (when base_url is used
	 *            only the file basename should be provided)
	 * @param caption_list
	 *            A comma-delimited list of captions for each image specified in
	 *            img_list
	 * @param factor
	 *            1 when zoom-in;-1 when zoom out
	 * @param div
	 *            The DOM div element to zoom-in/out (especially usefull when
	 *            comes to zoom-out)
	 * @param dimming
	 *            true when you want the picture to be shielded by a dimmed
	 *            background panel, false otherwise
	 */
	var zoomGallery = function(base_url, img_list, caption_list, factor, div, dimming) {
		if (!img_list || !img_list.length)
			return;
		base_url = base_url ? base_url + ('/' != base_url.substr(-1) ? '/' : '') : '';
		var ilist = img_list.split(','), clist = caption_list ? caption_list.split(',') : [];

		var callback = function(pos, offset) {
			var i = ilist.indexOf(pos);
			if ((-1 == i) || (i + offset >= ilist.length || i + offset < 0))
				return null;
			return { img : base_url + ilist[i + offset],
			caption : (i + offset >= clist.length) ? null : clist[i + offset] }
		};

		pos = ilist[0];
		caption = clist.length ? clist[0] : null;
		var pz = zoomImage(base_url + pos, caption, factor, div, dimming, callback);

	}

	/**
	 * Creates the zoom panel and listen for the HTML content
	 * 
	 * @param width
	 *            The div width
	 * @param height
	 *            The div height
	 * @param factor
	 *            1 when zoom-in;-1 when zoom out
	 * @param div
	 *            The DOM div element to zoom-in/out (especially usefull when
	 *            comes to zoom-out)
	 * @param dimming
	 *            true when you want the picture to be shielded by a dimmed
	 *            background panel, false otherwise
	 * @param callback
	 *            A callback function with one string argument that is called
	 *            tight before the div content is filled. It can be used for
	 *            instance to sanitize the div HTML content.
	 */
	var zoomDiv = function(width, height, factor, div, dimming, callback) {
		factor = isNull(factor, 1);
		dimming = isNull(dimming, PanZoom.DIMMING);
		var panzoom_div = div ? div : initPanel();
		var fc = panzoom_div.firstChild;
		mangleStyle(fc.style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
		var div = document.getElementById(panzoom_div.id).firstChild.firstChild;
		if (factor > 0) {
			fc.style.width = width + 'px';
			fc.style.height = height + 'px';
			fc.style.transform = 'translateX(-50%) translateY(-50%) scale(0)';
			if (dimming)
				var wall = addBackWall(panzoom_div);
		}

		var zoom_callback = function(xmlhttp) {
			if (factor < 0 || 'object' != typeof xmlhttp || xmlhttp.readyState === 4) {
				if (xmlhttp) {
					var t = 'object' == typeof xmlhttp ? xmlhttp.responseText : xmlhttp;
					if (callback)
						t = callback(t);
					div.innerHTML = t;
				}
				var i = 0, scale;
				var timer = setInterval(function() {
					scale = (factor < 0 ? 1 : 0) + factor * (i / PanZoom.ZOOM_STEPS);
					fc.style.transform = 'translateX(-50%) translateY(-50%) scale(' + scale + ')';
					if (i > PanZoom.ZOOM_STEPS) {
						clearInterval(timer);
						if (factor < 0)
							removePopup(panzoom_div);
						else
							// remove the scale transformation
							mangleStyle(fc.style, '10px', 'fixed', '50%', '50%', 'translateX(-50%) translateY(-50%)');
					}
					i++;
				}, PanZoom.ZOOM_DURATION / PanZoom.ZOOM_STEPS);
			}
		}

		// trigger the zoom-out
		if (factor < 0)
			return zoom_callback();

		return { div : panzoom_div,
		callback : zoom_callback };
	}

	/**
	 * Zoom-out (hides) the picture specified by div
	 */
	var zoomOut = function(div) {
		var imgs = div.getElementsByTagName('img'), found = false, i;
		for (i = 0; i < imgs.length; i += 1)
			if (0 === imgs[i].id.indexOf('panimg_')) {
				// by convention if there is an image named panimg_ then we
				// zoom-out the image else the div
				found = true;
				break;
			}
		if (found)
			zoomImage(null, null, -1, div, false);
		else
			zoomDiv(null, null, -1, div, false);
	};

	return { zoomImage : zoomImage,
	zoomDiv : zoomDiv,
	zoomOut : zoomOut,
	zoomGallery : zoomGallery,
	popupCalcSize : popupCalcSize,
	DIMMING : DIMMING,
	MARGIN_COLOR : MARGIN_COLOR,
	BORDER_RADIUS : BORDER_RADIUS,
	ZOOM_DURATION : ZOOM_DURATION,
	ZOOM_STEPS : ZOOM_STEPS,
	SHADOW : SHADOW,
	CURSOR : CURSOR };
})(this);