/**
 * 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/
 */

(function() {

	var d = document, w = window;

	/**
	* Get element by id
	*/
	function get(element) {
		if (typeof element == "string")
			element = d.getElementById(element);
		return element;
	}

	/**
	* Attaches event to a dom element
	*/
	function addEvent(el, type, fn) {
		if (w.addEventListener) {
			el.addEventListener(type, fn, false);
		} else if (w.attachEvent) {
			var f = function() {
				fn.call(el, w.event);
			};
			el.attachEvent('on' + type, f)
		}
	}


	/**
	* Creates and returns element from html chunk
	*/
	var toElement = function() {
		var div = d.createElement('div');
		return function(html) {
			div.innerHTML = html;
			var el = div.childNodes[0];
			div.removeChild(el);
			return el;
		}
	} ();

	function hasClass(ele, cls) {
		return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
	}
	function addClass(ele, cls) {
		if (!hasClass(ele, cls)) ele.className += " " + cls;
	}
	function removeClass(ele, cls) {
		var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
		ele.className = ele.className.replace(reg, ' ');
	}

	// getOffset function copied from jQuery lib (http://jquery.com/)
	if (document.documentElement["getBoundingClientRect"]) {
		// Get Offset using getBoundingClientRect
		// http://ejohn.org/blog/getboundingclientrect-is-awesome/
		var getOffset = function(el) {
			var box = el.getBoundingClientRect(),
		doc = el.ownerDocument,
		body = doc.body,
		docElem = doc.documentElement,

			// for ie 
		clientTop = docElem.clientTop || body.clientTop || 0,
		clientLeft = docElem.clientLeft || body.clientLeft || 0,

			// In Internet Explorer 7 getBoundingClientRect property is treated as physical,
			// while others are logical. Make all logical, like in IE8.


		zoom = 1;
			if (body.getBoundingClientRect) {
				var bound = body.getBoundingClientRect();
				zoom = (bound.right - bound.left) / body.clientWidth;
			}
			if (zoom > 1) {
				clientTop = 0;
				clientLeft = 0;
			}
			var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop,
		left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;

			return {
				top: top,
				left: left
			};
		}

	} else {
		// Get offset adding all offsets 
		var getOffset = function(el) {
			if (w.jQuery) {
				return jQuery(el).offset();
			}

			var top = 0, left = 0;
			do {
				top += el.offsetTop || 0;
				left += el.offsetLeft || 0;
			}
			while (el = el.offsetParent);

			return {
				left: left,
				top: top
			};
		}
	}

	function getBox(el) {
		var left, right, top, bottom;
		var offset = getOffset(el);
		left = offset.left;
		top = offset.top;

		right = left + el.offsetWidth;
		bottom = top + el.offsetHeight;

		return {
			left: left,
			right: right,
			top: top,
			bottom: bottom
		};
	}

	/**
	* 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()) : '';
	}

	// Please use AjaxUpload , Ajax_upload will be removed in the next version
	Ajax_upload = AjaxUpload = function(button, options) {
		if (button.jquery) {
			// jquery object was passed
			button = button[0];
		} else if (typeof button == "string" && /^#.*/.test(button)) {
			button = button.slice(1);
		}
		button = get(button);

		this._input = null;
		this._button = button;
		this._disabled = false;
		this._submitting = false;
		// Variable changes to true if the button was clicked
		// 3 seconds ago (requred to fix Safari on Mac error)
		this._justClicked = false;
		this._parentDialog = d.body;

		if (window.jQuery && jQuery.ui && jQuery.ui.dialog) {
			var parentDialog = jQuery(this._button).parents('.ui-dialog');
			if (parentDialog.length) {
				this._parentDialog = parentDialog[0];
			}
		}

		this._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) { }
		};

		// Merge the users options with our defaults
		for (var i in options) {
			this._settings[i] = options[i];
		}

		this._createInput();
		this._rerouteClicks();
	}

	// assigning methods to our class
	AjaxUpload.prototype = {
		setData: function(data) {
			this._settings.data = data;
		},
		disable: function() {
			this._disabled = true;
		},
		enable: function() {
			this._disabled = false;
		},
		// removes ajaxupload
		destroy: function() {
			if (this._input) {
				if (this._input.parentNode) {
					this._input.parentNode.removeChild(this._input);
				}
				this._input = null;
			}
		},
		/**
		* Creates invisible file input above the button 
		*/
		_createInput: function() {
			var self = this;
			var input = d.createElement("input");
			input.setAttribute('type', 'file');
			input.setAttribute('name', this._settings.name);
			var styles = {
				'position': 'absolute'
			, 'margin': '-5px 0 0 -175px'
			, 'padding': 0
			, 'width': '220px'
			, 'height': '30px'
			, 'fontSize': '14px'
			, 'opacity': 0
			, 'cursor': 'pointer'
			, 'display': 'none'
			, 'zIndex': 2147483583 //Max zIndex supported by Opera 9.0-9.2x 
				// Strange, I expected 2147483647					
			};
			for (var i in styles) {
				input.style[i] = styles[i];
			}

			// Make sure that element opacity exists
			// (IE uses filter instead)
			if (!(input.style.opacity === "0")) {
				input.style.filter = "alpha(opacity=0)";
			}

			this._parentDialog.appendChild(input);

			addEvent(input, 'change', function() {
				// get filename from input
				var file = fileFromPath(this.value);
				if (self._settings.onChange.call(self, file, getExt(file)) == false) {
					return;
				}
				// Submit form when value is changed
				if (self._settings.autoSubmit) {
					self.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
			addEvent(input, 'click', function() {
				self.justClicked = true;
				setTimeout(function() {
					// we will wait 3 seconds for dialog to open
					self.justClicked = false;
				}, 3000);
			});

			this._input = input;
		},
		_rerouteClicks: function() {
			var self = this;

			// IE displays 'access denied' error when using this method
			// other browsers just ignore click()
			// addEvent(this._button, 'click', function(e){
			//   self._input.click();
			// });

			var box, dialogOffset = { top: 0, left: 0 }, over = false;
			addEvent(self._button, 'mouseover', function(e) {
				if (!self._input || over) return;
				over = true;
				box = getBox(self._button);

				if (self._parentDialog != d.body) {
					dialogOffset = getOffset(self._parentDialog);
				}
			});


			// we can't use mouseout on the button,
			// because invisible input is over it
			addEvent(document, 'mousemove', function(e) {
				var input = self._input;
				if (!input || !over) return;

				if (self._disabled) {
					removeClass(self._button, 'hover');
					input.style.display = 'none';
					return;
				}

				var c = getMouseCoords(e);

				if ((c.x >= box.left) && (c.x <= box.right) &&
			(c.y >= box.top) && (c.y <= box.bottom)) {
					input.style.top = c.y - dialogOffset.top + 'px';
					input.style.left = c.x - dialogOffset.left + 'px';
					input.style.display = 'block';
					addClass(self._button, 'hover');
				} else {
					// mouse left the button
					over = false;
					if (!self.justClicked) {
						input.style.display = 'none';
					}
					removeClass(self._button, 'hover');
				}
			});

		},
		/**
		* Creates iframe with unique name
		*/
		_createIframe: function() {
			// 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 = toElement('<iframe src="javascript:false;" name="' + id + '" />');
			iframe.id = id;
			iframe.style.display = 'none';
			d.body.appendChild(iframe);
			return iframe;
		},
		/**
		* Upload file without refreshing the page
		*/
		submit: function() {
			var self = this, settings = this._settings;

			if (this._input.value === '') {
				// there is no file
				return;
			}

			// get filename from input
			var file = fileFromPath(this._input.value);

			// execute user event
			if (!(settings.onSubmit.call(this, file, getExt(file)) == false)) {
				// Create new iframe for this submission
				var iframe = this._createIframe();

				// Do not submit if user function returns false										
				var form = this._createForm(iframe);
				form.appendChild(this._input);

				form.submit();

				d.body.removeChild(form);
				form = null;
				this._input = null;

				// create new input
				this._createInput();

				var toDeleteFlag = false;

				addEvent(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;
					//fbis - added try..catch block to catch 500 errors when a file is too large.
					try {
						doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;

					// fixing Opera 9.26
					if (doc.readyState && doc.readyState != 'complete') {
						// Opera fires load event multiple times
						// Even when the DOM is not ready yet
						// this fix should not affect other browsers
						return;
					}

					// fixing Opera 9.64
					if (doc.body && doc.body.innerHTML == "false") {
						// In Opera 9.64 event was fired second time
						// when body.innerHTML changed from false 
						// to server response approx. after 1 sec
						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 (settings.responseType && settings.responseType.toLowerCase() == 'json') {
							// 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 && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {
								response = doc.body.firstChild.firstChild.nodeValue;
							}
							if (response) {
								response = window["eval"]("(" + response + ")");
							} else {
								response = {};
							}
						}
					} else {
						// response is a xml document
						var response = doc;
					}
				} catch (err) { var response = 'File is too large to upload!'; } //fbis
					settings.onComplete.call(self, 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
				// this._input.value = '';
				d.body.removeChild(this._input);
				this._input = null;

				// create new input
				this._createInput();
			}
		},
		/**
		* Creates form, that will be submitted to iframe
		*/
		_createForm: function(iframe) {
			var settings = this._settings;

			// method, enctype must be specified here
			// because changing this attr on the fly is not allowed in IE 6/7		
			var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
			form.style.display = 'none';
			form.action = settings.action;
			form.target = iframe.name;
			d.body.appendChild(form);

			// Create hidden input element for each data key
			for (var prop in settings.data) {
				var el = d.createElement("input");
				el.type = 'hidden';
				el.name = prop;
				el.value = settings.data[prop];
				form.appendChild(el);
			}
			return form;
		}
	};
})();