/*!
 * Ajax upload
 * Project page - http://valums.com/ajax-upload/
 * Copyright (c) 2008 Andris Valums, http://valums.com
 * Licensed under the MIT license (http://valums.com/mit-license/)
 * Version 3.5 (23.06.2009)
 */

/**
 * Changes from the previous version:
 * 1. Added better JSON handling that allows to use 'application/javascript' as a response
 * 2. Added demo for usage with jQuery UI dialog
 * 3. Fixed IE "mixed content" issue when used with secure connections
 *
 * For the full changelog please visit:
 * http://valums.com/ajax-upload-changelog/
 */

var ajaxUpload = (function(){

	var
		d = document,
		w = window;

	function getBox(el) {
		var offset = $(el).offset();

		return {
			left: offset.left,
			right: offset.left + el.offsetWidth,
			top: offset.top,
			bottom: offset.top + el.offsetHeight
		};
	}

	/**
	 * Crossbrowser mouse coordinates
	 */
	function getMouseCoords(e) {

		// pageX/Y is not supported in IE
		// http://www.quirksmode.org/dom/w3c_cssom.html
		if (!e.pageX && e.clientX) {
			// In Internet Explorer 7 some properties (mouse coordinates) are treated as physical, while others are logical (offset).
			var zoom = 1;
			var body = document.body;

			if (body.getBoundingClientRect) {
				var bound = body.getBoundingClientRect();
				zoom = (bound.right - bound.left)/body.clientWidth;
			}

			return {
				x: e.clientX / zoom + d.body.scrollLeft + d.documentElement.scrollLeft,
				y: e.clientY / zoom + d.body.scrollTop + d.documentElement.scrollTop
			};
		}

		return {
			x: e.pageX,
			y: e.pageY
		};
	}

	/**
	 * Function generates unique id
	 */
	var getUID = function() {
		var id = 0;

		return function() {
			return 'ValumsAjaxUpload' + id++;
		}
	}();

	function fileFromPath(file) {
		return file.replace(/.*(\/|\\)/, '');
	}

	function getExt(file) {
		return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
	}

	/**
	 * @param {String|Element|jQuery} button
	 * @param {Object} options
	 */
	function AjaxUpload(button, options) {

		button = $(button).get(0);

		var
			/** @type {Element} */
			input = null,
			disabled = false,
			submitting = false,
			// Variable changes to true if the button was clicked 3 seconds ago (requred to fix Safari on Mac error)
			justClicked = false,
			/** @type {Element} */
			parentDialog = d.body,

			settings = {
				// Location of the server-side upload script
				action: 'upload.php',
				// File upload name
				name: 'userfile',
				// Additional data to send
				data: {},
				// Submit file as soon as it's selected
				autoSubmit: true,
				// The type of data that you're expecting back from the server.
				// Html and xml are detected automatically.
				// Only useful when you are using json data as a response.
				// Set to "json" in that case.
				responseType: false,
				// When user selects a file, useful with autoSubmit disabled
				onChange: function(file, extension) {},
				// Callback to fire before file is uploaded
				// You can return false to cancel upload
				onSubmit: function(file, extension) {},
				// Fired when file upload is completed
				// WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
				onComplete: function(file, response) {}
			};

		var that = {
			setData: function(data) {
				settings.data = data;
			},
			disable: function() {
				disabled = true;
			},
			enable: function() {
				disabled = false;
			},
			/**
			 * Removes ajaxupload
			 */
			destroy: function() {
				if (!input) return;

				input.parentNode && input.parentNode.removeChild(input);
				input = null;
			},
			submit: submit
		}

		init();

		function init() {

			// Merge the users options with our defaults
			for (var i in options) {
				settings[i] = options[i];
			}

			createInput();
			rerouteClicks();
		}

		/**
		 * Creates invisible file input above the button
		 */
		function createInput(){
			var newInput = $(d.createElement('input'));
			newInput
				.attr({
					type: 'file',
					name: settings.name
				})
				.css({
					position: 'absolute',
					margin: '-5px 0 0 -200px',
					padding: 0,
					width: 220,
					height: 30,
					fontSize: 14,
					opacity: 0,
					cursor: 'pointer',
					display: 'none',
					zIndex:  2147483583 //Max zIndex supported by Opera 9.0-9.2x
					// Strange, I expected 2147483647
				})
				.appendTo($(parentDialog));

			newInput.change(function() {
				// get filename from input
				var file = fileFromPath(this.value);

				if (false == settings.onChange.call(this, file, getExt(file))) return;

				// Submit form when value is changed
				settings.autoSubmit && submit();
			});

			/**
			 * Fixing problem with Safari.
			 * The problem is that if you leave input before the file select dialog opens it does not upload the file.
			 * As dialog opens slowly (it is a sheet dialog which takes some time to open) there is some time while you can leave the button.
			 * So we should not change display to none immediately.
			 */
			newInput.click(function() {
				justClicked = true;

				setTimeout(function() {
					// We will wait 3 seconds for dialog to open.
					justClicked = false;
				}, 3000);
			});

			input = newInput.get(0);
		}

		function rerouteClicks() {
			// IE displays 'access denied' error when using this method. Other browsers just ignore click().
			// $(button).click(function(e){
			//   input.click();
			// });

			var
				box,
				dialogOffset = { top: 0, left: 0 },
				over = false;

			$(button).mouseover(function(e){
				if (!input || over) return;

				over = true;
				box = getBox(button);

				if (parentDialog != d.body) {
					dialogOffset = $(parentDialog).offset();
				}
			});


			// We can't use mouseout on the button, because invisible input is over it.
			$(document).mousemove(function(e) {
				var newInput = $(input);

				if (!newInput.length || !over) return;

				if (disabled){
					$(button).removeClass('hover');
					newInput.css('display', 'none');
					return;
				}

				var c = getMouseCoords(e);

				if ((c.x >= box.left) && (c.x <= box.right) && (c.y >= box.top) && (c.y <= box.bottom)) {
					newInput.css({
						top: c.y - dialogOffset.top,
						left: c.x - dialogOffset.left,
						display: 'block'
					});
					$(button).addClass('hover');
				} else {
					// mouse left the button
					over = false;

					justClicked || newInput.css('display', 'none');
					$(button).removeClass('hover');
				}
			});
		}

		/**
		 * Создает iframe с уникальным именем.
		 */
		function createIframe() {
			// unique name
			// We cannot use getTime, because it sometimes return same value in safari :(
			var id = getUID();

			// Remove ie6 "This page contains both secure and nonsecure items" prompt
			// http://tinyurl.com/77w9wh
			var iframe = $('<iframe src="javascript:false;" name="' + id + '" />').attr('id', id).css('display', 'none').appendTo($(d.body));

			return iframe.get(0);
		}

		/**
		 * Создает форму, которая будет сабмититься в айфрейме.
		 */
		function createForm(iframe) {
			var newSettings = settings;

			// method, enctype must be specified here because changing this attr on the fly is not allowed in IE 6/7
			var form = $('<form method="post" enctype="multipart/form-data"></form>')
				.css('display', 'none')
				.attr({
					action: newSettings.action,
					target: iframe.name
				})
				.appendTo($(d.body));

			// Create hidden input element for each data key
			for (var prop in newSettings.data) {
				var el = $(d.createElement('input'))
					.attr({
						type: 'hidden',
						name: prop,
						value: newSettings.data[prop]
					})
					.appendTo(form);
			}

			return form.get(0);
		}

		/**
		 * Загружает файл без перезагруза страницы.
		 */
		function submit() {
			// Файл не выбран.
			if ('' === input.value) return;

			var newSettings = settings;
			// Берем имя файла из инпута.
			var file = fileFromPath(input.value);

			// Выполняем событие пользователя.
			if (false != newSettings.onSubmit.call(that, file, getExt(file))) {
				// Create new iframe for this submission
				var iframe = createIframe();

				// Do not submit if user function returns false
				var form = createForm(iframe);
				form.appendChild(input);
				form.submit();

				d.body.removeChild(form);
				form = null;
				input = null;

				// create new input
				createInput();

				var toDeleteFlag = false;

				$(iframe).load(function(e) {

					if (// For Safari
						iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
						// For FF, IE
						iframe.src == "javascript:'<html></html>';") {

						// First time around, do not delete.
						if (toDeleteFlag) {
							// Fix busy state in FF3
							setTimeout(function() {
								d.body.removeChild(iframe);
							}, 0);
						}

						return;
					}

					var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;

					// fixing Opera 9.26
					// Opera fires load event multiple times. Even when the DOM is not ready yet this fix should not affect other browsers
					if ('complete' != doc.readyState && doc.readyState) return;

					// fixing Opera 9.64
					// In Opera 9.64 event was fired second time when body.innerHTML changed from false to server response approx. after 1 sec
					if (doc.body && 'false' == doc.body.innerHTML) return;

					var response;

					if (doc.XMLDocument) {
						// response is a xml document IE property
						response = doc.XMLDocument;
					} else if (doc.body) {
						// response is html document or plain text
						response = doc.body.innerHTML;

						if (newSettings.responseType && 'json' == newSettings.responseType.toLowerCase()) {
							// If the document was sent as 'application/javascript' or 'text/javascript', then the browser
							// wraps the text in a <pre>  tag and performs html encoding on the contents.  In this case,
							// we need to pull the original text content from the text node's nodeValue property
							// to retrieve the unmangled content.
							// Note that IE6 only understands text/html
							if (doc.body.firstChild && 'PRE' == doc.body.firstChild.nodeName.toUpperCase()) {
								response = doc.body.firstChild.firstChild.nodeValue;
							}

							response = response ? window["eval"]("(" + response + ")") : {};
						}
					} else {
						// response is a xml document
						response = doc;
					}

					newSettings.onComplete.call(that, file, response);
					// Reload blank page, so that reloading main page does not re-submit the post. Also, remember to delete the frame
					toDeleteFlag = true;
					// Fix IE mixed content issue
					iframe.src = "javascript:'<html></html>';";
				});

			} else {
				// clear input to allow user to select same file. Doesn't work in IE6 input.value = '';
				d.body.removeChild(input);
				input = null;
				// create new input
				createInput();
			}
		}

		return that;
	}

	return function(button, options) {
		new AjaxUpload(button, options);
	};
})();
