var qq = qq || {};
(function($) {
	// jQuery Aufruf
	$.fn.fileuploader = function(options) {
		if (arguments.length > 0 && arguments[0].constructor == String) {
			var action = arguments[0].toString();
			var params = [];
	
			for ( var i = 1; i < arguments.length; i++ )
				params[i - 1] = arguments[i];
			
			if (action in qq.FileUploader) {
				
				var obj = $.data(this[0], 'fileuploader');
				if (obj)
					return obj[action].apply(obj, params);
					
				return false;
			}
			else
				return this;
		}
			
		// Sich selbst zurückgeben
		return this.each(function() {
			qq.FileUploader(this, options);
		});
	};
	
	/**
	FileUploaderBasic
	*/
	qq.FileUploaderBasic = function(original, options) {
		var element = this.element = $(original);
			
		this.options = $.extend({
			// Ajax
			debug: false,
			action: 'upload.php', // Ziel
			params: {}, // Parameter
			
			// Upload
			multiple: true,
			maxConnections: 1,
			
			// Validation
			allowedExtensions: [],
			sizeLimit: 0,
			minSizeLimit: 0,
			
			// Events
			onSubmit: function(id, fileName) {},
			onProgress: function(id, fileName, loaded, total) {},
			onComplete: function(id, fileName, responseJSON) {},
			onCancel: function(id, fileName) {},
			
			// Nachrichten
			messages: {
				typeError: "{file} has invalid extension. Only {extensions} are allowed.",
				sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
				minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
				emptyError: "{file} is empty, please select files again without it.",
				onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
			},
			showMessage: function(message) {
				alert(message);
			}
		}, options);
		
		// number of files being uploaded
		this.filesInProgress = 0;
		this.handler = this.createUploadHandler();
		
		// Uploadbutton erstellen
		this.button = this.createUploadButton();
		
		// Unload Event
		this.preventLeaveInProgress();
	};
	
	// Private Functions
	qq.FileUploaderBasic.prototype = {
		// setParams
		setParams: function(params) {
			this.options.params = params;
		},
		
		// getInProgress
		getInProgress: function() {
			return this.filesInProgress;
		},
		
		// createUploadButton
		createUploadButton: function() {
			var self = this;
			var element = this.element;
			
			return new qq.UploadButton(element,{
				multiple: this.options.multiple && qq.UploadHandlerXhr.isSupported(),
				onChange: function(input) {
					self.onInputChange(input);
				}
			});
		},
		
		// createUploadHandler
		createUploadHandler: function() {
			var self = this;
			var handlerClass;
		
			if (qq.UploadHandlerXhr.isSupported())
				handlerClass = 'UploadHandlerXhr';
			else
				handlerClass = 'UploadHandlerForm';

			var handler = new qq[handlerClass]({
				debug: this.options.debug,
				action: this.options.action,
				maxConnections: this.options.maxConnections,
				onProgress: function(id, fileName, loaded, total){
					self.onProgress(id, fileName, loaded, total);
					self.options.onProgress(id, fileName, loaded, total);
				},
				onComplete: function(id, fileName, result){
					self.onComplete(id, fileName, result);
					self.options.onComplete(id, fileName, result, self.filesInProgress);
				},
				onCancel: function(id, fileName){
					self.onCancel(id, fileName);
					self.options.onCancel(id, fileName);
				}
			});
			
			this.handler = handler;
			return handler;
		},
		
		// preventLeaveInProgress
		preventLeaveInProgress: function(){
			var self = this;
			
			$(window).bind('beforeunload', function(e) {
				if (!self.filesInProgress) 
					return;
				
				var e = e || window.event;
				
				// for ie, ff
				e.returnValue = self.options.messages.onLeave;
				
				// for webkit
				return self.options.messages.onLeave;
			});
		},
		
		// onSubmit
		onSubmit: function(id, fileName){
			this.filesInProgress++;
		},
		
		// onProgress
		onProgress: function(id, fileName, loaded, total) {
		
		},
		
		// onComplete
		onComplete: function(id, fileName, result){
			this.filesInProgress--;
			if (result.error)
				this.options.showMessage(result.error);
		},
		
		// onCancel
		onCancel: function(id, fileName){
			this.filesInProgress--;
		},
		
		// onInputChange
		onInputChange: function(input){
			if (this.handler instanceof qq.UploadHandlerXhr)
				this.uploadFileList(input.files);
			else if (this.validateFile(input))
				this.uploadFile(input);
			
			this.button.reset();
		},
		
		// uploadFileList
		uploadFileList: function(files){
			for (var i = 0; i < files.length; i++) {
				if (!this.validateFile(files[i]))
					return;
			}
			
			for (var i = 0; i < files.length; i++) {
				this.uploadFile(files[i]);
			}
		},
		
		// uploadFile
		uploadFile: function(fileContainer) {
			var id = this.handler.add(fileContainer);
			var fileName = this.handler.getName(id);
			
			if (this.options.onSubmit(id, fileName) !== false){
				this.onSubmit(id, fileName);
				this.handler.upload(id, this.options.params);
			}
		},
		
		// validateFile
		validateFile: function(file) {
			var name, size;
			
			if (file.value){
				// it is a file input
				// get input value and remove path to normalize
				name = file.value.replace(/.*(\/|\\)/, "");
			}
			else {
				// fix missing properties in Safari
				name = file.fileName != null ? file.fileName : file.name;
				size = file.fileSize != null ? file.fileSize : file.size;
			}
			
			if (!this.isAllowedExtension(name)){
				this.error('typeError', name);
				return false;
			}
			else if (size === 0){
				this.error('emptyError', name);
				return false;
				
			}
			else if (size && this.options.sizeLimit && size > this.options.sizeLimit){
				this.error('sizeError', name);
				return false;
				
			}
			else if (size && size < this.options.minSizeLimit){
				this.error('minSizeError', name);
				return false;
			}
			
			return true;
		},
		
		// error
		error: function(code, fileName) {
			var message = this.options.messages[code];
			function r(name, replacement) {
				message = message.replace(name, replacement);
			}
			
			r('{file}', this.formatFileName(fileName));
			r('{extensions}', this.options.allowedExtensions.join(', '));
			r('{sizeLimit}', this.formatSize(this.options.sizeLimit));
			r('{minSizeLimit}', this.formatSize(this.options.minSizeLimit));
			
			this.options.showMessage(message);
		},
		
		// formatFileName
		formatFileName: function(name) {
			if (name.length > 33)
				name = name.slice(0, 19) + '...' + name.slice(-13);
			
			return name;
		},
		
		// isAllowedExtension
		isAllowedExtension: function(fileName) {
			var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
			this.options.allowedExtensions = [];
			
			var allowed = this.options.allowedExtensions;
			
			if (!allowed.length)
				return true;
			
			for (var i = 0; i < allowed.length; i++) {
				if (allowed[i].toLowerCase() == ext)
					return true;
			}
			
			return false;
		},
		
		// formatSize
		formatSize: function(bytes) {
			var i = -1;    
			do {
				bytes = bytes / 1024;
				i++;
			} while (bytes > 99);
			
			return Math.max(bytes, 0.1).toFixed(1) + ['KB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
		}
	},
	
	
	/**
	FileUploader
	*/
	qq.FileUploader = function(element, options){
		return this instanceof qq.FileUploader ? this.init(element, options) : new qq.FileUploader(element, options);
	};
	
	// Public Functions
    $.extend(qq.FileUploader, {
		setParams: null,
		getInProgress: null,
		getFiles: null,
		enable: null,
		disable: null
	});
	
	// Inherit from FileUploaderBasic
	$.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
	
	// Private Functions
	$.extend(qq.FileUploader.prototype, {
		options: null,
		
		classes: null,
		
		element: null, // Button
		dropzone: null, // dropzoneElement
		list: null, // Listenelement
		
		files: {},
		
		active: true,
				
		init: function(original, options) {
			var self = this;
			
			options = $.extend({
				listElement: null, // Container für Fileliste
				dropzoneElement: null, // Dropzone
				
				dropzoneText: 'Datei her ziehen für Upload',
				
				classes: {
					// used to get elements from templates
					button: 'qq-upload-button',
					drop: 'qq-upload-drop-area',
					dropFix: 'qq-upload-drop-fix',
					dropActive: 'qq-upload-drop-area-active',
					list: 'qq-upload-list',
								
					file: 'qq-upload-file',
					spinner: 'qq-upload-spinner',
					size: 'qq-upload-size',
					cancel: 'qq-upload-cancel',
					remove: 'qq-upload-remove',
		
					// added to list item when upload completes
					// used in css to hide progress spinner
					success: 'qq-upload-success',
					fail: 'qq-upload-fail'
				},
				iframeFix: true
			}, options);
			
			this.options = options;
			
			this.classes = this.options.classes;
			
			this.files = {};
			
			qq.FileUploaderBasic.apply(this, arguments);
			
			// Speichere Instanz im Node
			$.data(original, 'fileuploader', this);
			
			var element = this.element = $(original);
			
			// Listencontainer
			var list = this.list = this.options.listElement;
			if (this.list)
				this.list.addClass(this.classes.list);
			
			// Dropzone
			var dropzone = this.dropzone = this.options.dropzoneElement;
			
			if (this.dropzone)
				this.setupDragDrop();
			
			return this;
		},
		
		// getFiles
		getFiles: function() {
			return this.files;
		},
		
		// Enable (Dropzone)
		enable: function() {
			this.active = true;
			if (this.dz)
				this.dz.active = true;
		},
		
		// Disable (Dropzone)
		disable: function() {
			this.active = false;
			if (this.dz)
				this.dz.active = false;
		},
		
		// setupDragDrop
		setupDragDrop: function() {
			var self = this;
			var dropzone = this.dropzone;
			
			var dz = this.dz = new qq.UploadDropZone(dropzone, {
				onEnter: function(e){
					dropzone.find('.'+self.classes.drop).addClass(self.classes.dropActive);
					e.stopPropagation();
				},
				onLeave: function(e){
					e.stopPropagation();
				},
				onLeaveNotDescendants: function(e){
					dropzone.find('.'+self.classes.drop).removeClass(self.classes.dropActive);
				},
				onDrop: function(e){
					dropzone.hide().next('div.'+self.classes.dropFix).hide();
					dropzone.find('.'+self.classes.drop).removeClass(self.classes.dropActive);
					self.uploadFileList(e.originalEvent.dataTransfer.files);
				},
				iframeFix: this.options.iframeFix,
				classIframeFix: this.classes.dropFix,
				classDrop: this.classes.drop,
				dropzoneText: this.options.dropzoneText
			});
			
			$(document).bind('dragenter', function(e) {
				if (!dz.isValidFileDrag(e))
					return;
				
				dropzone.show();
				if (dz.iframeFix)
					dz.iframeFix.show();
					
				if ($.isFunction(self.options.onDragEnter))
					self.options.onDragEnter(e);
			});
			
			$(document).bind('dragleave', function(e) {
				if (!dz.isValidFileDrag(e))
					return;
				
				var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
				
				// only fire when leaving document out
				if (!relatedTarget || relatedTarget.nodeName == 'HTML') {
					dropzone.hide();
					if (dz.iframeFix)
						dz.iframeFix.hide();
				}
				
				//self.options.onDragLeave(e);
			});
		},
		
		// onSubmit
		onSubmit: function(id, fileName) {
			qq.FileUploaderBasic.prototype.onSubmit.apply(this, arguments);
			this.addToList(id, fileName);
		},
		
		// onProgress
		onProgress: function(id, fileName, loaded, total) {
			qq.FileUploaderBasic.prototype.onProgress.apply(this, arguments);
			
			if (!this.list)
				return;
			
			var item = this.getItemByFileId(id);
			
			var size = item.find('.'+this.classes.size);
			size.show();
			
			var text;
			if (loaded != total)
				text = Math.round(loaded / total * 100) + '% from ' + this.formatSize(total);
			else
				text = this.formatSize(total);
					
			size.text(text);
		},
		
		// onComplete
		onComplete: function(id, fileName, result){
			qq.FileUploaderBasic.prototype.onComplete.apply(this, arguments);
			
			if (!this.list)
				return;
				
			// mark completed
			var item = this.getItemByFileId(id);
			
			item.find('.'+this.classes.cancel).remove();
			item.find('.'+this.classes.spinner).remove();
			
			var size = item.find('.'+this.classes.size);
			var text = this.formatSize(result.size);
			size.text(text).show();
			
			var remove = item.find('.'+this.classes.remove);
			remove.show();
			
			if (result.success) {
				item.addClass(this.classes.success);
				this.files[id] = {
					filename: result.filename,
					newfilename: result.newfilename,
					size: result.size,
					mimetype: result.mimetype
				};
			}
			else
				item.addClass(this.classes.fail);
		},
		
		// addToList
		addToList: function(id, fileName) {
			var self = this;
			
			if (!this.list)
				return;
			
			var item = $('<li/>')
				.attr('data-id', id)
				.click(function() {
					self.list.find('li').removeClass('focused');
					$(this).addClass('focused');
				})
				.disableTextSelect();
			
			var fileElement = $('<span/>')
				.addClass(this.classes.file)
				.appendTo(item)
				.text(this.formatFileName(fileName));
				
			var size = $('<span/>')
				.addClass(this.classes.size)
				.appendTo(item)
				.hide();
				
			var spinner = $('<span/>')
				.addClass(this.classes.spinner)
				.appendTo(item);
				
			var cancel = $('<a href="#"/>')
				.addClass(this.classes.cancel)
				.appendTo(item)
				.text('Abbrechen')
				.click(function(e) {
					e.preventDefault();
					self.handler.cancel($(this).parent('li').attr('data-id'));
					item.remove();
				});
			
			var remove = $('<a href="#"/>')
				.addClass(this.classes.remove)
				.appendTo(item)
				.hide()
				.click(function(e) {
					e.preventDefault();
					var id = $(this).parent('li').attr('data-id');
					delete self.files[id];
					item.remove();
				});
			
			this.list.append(item);
		},
		
		// getItemByFileId
		getItemByFileId: function(id) {
			if (!this.list)
				return;
				
			return this.list.find('li[data-id='+id+']');
		}
	});
	
	/**
	UploadDropZone
	*/
	qq.UploadDropZone = function(element, options){
		options = $.extend({
			onEnter: function(e) {}, // Enter
			onDragEnter: function(e) {}, // Enter Document
			onDragLeave: function(e) {}, // Leave Document
			// is not fired when leaving element by hovering descendants
			onLeaveNotDescendants: function(e) {},
			onDrop: function(e) {},
			iframeFix: false,
			classIframeFix: 'qq-upload-drop-fix',
			classDrop: 'qq-upload-drop-area',
			dropzoneText: 'Dateien herziehen'
		}, options);
		this.options = options;
		
		this.element = element;
		
		this.active = true;
		
		this.dropzone = $('<div/>')
			.addClass(this.options.classDrop)
			.append($('<span/>')
				.text(this.options.dropzoneText)
			);
		
		this.element
			.hide()
			.css('position', 'relative')
			.append(this.dropzone);

		this.iframeFix = null;
		if (this.options.iframeFix) {
			this.iframeFix = $('<div/>')
				.addClass(this.options.classIframeFix)
				.hide();
			this.element.after(this.iframeFix);
		}
		
		this.disableDropOutside();
		this.attachEvents();
	};
		
	// Private Functions
	qq.UploadDropZone.prototype = {
		// disableDropOutside
		disableDropOutside: function(e) {
			// run only once for all instances
			if (!qq.UploadDropZone.dropOutsideDisabled) {
				$(document).bind('dragover', function(e){
					if (e.originalEvent.dataTransfer) {
						e.originalEvent.dataTransfer.dropEffect = 'none';
						e.preventDefault();
					}
				});
				
				qq.UploadDropZone.dropOutsideDisabled = true;
			}
		},
		
		// attachEvents
		attachEvents: function() {
			var self = this;
			  
			this.dropzone.bind('dragover', function(e) {
				if (!self.isValidFileDrag(e))
					return;
				
				e = e.originalEvent;
				
				var effect = e.dataTransfer.effectAllowed;
				if (effect == 'move' || effect == 'linkMove')
					e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
				else
					e.dataTransfer.dropEffect = 'copy'; // for Chrome
				
				e.dataTransfer.dropEffect = 'move';
				
				e.stopPropagation();
				e.preventDefault();
			});
			
			this.dropzone.bind('dragenter', function(e){
				if (!self.isValidFileDrag(e))
					return;
					
				self.options.onEnter(e);
			});
			
			this.dropzone.bind('dragleave', function(e){
				if (!self.isValidFileDrag(e))
					return;
				
				self.options.onLeave(e);
				
				var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
				// do not fire when moving a mouse over a descendant
				if (qq.contains(this, relatedTarget))
					return;
				
				self.options.onLeaveNotDescendants(e);
			});
			
			this.dropzone.bind('drop', function(e){
				if (!self.isValidFileDrag(e))
					return;
				
				e.preventDefault();
				self.options.onDrop(e);
			});
		},
		
		// isValidFileDrag
		isValidFileDrag: function(e){
			if (!this.active)
				return false;
			var dt = e.originalEvent.dataTransfer;
			// dt.effectAllowed is none in Safari 5
			// dt.types.contains check is for firefox
			return dt && dt.effectAllowed != 'none' && (dt.files || (!$.browser.webkit && dt.types.contains && dt.types.contains('Files')));
		}
	};
	
	
	/**
	UploadButton
	*/
	qq.UploadButton = function(element, options){
		this.options = $.extend({
			multiple: false,
			// name attribute of file input
			name: 'file',
			onChange: function(input) {},
			
			hoverClass: 'qq-upload-button-hover',
			focusClass: 'qq-upload-button-focus'
		}, options);
		
		this.element = element;
		
		this.element.css({
			position: 'relative',
			overflow: 'hidden',
			// Make sure browse button is in the right side
			// in Internet Explorer
			direction: 'ltr'
		});
		
		this.input = this.createInput();
	};
		
	// Private Functions
	qq.UploadButton.prototype = {
		
		// returns file input element
		getInput: function(){
			return this.input[0];
		},
		
		// cleans/recreates the file input
		reset: function(){
			this.element.children('input').remove();
			
			this.element.removeClass(this.options.focusClass);
			
			this.createInput();
		},
		
		// createInput
		createInput: function(){
			var input = $('<input/>')
				.attr('type', 'file')
				.attr('name', this.options.name);
			
			if (this.options.multiple)
				input.attr('multiple', 'multiple');
					
			input.css({
				position: 'absolute',
				// in Opera only 'browse' button
				// is clickable and it is located at
				// the right side of the input
				right: 0,
				top: 0,
				fontFamily: 'Arial',
				// 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
				fontSize: '118px',
				margin: 0,
				padding: 0,
				cursor: 'default',
				opacity: 0
			});
			
			this.element.append(input);
	
			var self = this;
			
			input.bind('change', function(){
				self.options.onChange(this);
			});
			/*
			input.bind('mouseover', function(){
				self.element.addClass(self.options.hoverClass);
			});
			
			input.bind('mouseout', function(){
				self.element.removeClass(self.options.hoverClass);
			});
			
			input.bind('focus', function(){
				self.element.addClass(self.options.focusClass);
			});
			
			input.bind('blur', function(){
				self.element.removeClass(self.options.focusClass);
			});
			*/
			
			// IE and Opera, unfortunately have 2 tab stops on file input
			// which is unacceptable in our case, disable keyboard access
			if (window.attachEvent){
				// it is IE or Opera
				input.attr('tabIndex', '-1');
			}
			
			this.input = input;
			return input;
		}
	};
	
	qq.indexOf = function(arr, elt, from) {
		if (arr.indexOf)
			return arr.indexOf(elt, from);
		
		from = from || 0;
		var len = arr.length;
		
		if (from < 0)
			from += len;
		
		for (; from < len; from++) {
			if (from in arr && arr[from] === elt)
				return from;
		}
		return -1;
	};
	
	qq.contains = function(parent, descendant){
		// compareposition returns false in this case
		if (parent == descendant)
			return true;
		
		if (parent.contains)
			return parent.contains(descendant);
		else {
			if (!descendant)
				return false;
			return !!(descendant.compareDocumentPosition(parent) & 8);
		}
	};

	/**
	UploadHandlerAbstract
	*/
	qq.UploadHandlerAbstract = function(options){
		this.options = $.extend({
			debug: false,
			action: '/upload.php',
			// maximum number of concurrent uploads
			maxConnections: 999,
			onProgress: function(id, fileName, loaded, total) {},
			onComplete: function(id, fileName, response) {},
			onCancel: function(id, fileName) {}
		}, options);
		
		this.queue = [];
		// params for files in queue
		this.params = [];
	};
		
	// Private Functions
	qq.UploadHandlerAbstract.prototype = {
		
		// log
		log: function(str) {
			if (this.options.debug && window.console)
				console.log('[uploader] ' + str);
		},
		
		// Adds file or file input to the queue
		add: function(file) {
			
		},
		
		// Sends the file identified by id and additional query params to the server
		upload: function(id, params){
			var len = this.queue.push(id);
	
			var copy = {};
			$.extend(copy, params);
			this.params[id] = copy;
					
			// if too many active uploads, wait...
			if (len <= this.options.maxConnections)
				this._upload(id, this.params[id]);
		},
		
		// Cancels file upload by id
		cancel: function(id) {
			this._cancel(id);
			this._dequeue(id);
		},
		
		// Cancells all uploads
		cancelAll: function() {
			for (var i = 0; i < this.queue.length; i++)
				this._cancel(this.queue[i]);
			
			this.queue = [];
		},
		
		// Returns name of the file identified by id
		getName: function(id) {
		
		},
		
		// Returns size of the file identified by id
		getSize: function(id) {
		
		},
		
		// Returns id of files being uploaded or waiting for their turn
		getQueue: function(){
			return this.queue;
		},
		
		// Actual upload method
		_upload: function(id) {
		
		},
		
		// Actual cancel method
		_cancel: function(id) {
		
		},
		
		// Removes element from queue, starts upload of next
		_dequeue: function(id){
			var i = qq.indexOf(this.queue, id);
			this.queue.splice(i, 1);
					
			var max = this.options.maxConnections;
			
			if (this.queue.length >= max){
				var nextId = this.queue[max-1];
				this._upload(nextId, this.params[nextId]);
			}
		}
	};
	
	/**
	UploadHandlerXhr
	*/
	qq.UploadHandlerXhr = function(options){
		qq.UploadHandlerAbstract.apply(this, arguments);
		
		this.files = [];
		this.xhrs = [];
		
		// current loaded size in bytes for each file
		this.loaded = [];
	};
	
	// static method
	qq.UploadHandlerXhr.isSupported = function(){
		var input = document.createElement('input');
		input.type = 'file';
		return ('multiple' in input && typeof File != "undefined" && typeof (new XMLHttpRequest()).upload != "undefined");
	};
	
	// @inherits qq.UploadHandlerAbstract
	$.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype);
		
	// Private Functions
	$.extend(qq.UploadHandlerXhr.prototype, {
		// Adds file to the queue Returns id to use with upload, cancel
		add: function(file) {
			if (!(file instanceof File))
				throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
					
			return this.files.push(file) - 1;
		},
		
		// getName
		getName: function(id){
			var file = this.files[id];
			// fix missing name in Safari 4
			return file.fileName != null ? file.fileName : file.name;
		},
		
		// getSize
		getSize: function(id){
			var file = this.files[id];
			return file.fileSize != null ? file.fileSize : file.size;
		},
		
		// Returns uploaded bytes for file identified by id
		getLoaded: function(id){
			return this.loaded[id] || 0;
		},
		
		// Sends the file identified by id and additional query params to the server
		// @param {Object} params name-value string pairs
		_upload: function(id, params){
			var file = this.files[id];
			var name = this.getName(id);
			var size = this.getSize(id);
					
			this.loaded[id] = 0;
									
			var xhr = this.xhrs[id] = new XMLHttpRequest();
			var self = this;
											
			xhr.upload.onprogress = function(e){
				if (e.lengthComputable){
					self.loaded[id] = e.loaded;
					self.options.onProgress(id, name, e.loaded, e.total);
				}
			};
	
			xhr.onreadystatechange = function(){
				if (xhr.readyState == 4)
					self.onComplete(id, xhr);
			};
	
			// build query string
			params = params || {};
			params['qqfile'] = name;
			var queryString = this.options.action + '?' + $.param(params);
	
			xhr.open('POST', queryString, true);
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
			xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
			xhr.setRequestHeader("Content-Type", "application/octet-stream");
			xhr.send(file);
		},
		
		// onComplete
		onComplete: function(id, xhr){
			// the request was aborted/cancelled
			if (!this.files[id]) return;
			
			var name = this.getName(id);
			var size = this.getSize(id);
				
			this.options.onProgress(id, name, size, size);
					
			if (xhr.status == 200){
				this.log("xhr - server response received");
				this.log("responseText = " + xhr.responseText);
							
				var response;
						
				try {
					response = eval("(" + xhr.responseText + ")");
				} catch(err){
					response = {};
				}
				
				this.options.onComplete(id, name, response);
							
			} else {
				this.options.onComplete(id, name, {});
			}
					
			this.files[id] = null;
			this.xhrs[id] = null;
			this._dequeue(id);
		},
		
		_cancel: function(id){
			this.options.onCancel(id, this.getName(id));
			
			this.files[id] = null;
			
			if (this.xhrs[id]) {
				this.xhrs[id].abort();
				this.xhrs[id] = null;
			}
		}
	});
	
	/**
	UploadHandlerForm
	*/
	qq.UploadHandlerForm = function(options){
		qq.UploadHandlerAbstract.apply(this, arguments);
		
		this.inputs = [];
	};
	
	qq.id = 0;
	
	// @inherits qq.UploadHandlerAbstract
	$.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
	
	// Private Functions
	$.extend(qq.UploadHandlerForm.prototype, {
		// add
		add: function(fileInput){
			fileInput = $(fileInput);
			fileInput.attr('name', 'qqfile');
			qq.id++;
			var id = 'qq-upload-handler-iframe' + qq.id;
			
			this.inputs[id] = fileInput[0];
			
			// remove file input from DOM
			if (fileInput.parent().length > 0)
				fileInput.remove();
					
			return id;
		},
		
		// getName
		getName: function(id){
			// get input value and remove path to normalize
			return this.inputs[id].value.replace(/.*(\/|\\)/, "");
		},
		
		// _cancel
		_cancel: function(id){
			this.options.onCancel(id, this.getName(id));
			
			delete this.inputs[id];
	
			var iframe = document.getElementById(id);
			if (iframe){
				// to cancel request set src to something else
				// we use src="javascript:false;" because it doesn't
				// trigger ie6 prompt on https
				$(iframe).attr('src', 'javascript:false;').remove();
			}
		},
		
		// _upload
		_upload: function(id, params){
			var input = this.inputs[id];
			
			if (input.length == 0)
				throw new Error('file with passed id was not added, or already uploaded or cancelled');
	
			var fileName = this.getName(id);
					
			var iframe = this._createIframe(id);
			var form = this._createForm(iframe, params);
			form.appendChild(input);
	
			var self = this;
			
			this._attachLoadEvent(iframe, function(){
				self.log('iframe loaded');
				
				var response = self._getIframeContentJSON(iframe);
	
				self.options.onComplete(id, fileName, response);
				self._dequeue(id);
				
				delete self.inputs[id];
				// timeout added to fix busy state in FF3.6
				setTimeout(function(){
					$(iframe).remove();
				}, 1);
			});
	
			form.submit();
			$(form).remove();
			
			return id;
		},
		
		// _attachLoadEvent
		_attachLoadEvent: function(iframe, callback){
			$(iframe).bind('load', function() {
				// when we remove iframe from dom the request stops, but in IE load event fires
				if ($(this).length == 0)
					return;
				
				// fixing Opera 10.53
				if (iframe.contentDocument &&
					iframe.contentDocument.body &&
					iframe.contentDocument.body.innerHTML == "false"){
					// In Opera event is fired second time when body.innerHTML changed from false to server response approx. after 1 sec when we upload file with iframe
					return;
				}
				
				callback();
			});
		},
		
		// Returns json object received by iframe from server
		_getIframeContentJSON: function(iframe){
			var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document;
			var response = jQuery.parseJSON(doc.body.innerHTML);
			
			return response;
		},
		
		// Creates iframe with unique name
		_createIframe: function(id){
			var iframe = $('<iframe src="javascript:false;" name="' + id + '" />')	
				.attr('id', id)
				.hide()
				.appendTo($('body'));
	
			return iframe[0];
		},
		
		// Creates form, that will be submitted to iframe
		_createForm: function(iframe, params){
			params.iframeencoding = true;
			var queryString = this.options.action + '?' + $.param(params);
			
			var form = $('<form method="post" enctype="multipart/form-data"></form>')
				.attr('action', queryString)
				.attr('target', iframe.name)
				.hide()
				.appendTo($('body'));
	
			return form[0];
		}
	});
})(jQuery);
