/**
* 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.2 (19.05.2009)
*/

/**
* Changes from the previous version:
* 1. Input is cleared after submit is canceled to allow user to select same file
* 2. Fixed problem with FF3 when used on page with smaller font size
*
* 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(self._button).parents('.ui-dialog-content');
		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 (iframe.src == "about:blank"){
					// 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
				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 == 'json'){
						response = window["eval"]("(" + response + ")");
					}
				} else {
					// response is a xml document
					var response = doc;
				}

				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;
				iframe.src = "about:blank"; //load event fired
			});

		} 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;
	}
};
})();