/**
 * FileInput.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/file/FileInput', [
	'moxie/core/utils/Basic',
	'moxie/core/utils/Env',
	'moxie/core/utils/Mime',
	'moxie/core/utils/Dom',
	'moxie/core/Exceptions',
	'moxie/core/EventTarget',
	'moxie/core/I18n',
	'moxie/runtime/Runtime',
	'moxie/runtime/RuntimeClient'
], function(Basic, Env, Mime, Dom, x, EventTarget, I18n, Runtime, RuntimeClient) {
	/**
	Provides a convenient way to create cross-browser file-picker. Generates file selection dialog on click,
	converts selected files to _File_ objects, to be used in conjunction with _Image_, preloaded in memory
	with _FileReader_ or uploaded to a server through _XMLHttpRequest_.

	@class moxie/file/FileInput
	@constructor
	@extends EventTarget
	@uses RuntimeClient
	@param {Object|String|DOMElement} options If options is string or node, argument is considered as _browse\_button_.
		@param {String|DOMElement} options.browse_button DOM Element to turn into file picker.
		@param {Array} [options.accept] Array of mime types to accept. By default accepts all.
		@param {Boolean} [options.multiple=false] Enable selection of multiple files.
		@param {Boolean} [options.directory=false] Turn file input into the folder input (cannot be both at the same time).
		@param {String|DOMElement} [options.container] DOM Element to use as a container for file-picker. Defaults to parentNode
		for _browse\_button_.
		@param {Object|String} [options.required_caps] Set of required capabilities, that chosen runtime must support.

	@example

			Browse...

	*/
	var dispatches = [
		/**
		Dispatched when runtime is connected and file-picker is ready to be used.

		@event ready
		@param {Object} event
		*/
		'ready',

		/**
		Dispatched right after [ready](#event_ready) event, and whenever [refresh()](#method_refresh) is invoked.
		Check [corresponding documentation entry](#method_refresh) for more info.

		@event refresh
		@param {Object} event
		*/

		/**
		Dispatched when selection of files in the dialog is complete.

		@event change
		@param {Object} event
		*/
		'change',

		'cancel', // TODO: might be useful

		/**
		Dispatched when mouse cursor enters file-picker area. Can be used to style element
		accordingly.

		@event mouseenter
		@param {Object} event
		*/
		'mouseenter',

		/**
		Dispatched when mouse cursor leaves file-picker area. Can be used to style element
		accordingly.

		@event mouseleave
		@param {Object} event
		*/
		'mouseleave',

		/**
		Dispatched when functional mouse button is pressed on top of file-picker area.

		@event mousedown
		@param {Object} event
		*/
		'mousedown',

		/**
		Dispatched when functional mouse button is released on top of file-picker area.

		@event mouseup
		@param {Object} event
		*/
		'mouseup'
	];

	function FileInput(options) {
		if (MXI_DEBUG) {
			Env.log("Instantiating FileInput...");
		}

		var container, browseButton, defaults;

		// if flat argument passed it should be browse_button id
		if (Basic.inArray(Basic.typeOf(options), ['string', 'node']) !== -1) {
			options = { browse_button : options };
		}

		// this will help us to find proper default container
		browseButton = Dom.get(options.browse_button);
		if (!browseButton) {
			// browse button is required
			throw new x.DOMException(x.DOMException.NOT_FOUND_ERR);
		}

		// figure out the options
		defaults = {
			accept: [{
				title: I18n.translate('All Files'),
				extensions: '*'
			}],
			multiple: false,
			required_caps: false,
			container: browseButton.parentNode || document.body
		};

		options = Basic.extend({}, defaults, options);

		// convert to object representation
		if (typeof(options.required_caps) === 'string') {
			options.required_caps = Runtime.parseCaps(options.required_caps);
		}

		// normalize accept option (could be list of mime types or array of title/extensions pairs)
		if (typeof(options.accept) === 'string') {
			options.accept = Mime.mimes2extList(options.accept);
		}

		container = Dom.get(options.container);
		// make sure we have container
		if (!container) {
			container = document.body;
		}

		// make container relative, if it's not
		if (Dom.getStyle(container, 'position') === 'static') {
			container.style.position = 'relative';
		}

		container = browseButton = null; // IE

		RuntimeClient.call(this);

		Basic.extend(this, {
			/**
			Unique id of the component

			@property uid
			@protected
			@readOnly
			@type {String}
			@default UID
			*/
			uid: Basic.guid('uid_'),

			/**
			Unique id of the connected runtime, if any.

			@property ruid
			@protected
			@type {String}
			*/
			ruid: null,

			/**
			Unique id of the runtime container. Useful to get hold of it for various manipulations.

			@property shimid
			@protected
			@type {String}
			*/
			shimid: null,

			/**
			Array of selected moxie.file.File objects

			@property files
			@type {Array}
			@default null
			*/
			files: null,

			/**
			Initializes the file-picker, connects it to runtime and dispatches event ready when done.

			@method init
			*/
			init: function() {
				var self = this;

				self.bind('RuntimeInit', function(e, runtime) {
					self.ruid = runtime.uid;
					self.shimid = runtime.shimid;

					self.bind("Ready", function() {
						self.trigger("Refresh");
					}, 999);

					// re-position and resize shim container
					self.bind('Refresh', function() {
						var pos, size, browseButton, shimContainer, zIndex;

						browseButton = Dom.get(options.browse_button);
						shimContainer = Dom.get(runtime.shimid); // do not use runtime.getShimContainer(), since it will create container if it doesn't exist

						if (browseButton) {
							pos = Dom.getPos(browseButton, Dom.get(options.container));
							size = Dom.getSize(browseButton);
							zIndex = parseInt(Dom.getStyle(browseButton, 'z-index'), 10) || 0;

							if (shimContainer) {
								Basic.extend(shimContainer.style, {
									top: pos.y + 'px',
									left: pos.x + 'px',
									width: size.w + 'px',
									height: size.h + 'px',
									zIndex: zIndex + 1
								});
							}
						}
						shimContainer = browseButton = null;
					});

					runtime.exec.call(self, 'FileInput', 'init', options);
				});

				// runtime needs: options.required_features, options.runtime_order and options.container
				self.connectRuntime(Basic.extend({}, options, {
					required_caps: {
						select_file: true
					}
				}));
			},

			/**
			 * Get current option value by its name
			 *
			 * @method getOption
			 * @param name
			 * @return {Mixed}
			 */
			getOption: function(name) {
				return options[name];
			},

			/**
			 * Sets a new value for the option specified by name
			 *
			 * @method setOption
			 * @param name
			 * @param value
			 */
			setOption: function(name, value) {
				if (!options.hasOwnProperty(name)) {
					return;
				}

				var oldValue = options[name];

				switch (name) {
					case 'accept':
						if (typeof(value) === 'string') {
							value = Mime.mimes2extList(value);
						}
						break;

					case 'container':
					case 'required_caps':
						throw new x.FileException(x.FileException.NO_MODIFICATION_ALLOWED_ERR);
				}

				options[name] = value;
				this.exec('FileInput', 'setOption', name, value);

				this.trigger('OptionChanged', name, value, oldValue);
			},

			/**
			Disables file-picker element, so that it doesn't react to mouse clicks.

			@method disable
			@param {Boolean} [state=true] Disable component if - true, enable if - false
			*/
			disable: function(state) {
				var runtime = this.getRuntime();
				if (runtime) {
					this.exec('FileInput', 'disable', Basic.typeOf(state) === 'undefined' ? true : state);
				}
			},

			/**
			Reposition and resize dialog trigger to match the position and size of browse_button element.

			@method refresh
			*/
			refresh: function() {
				this.trigger("Refresh");
			},

			/**
			Destroy component.

			@method destroy
			*/
			destroy: function() {
				var runtime = this.getRuntime();
				if (runtime) {
					runtime.exec.call(this, 'FileInput', 'destroy');
					this.disconnectRuntime();
				}

				if (Basic.typeOf(this.files) === 'array') {
					// no sense in leaving associated files behind
					Basic.each(this.files, function(file) {
						file.destroy();
					});
				}
				this.files = null;

				this.unbindAll();
			}
		});

		this.handleEventProps(dispatches);
	}

	FileInput.prototype = EventTarget.instance;

	return FileInput;
});


/**
 * Mime.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
@class moxie/core/utils/Mime
@public
@static
*/

define("moxie/core/utils/Mime", [
	"moxie/core/utils/Basic",
	"moxie/core/I18n"
], function(Basic, I18n) {

	var mimeData = "" +
		"application/msword,doc dot," +
		"application/pdf,pdf," +
		"application/pgp-signature,pgp," +
		"application/postscript,ps ai eps," +
		"application/rtf,rtf," +
		"application/vnd.ms-excel,xls xlb xlt xla," +
		"application/vnd.ms-powerpoint,ppt pps pot ppa," +
		"application/zip,zip," +
		"application/x-shockwave-flash,swf swfl," +
		"application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx," +
		"application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx," +
		"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx," +
		"application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx," +
		"application/vnd.openxmlformats-officedocument.presentationml.template,potx," +
		"application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx," +
		"application/x-javascript,js," +
		"application/json,json," +
		"audio/mpeg,mp3 mpga mpega mp2," +
		"audio/x-wav,wav," +
		"audio/x-m4a,m4a," +
		"audio/ogg,oga ogg," +
		"audio/aiff,aiff aif," +
		"audio/flac,flac," +
		"audio/aac,aac," +
		"audio/ac3,ac3," +
		"audio/x-ms-wma,wma," +
		"image/bmp,bmp," +
		"image/gif,gif," +
		"image/jpeg,jpg jpeg jpe," +
		"image/photoshop,psd," +
		"image/png,png," +
		"image/svg+xml,svg svgz," +
		"image/tiff,tiff tif," +
		"text/plain,asc txt text diff log," +
		"text/html,htm html xhtml," +
		"text/css,css," +
		"text/csv,csv," +
		"text/rtf,rtf," +
		"video/mpeg,mpeg mpg mpe m2v," +
		"video/quicktime,qt mov," +
		"video/mp4,mp4," +
		"video/x-m4v,m4v," +
		"video/x-flv,flv," +
		"video/x-ms-wmv,wmv," +
		"video/avi,avi," +
		"video/webm,webm," +
		"video/3gpp,3gpp 3gp," +
		"video/3gpp2,3g2," +
		"video/vnd.rn-realvideo,rv," +
		"video/ogg,ogv," +
		"video/x-matroska,mkv," +
		"application/vnd.oasis.opendocument.formula-template,otf," +
		"application/octet-stream,exe";

	/**
	 * Map of mimes to extensions
	 *
	 * @property mimes
	 * @type {Object}
	 */
	var mimes = {};

	/**
	 * Map of extensions to mimes
	 *
	 * @property extensions
	 * @type {Object}
	 */
	var extensions = {};

	/**
	* Parses mimeData string into a mimes and extensions lookup maps. String should have the
	* following format:
	*
	* application/msword,doc dot,application/pdf,pdf, ...
	*
	* so mime-type followed by comma and followed by space-separated list of associated extensions,
	* then comma again and then another mime-type, etc.
	*
	* If invoked externally will replace override internal lookup maps with user-provided data.
	*
	* @method addMimeType
	* @param {String} mimeData
	*/
	var addMimeType = function (mimeData) {
		var items = mimeData.split(/,/), i, ii, ext;

		for (i = 0; i < items.length; i += 2) {
			ext = items[i + 1].split(/ /);

			// extension to mime lookup
			for (ii = 0; ii < ext.length; ii++) {
				mimes[ext[ii]] = items[i];
			}
			// mime to extension lookup
			extensions[items[i]] = ext;
		}
	};

	var extList2mimes = function (filters, addMissingExtensions) {
		var ext, i, ii, type, mimes = [];

		// convert extensions to mime types list
		for (i = 0; i < filters.length; i++) {
			ext = filters[i].extensions.toLowerCase().split(/\s*,\s*/);

			for (ii = 0; ii < ext.length; ii++) {

				// if there's an asterisk in the list, then accept attribute is not required
				if (ext[ii] === '*') {
					return [];
				}

				type = mimes[ext[ii]];

				// future browsers should filter by extension, finally
				if (addMissingExtensions && /^\w+$/.test(ext[ii])) {
					mimes.push('.' + ext[ii]);
				} else if (type && Basic.inArray(type, mimes) === -1) {
					mimes.push(type);
				} else if (!type) {
					// if we have no type in our map, then accept all
					return [];
				}
			}
		}
		return mimes;
	};

	var mimes2exts = function(mimes) {
		var exts = [];

		Basic.each(mimes, function(mime) {
			mime = mime.toLowerCase();

			if (mime === '*') {
				exts = [];
				return false;
			}

			// check if this thing looks like mime type
			var m = mime.match(/^(\w+)\/(\*|\w+)$/);
			if (m) {
				if (m[2] === '*') {
					// wildcard mime type detected
					Basic.each(extensions, function(arr, mime) {
						if ((new RegExp('^' + m[1] + '/')).test(mime)) {
							[].push.apply(exts, extensions[mime]);
						}
					});
				} else if (extensions[mime]) {
					[].push.apply(exts, extensions[mime]);
				}
			}
		});
		return exts;
	};

	var mimes2extList = function(mimes) {
		var accept = [], exts = [];

		if (Basic.typeOf(mimes) === 'string') {
			mimes = Basic.trim(mimes).split(/\s*,\s*/);
		}

		exts = mimes2exts(mimes);

		accept.push({
			title: I18n.translate('Files'),
			extensions: exts.length ? exts.join(',') : '*'
		});

		return accept;
	};

	/**
	 * Extract extension from the given filename
	 *
	 * @method getFileExtension
	 * @param {String} fileName
	 * @return {String} File extension
	 */
	var getFileExtension = function(fileName) {
		var matches = fileName && fileName.match(/\.([^.]+)$/);
		if (matches) {
			return matches[1].toLowerCase();
		}
		return '';
	};

	/**
	 * Get file mime-type from it's filename - will try to match the extension
	 * against internal mime-type lookup map
	 *
	 * @method getFileMime
	 * @param {String} fileName
	 * @return File mime-type if found or an empty string if not
	 */
	var getFileMime = function(fileName) {
		return mimes[getFileExtension(fileName)] || '';
	};

	addMimeType(mimeData);

	return {
		mimes: mimes,
		extensions: extensions,
		addMimeType: addMimeType,
		extList2mimes: extList2mimes,
		mimes2exts: mimes2exts,
		mimes2extList: mimes2extList,
		getFileExtension: getFileExtension,
		getFileMime: getFileMime
	}
});


var fs = require('fs');
var utils = require('./utils');
var tools = require('./tools');

function getAvailbleRuntimes(baseDir) {
	var runtimesDir = baseDir + '/runtime';

	// each runtime is represented by a directory inside runtime/ dir
	var files = fs.readdirSync(runtimesDir)
	, runtimes = []
	;
	files.forEach(function(file) {
		if (fs.lstatSync(runtimesDir + '/' + file).isDirectory()) {
			runtimes.push(file);
		}
	});
	return runtimes;
}

function getExtensionPaths(options) {
	var modules = ["file/Blob", "file/FileInput", "file/FileDrop", "file/FileReader", "xhr/XMLHttpRequest"];
	var extensions = [];

	if (options.imageSupport) {
		modules.push("image/Image");
	}

	extensions = getExtensionPaths4(modules, {
		runtimes: options.runtimes,
		baseDir: options.extensionsDir
	});

	// we need to strip of the baseDir for plupload
	var re = new RegExp('^' + options.baseDir.replace(/\//g, '\/') + '\/?');
	return extensions.map(function(ext) {
		return ext.replace(re, '');
	});
}

/**
@param {Array} paths Array of module path that can be extended with runtimes
@param {Object} options
	@param {String} options.baseDir
	@param {String|Array} [options.runtimes=all]
@return {Array} Array of paths to extensions
*/
function getExtensionPaths4(paths, options) {
	var runtimes = options.runtimes;
	var resolvedPaths = [];

	if (runtimes == 'all') {
		runtimes = getAvailbleRuntimes(options.baseDir);
	} else {
		runtimes = (runtimes || 'html5,flash,silverlight,html4').split(/,/);
	}

	if (runtimes.length) {
		runtimes.forEach(function(type) {
			paths.forEach(function(path) {
				path = options.baseDir + '/runtime/' + type + '/' + path.replace(/(^\/|\.js$)/, '') + '.js';
				if (fs.existsSync(path)) {
					resolvedPaths.push(path);
				}
			});
		});
	}

	return resolvedPaths;
}

var addCompat = function(options) {
	var buffer = fs.readFileSync(options.baseDir + '/o.js');

	// add normal
	if (fs.existsSync(options.targetDir + "/moxie.js")) {
		fs.appendFileSync(options.targetDir + "/moxie.js", "\n" + buffer);
	}

	// ... minified
	if (fs.existsSync(options.targetDir + "/moxie.min.js")) {
		fs.appendFileSync(options.targetDir + "/moxie.min.js", tools.uglify(options.baseDir + '/o.js', null, {
			sourceBase: options.baseDir
		}));
	}

	// .. dev/cov
	['dev', 'cov'].forEach(function(suffix) {
		var fileName = "moxie." + suffix + ".js";
		if (fs.existsSync(options.targetDir + "/" + fileName)) {
			fs.appendFileSync(options.targetDir + "/" + fileName,
				"\n\n(function() {\n" +
				"	var baseDir = '';\n" +
				"	var scripts = document.getElementsByTagName('script');\n" +
				"	for (var i = 0; i < scripts.length; i++) {\n" +
				"		var src = scripts[i].src;\n" +
				"		if (src.indexOf('/" + fileName + "') != -1) {\n" +
				"			baseDir = src.substring(0, src.lastIndexOf('/'));\n" +
				"		}\n" +
				"	}\n" +
				"	document.write('<script type=\"text/javascript\" src=\"' + baseDir + '/../../" + options.baseDir + "/o.js\"></script>');\n" +
				"})();\n"
			);
		}
	});
};

var addDebug = function(srcPath, enable) {
	enable = enable === undefined ? true : enable;

	if (fs.existsSync(srcPath)) {
		var buffer = fs.readFileSync(srcPath);
		fs.writeFileSync(srcPath, ";var MXI_DEBUG = "+enable+";\n" + buffer);
	}
};

// String.prototype.replace() does something weird on strings with fancy regexps,
// hence this artifical version
var replace = function(from, to, text, all) {
	var pos = text.indexOf(from);
	if (pos == -1) {
		return text;
	}
	text = text.substring(0, pos) + to + text.substring(pos + from.length);
	return !all ? text : replace.call(null, from, to, text, all);
};

var addUMD = function(ns, iife, deps) {
	var wrap = '\
;(function (global, factory) {\n\
	var extract = function() {\n\
		var ctx = {};\n\
		factory.apply(ctx, arguments);\n\
		return ctx.__ns__;\n\
	};\n\
	\n\
	if (typeof define === "function" && define.amd) {\n\
		define("__ns__", [__deps_quoted__], extract);\n\
	} else if (typeof module === "object" && module.exports) {\n\
		module.exports = extract(__deps_require__);\n\
	} else {\n\
		global.__ns__ = extract(__deps_global__);\n\
	}\n\
}(this || window, function(__deps__) {\n\
__iife__\n\
}));\
';

	var depsQuoted = depsGlobal = depsRequire = depsPlain = '';

	if (deps && deps.length) {
		depsQuoted = "'./" + deps.join("', './") + "'";
		depsGlobal = "global." + deps.join(", global.");
		depsRequire = "require('./" + deps.join("'), require('./") + "')";
		depsPlain = deps.join(', ');
	}

	wrap = replace('__ns__', ns, wrap, true);
	wrap = replace('__deps__', depsPlain, wrap, true);
	wrap = replace('__deps_quoted__', depsQuoted, wrap, true);
	wrap = replace('__deps_global__', depsGlobal, wrap, true);
	wrap = replace('__deps_require__', depsRequire, wrap, true);
	wrap = replace('__iife__', iife, wrap);
	return wrap;
};

module.exports = {
	getExtensionPaths: getExtensionPaths,
	getExtensionPaths4: getExtensionPaths4,
	addCompat: addCompat,
	addDebug: addDebug,
	addUMD: addUMD
};


/**
 * Transporter.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define("moxie/runtime/Transporter", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Encode",
	"moxie/runtime/RuntimeClient",
	"moxie/core/EventTarget"
], function(Basic, Encode, RuntimeClient, EventTarget) {

	/**
	@class moxie/runtime/Transporter
	@private
	@constructor
	*/
	function Transporter() {
		var mod, _runtime, _data, _size, _pos, _chunk_size;

		RuntimeClient.call(this);

		Basic.extend(this, {
			uid: Basic.guid('uid_'),

			state: Transporter.IDLE,

			result: null,

			transport: function(data, type, options) {
				var self = this;

				options = Basic.extend({
					chunk_size: 204798
				}, options);

				// should divide by three, base64 requires this
				if ((mod = options.chunk_size % 3)) {
					options.chunk_size += 3 - mod;
				}

				_chunk_size = options.chunk_size;

				_reset.call(this);
				_data = data;
				_size = data.length;

				if (Basic.typeOf(options) === 'string' || options.ruid) {
					_run.call(self, type, this.connectRuntime(options));
				} else {
					// we require this to run only once
					var cb = function(e, runtime) {
						self.unbind("RuntimeInit", cb);
						_run.call(self, type, runtime);
					};
					this.bind("RuntimeInit", cb);
					this.connectRuntime(options);
				}
			},

			abort: function() {
				var self = this;

				self.state = Transporter.IDLE;
				if (_runtime) {
					_runtime.exec.call(self, 'Transporter', 'clear');
					self.trigger("TransportingAborted");
				}

				_reset.call(self);
			},

			destroy: function() {
				this.unbindAll();
				_runtime = null;
				this.disconnectRuntime();
				_reset.call(this);
			}
		});

		function _reset() {
			_size = _pos = 0;
			_data = this.result = null;
		}

		function _run(type, runtime) {
			var self = this;

			_runtime = runtime;

			//self.unbind("RuntimeInit");

			self.bind("TransportingProgress", function(e) {
				_pos = e.loaded;

				if (_pos < _size && Basic.inArray(self.state, [Transporter.IDLE, Transporter.DONE]) === -1) {
					_transport.call(self);
				}
			}, 999);

			self.bind("TransportingComplete", function() {
				_pos = _size;
				self.state = Transporter.DONE;
				_data = null; // clean a bit
				self.result = _runtime.exec.call(self, 'Transporter', 'getAsBlob', type || '');
			}, 999);

			self.state = Transporter.BUSY;
			self.trigger("TransportingStarted");
			_transport.call(self);
		}

		function _transport() {
			var self = this,
				chunk,
				bytesLeft = _size - _pos;

			if (_chunk_size > bytesLeft) {
				_chunk_size = bytesLeft;
			}

			chunk = Encode.btoa(_data.substr(_pos, _chunk_size));
			_runtime.exec.call(self, 'Transporter', 'receive', chunk, _size);
		}
	}

	Transporter.IDLE = 0;
	Transporter.BUSY = 1;
	Transporter.DONE = 2;

	Transporter.prototype = EventTarget.instance;

	return Transporter;
});


/**
 * FileReader.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/file/FileReader', [
	'moxie/core/utils/Basic',
	'moxie/core/utils/Encode',
	'moxie/core/Exceptions',
	'moxie/core/EventTarget',
	'moxie/file/Blob',
	'moxie/runtime/RuntimeClient'
], function(Basic, Encode, x, EventTarget, Blob, RuntimeClient) {
	/**
	Utility for preloading o.Blob/o.File objects in memory. By design closely follows [W3C FileReader](http://www.w3.org/TR/FileAPI/#dfn-filereader)
	interface. Where possible uses native FileReader, where - not falls back to shims.

	@class moxie/file/FileReader
	@constructor FileReader
	@extends EventTarget
	@uses RuntimeClient
	*/
	var dispatches = [

		/**
		Dispatched when the read starts.

		@event loadstart
		@param {Object} event
		*/
		'loadstart',

		/**
		Dispatched while reading (and decoding) blob, and reporting partial Blob data (progess.loaded/progress.total).

		@event progress
		@param {Object} event
		*/
		'progress',

		/**
		Dispatched when the read has successfully completed.

		@event load
		@param {Object} event
		*/
		'load',

		/**
		Dispatched when the read has been aborted. For instance, by invoking the abort() method.

		@event abort
		@param {Object} event
		*/
		'abort',

		/**
		Dispatched when the read has failed.

		@event error
		@param {Object} event
		*/
		'error',

		/**
		Dispatched when the request has completed (either in success or failure).

		@event loadend
		@param {Object} event
		*/
		'loadend'
	];

	function FileReader() {

		RuntimeClient.call(this);

		Basic.extend(this, {
			/**
			UID of the component instance.

			@property uid
			@type {String}
			*/
			uid: Basic.guid('uid_'),

			/**
			Contains current state of FileReader object. Can take values of FileReader.EMPTY, FileReader.LOADING
			and FileReader.DONE.

			@property readyState
			@type {Number}
			@default FileReader.EMPTY
			*/
			readyState: FileReader.EMPTY,

			/**
			Result of the successful read operation.

			@property result
			@type {String}
			*/
			result: null,

			/**
			Stores the error of failed asynchronous read operation.

			@property error
			@type {DOMError}
			*/
			error: null,

			/**
			Initiates reading of File/Blob object contents to binary string.

			@method readAsBinaryString
			@param {Blob|File} blob Object to preload
			*/
			readAsBinaryString: function(blob) {
				_read.call(this, 'readAsBinaryString', blob);
			},

			/**
			Initiates reading of File/Blob object contents to dataURL string.

			@method readAsDataURL
			@param {Blob|File} blob Object to preload
			*/
			readAsDataURL: function(blob) {
				_read.call(this, 'readAsDataURL', blob);
			},

			/**
			Initiates reading of File/Blob object contents to string.

			@method readAsText
			@param {Blob|File} blob Object to preload
			*/
			readAsText: function(blob) {
				_read.call(this, 'readAsText', blob);
			},

			/**
			Aborts preloading process.

			@method abort
			*/
			abort: function() {
				this.result = null;

				if (Basic.inArray(this.readyState, [FileReader.EMPTY, FileReader.DONE]) !== -1) {
					return;
				} else if (this.readyState === FileReader.LOADING) {
					this.readyState = FileReader.DONE;
				}

				this.exec('FileReader', 'abort');

				this.trigger('abort');
				this.trigger('loadend');
			},

			/**
			Destroy component and release resources.

			@method destroy
			*/
			destroy: function() {
				this.abort();
				this.exec('FileReader', 'destroy');
				this.disconnectRuntime();
				this.unbindAll();
			}
		});

		// uid must already be assigned
		this.handleEventProps(dispatches);

		this.bind('Error', function(e, err) {
			this.readyState = FileReader.DONE;
			this.error = err;
		}, 999);

		this.bind('Load', function(e) {
			this.readyState = FileReader.DONE;
		}, 999);

		function _read(op, blob) {
			var self = this;

			this.trigger('loadstart');

			if (this.readyState === FileReader.LOADING) {
				this.trigger('error', new x.DOMException(x.DOMException.INVALID_STATE_ERR));
				this.trigger('loadend');
				return;
			}

			// if source is not o.Blob/o.File
			if (!(blob instanceof Blob)) {
				this.trigger('error', new x.DOMException(x.DOMException.NOT_FOUND_ERR));
				this.trigger('loadend');
				return;
			}

			this.result = null;
			this.readyState = FileReader.LOADING;

			if (blob.isDetached()) {
				var src = blob.getSource();
				switch (op) {
					case 'readAsText':
					case 'readAsBinaryString':
						this.result = src;
						break;
					case 'readAsDataURL':
						this.result = 'data:' + blob.type + ';base64,' + Encode.btoa(src);
						break;
				}
				this.readyState = FileReader.DONE;
				this.trigger('load');
				this.trigger('loadend');
			} else {
				this.connectRuntime(blob.ruid);
				this.exec('FileReader', 'read', op, blob);
			}
		}
	}

	/**
	Initial FileReader state

	@property EMPTY
	@type {Number}
	@final
	@static
	@default 0
	*/
	FileReader.EMPTY = 0;

	/**
	FileReader switches to this state when it is preloading the source

	@property LOADING
	@type {Number}
	@final
	@static
	@default 1
	*/
	FileReader.LOADING = 1;

	/**
	Preloading is complete, this is a final state

	@property DONE
	@type {Number}
	@final
	@static
	@default 2
	*/
	FileReader.DONE = 2;

	FileReader.prototype = EventTarget.instance;

	return FileReader;
});


/**
 * Env.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
@class moxie/core/utils/Env
@public
@static
*/

define("moxie/core/utils/Env", [
	"moxie/core/utils/Basic"
], function(Basic) {

	/**
	 * UAParser.js v0.7.7
	 * Lightweight JavaScript-based User-Agent string parser
	 * https://github.com/faisalman/ua-parser-js
	 *
	 * Copyright © 2012-2015 Faisal Salman <fyzlman@gmail.com>
	 * Dual licensed under GPLv2 & MIT
	 */
	var UAParser = (function (undefined) {

	    //////////////
	    // Constants
	    /////////////

	    var EMPTY       = '',
	        UNKNOWN     = '?',
	        FUNC_TYPE   = 'function',
	        UNDEF_TYPE  = 'undefined',
	        OBJ_TYPE    = 'object',
	        MAJOR       = 'major',
	        MODEL       = 'model',
	        NAME        = 'name',
	        TYPE        = 'type',
	        VENDOR      = 'vendor',
	        VERSION     = 'version',
	        ARCHITECTURE= 'architecture',
	        CONSOLE     = 'console',
	        MOBILE      = 'mobile',
	        TABLET      = 'tablet';

	    ///////////
	    // Helper
	    //////////

	    var util = {
	        has : function (str1, str2) {
	            return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
	        },
	        lowerize : function (str) {
	            return str.toLowerCase();
	        }
	    };

	    ///////////////
	    // Map helper
	    //////////////

	    var mapper = {

	        rgx : function () {

	            // loop through all regexes maps
	            for (var result, i = 0, j, k, p, q, matches, match, args = arguments; i < args.length; i += 2) {

	                var regex = args[i],       // even sequence (0,2,4,..)
	                    props = args[i + 1];   // odd sequence (1,3,5,..)

	                // construct object barebones
	                if (typeof(result) === UNDEF_TYPE) {
	                    result = {};
	                    for (p in props) {
	                        q = props[p];
	                        if (typeof(q) === OBJ_TYPE) {
	                            result[q[0]] = undefined;
	                        } else {
	                            result[q] = undefined;
	                        }
	                    }
	                }

	                // try matching uastring with regexes
	                for (j = k = 0; j < regex.length; j++) {
	                    matches = regex[j].exec(this.getUA());
	                    if (!!matches) {
	                        for (p = 0; p < props.length; p++) {
	                            match = matches[++k];
	                            q = props[p];
	                            // check if given property is actually array
	                            if (typeof(q) === OBJ_TYPE && q.length > 0) {
	                                if (q.length == 2) {
	                                    if (typeof(q[1]) == FUNC_TYPE) {
	                                        // assign modified match
	                                        result[q[0]] = q[1].call(this, match);
	                                    } else {
	                                        // assign given value, ignore regex match
	                                        result[q[0]] = q[1];
	                                    }
	                                } else if (q.length == 3) {
	                                    // check whether function or regex
	                                    if (typeof(q[1]) === FUNC_TYPE && !(q[1].exec && q[1].test)) {
	                                        // call function (usually string mapper)
	                                        result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
	                                    } else {
	                                        // sanitize match using given regex
	                                        result[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
	                                    }
	                                } else if (q.length == 4) {
	                                        result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
	                                }
	                            } else {
	                                result[q] = match ? match : undefined;
	                            }
	                        }
	                        break;
	                    }
	                }

	                if(!!matches) break; // break the loop immediately if match found
	            }
	            return result;
	        },

	        str : function (str, map) {

	            for (var i in map) {
	                // check if array
	                if (typeof(map[i]) === OBJ_TYPE && map[i].length > 0) {
	                    for (var j = 0; j < map[i].length; j++) {
	                        if (util.has(map[i][j], str)) {
	                            return (i === UNKNOWN) ? undefined : i;
	                        }
	                    }
	                } else if (util.has(map[i], str)) {
	                    return (i === UNKNOWN) ? undefined : i;
	                }
	            }
	            return str;
	        }
	    };

	    ///////////////
	    // String map
	    //////////////

	    var maps = {

	        browser : {
	            oldsafari : {
	                major : {
	                    '1' : ['/8', '/1', '/3'],
	                    '2' : '/4',
	                    '?' : '/'
	                },
	                version : {
	                    '1.0'   : '/8',
	                    '1.2'   : '/1',
	                    '1.3'   : '/3',
	                    '2.0'   : '/412',
	                    '2.0.2' : '/416',
	                    '2.0.3' : '/417',
	                    '2.0.4' : '/419',
	                    '?'     : '/'
	                }
	            }
	        },

	        device : {
	            sprint : {
	                model : {
	                    'Evo Shift 4G' : '7373KT'
	                },
	                vendor : {
	                    'HTC'       : 'APA',
	                    'Sprint'    : 'Sprint'
	                }
	            }
	        },

	        os : {
	            windows : {
	                version : {
	                    'ME'        : '4.90',
	                    'NT 3.11'   : 'NT3.51',
	                    'NT 4.0'    : 'NT4.0',
	                    '2000'      : 'NT 5.0',
	                    'XP'        : ['NT 5.1', 'NT 5.2'],
	                    'Vista'     : 'NT 6.0',
	                    '7'         : 'NT 6.1',
	                    '8'         : 'NT 6.2',
	                    '8.1'       : 'NT 6.3',
	                    'RT'        : 'ARM'
	                }
	            }
	        }
	    };

	    //////////////
	    // Regex map
	    /////////////

	    var regexes = {

	        browser : [[

	            // Presto based
	            /(opera\smini)\/([\w\.-]+)/i,                                       // Opera Mini
	            /(opera\s[mobiletab]+).+version\/([\w\.-]+)/i,                      // Opera Mobi/Tablet
	            /(opera).+version\/([\w\.]+)/i,                                     // Opera > 9.80
	            /(opera)[\/\s]+([\w\.]+)/i                                          // Opera < 9.80

	            ], [NAME, VERSION], [

	            /\s(opr)\/([\w\.]+)/i                                               // Opera Webkit
	            ], [[NAME, 'Opera'], VERSION], [

	            // Mixed
	            /(kindle)\/([\w\.]+)/i,                                             // Kindle
	            /(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,
	                                                                               // Lunascape/Maxthon/Netfront/Jasmine/Blazer

	            // Trident based
	            /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,
	                                                                               // Avant/IEMobile/SlimBrowser/Baidu
	            /(?:ms|\()(ie)\s([\w\.]+)/i,                                        // Internet Explorer

	            // Webkit/KHTML based
	            /(rekonq)\/([\w\.]+)*/i,                                            // Rekonq
	            /(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i
	                                                                               // Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron
	            ], [NAME, VERSION], [

	            /(trident).+rv[:\s]([\w\.]+).+like\sgecko/i                         // IE11
	            ], [[NAME, 'IE'], VERSION], [

	            /(edge)\/((\d+)?[\w\.]+)/i                                          // Microsoft Edge
	            ], [NAME, VERSION], [

	            /(yabrowser)\/([\w\.]+)/i                                           // Yandex
	            ], [[NAME, 'Yandex'], VERSION], [

	            /(comodo_dragon)\/([\w\.]+)/i                                       // Comodo Dragon
	            ], [[NAME, /_/g, ' '], VERSION], [

	            /(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i,
	                                                                               // Chrome/OmniWeb/Arora/Tizen/Nokia
	            /(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i
	                                                                               // UCBrowser/QQBrowser
	            ], [NAME, VERSION], [

	            /(dolfin)\/([\w\.]+)/i                                              // Dolphin
	            ], [[NAME, 'Dolphin'], VERSION], [

	            /((?:android.+)crmo|crios)\/([\w\.]+)/i                             // Chrome for Android/iOS
	            ], [[NAME, 'Chrome'], VERSION], [

	            /XiaoMi\/MiuiBrowser\/([\w\.]+)/i                                   // MIUI Browser
	            ], [VERSION, [NAME, 'MIUI Browser']], [

	            /android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i         // Android Browser
	            ], [VERSION, [NAME, 'Android Browser']], [

	            /FBAV\/([\w\.]+);/i                                                 // Facebook App for iOS
	            ], [VERSION, [NAME, 'Facebook']], [

	            /version\/([\w\.]+).+?mobile\/\w+\s(safari)/i                       // Mobile Safari
	            ], [VERSION, [NAME, 'Mobile Safari']], [

	            /version\/([\w\.]+).+?(mobile\s?safari|safari)/i                    // Safari & Safari Mobile
	            ], [VERSION, NAME], [

	            /webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i                     // Safari < 3.0
	            ], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [

	            /(konqueror)\/([\w\.]+)/i,                                          // Konqueror
	            /(webkit|khtml)\/([\w\.]+)/i
	            ], [NAME, VERSION], [

	            // Gecko based
	            /(navigator|netscape)\/([\w\.-]+)/i                                 // Netscape
	            ], [[NAME, 'Netscape'], VERSION], [
	            /(swiftfox)/i,                                                      // Swiftfox
	            /(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
	                                                                               // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
	            /(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,
	                                                                               // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
	            /(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i,                          // Mozilla

	            // Other
	            /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i,
	                                                                               // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf
	            /(links)\s\(([\w\.]+)/i,                                            // Links
	            /(gobrowser)\/?([\w\.]+)*/i,                                        // GoBrowser
	            /(ice\s?browser)\/v?([\w\._]+)/i,                                   // ICE Browser
	            /(mosaic)[\/\s]([\w\.]+)/i                                          // Mosaic
	            ], [NAME, VERSION]
	        ],

	        engine : [[

	            /windows.+\sedge\/([\w\.]+)/i                                       // EdgeHTML
	            ], [VERSION, [NAME, 'EdgeHTML']], [

	            /(presto)\/([\w\.]+)/i,                                             // Presto
	            /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i,     // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
	            /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i,                          // KHTML/Tasman/Links
	            /(icab)[\/\s]([23]\.[\d\.]+)/i                                      // iCab
	            ], [NAME, VERSION], [

	            /rv\:([\w\.]+).*(gecko)/i                                           // Gecko
	            ], [VERSION, NAME]
	        ],

	        os : [[

	            // Windows based
	            /microsoft\s(windows)\s(vista|xp)/i                                 // Windows (iTunes)
	            ], [NAME, VERSION], [
	            /(windows)\snt\s6\.2;\s(arm)/i,                                     // Windows RT
	            /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
	            ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
	            /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
	            ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [

	            // Mobile/Embedded OS
	            /\((bb)(10);/i                                                      // BlackBerry 10
	            ], [[NAME, 'BlackBerry'], VERSION], [
	            /(blackberry)\w*\/?([\w\.]+)*/i,                                    // Blackberry
	            /(tizen)[\/\s]([\w\.]+)/i,                                          // Tizen
	            /(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,
	                                                                               // Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
	            /linux;.+(sailfish);/i                                              // Sailfish OS
	            ], [NAME, VERSION], [
	            /(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i                 // Symbian
	            ], [[NAME, 'Symbian'], VERSION], [
	            /\((series40);/i                                                    // Series 40
	            ], [NAME], [
	            /mozilla.+\(mobile;.+gecko.+firefox/i                               // Firefox OS
	            ], [[NAME, 'Firefox OS'], VERSION], [

	            // Console
	            /(nintendo|playstation)\s([wids3portablevu]+)/i,                    // Nintendo/Playstation

	            // GNU/Linux based
	            /(mint)[\/\s\(]?(\w+)*/i,                                           // Mint
	            /(mageia|vectorlinux)[;\s]/i,                                       // Mageia/VectorLinux
	            /(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i,
	                                                                               // Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
	                                                                               // Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
	            /(hurd|linux)\s?([\w\.]+)*/i,                                       // Hurd/Linux
	            /(gnu)\s?([\w\.]+)*/i                                               // GNU
	            ], [NAME, VERSION], [

	            /(cros)\s[\w]+\s([\w\.]+\w)/i                                       // Chromium OS
	            ], [[NAME, 'Chromium OS'], VERSION],[

	            // Solaris
	            /(sunos)\s?([\w\.]+\d)*/i                                           // Solaris
	            ], [[NAME, 'Solaris'], VERSION], [

	            // BSD based
	            /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i                   // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
	            ], [NAME, VERSION],[

	            /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i             // iOS
	            ], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [

	            /(mac\sos\sx)\s?([\w\s\.]+\w)*/i,
	            /(macintosh|mac(?=_powerpc)\s)/i                                    // Mac OS
	            ], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [

	            // Other
	            /((?:open)?solaris)[\/\s-]?([\w\.]+)*/i,                            // Solaris
	            /(haiku)\s(\w+)/i,                                                  // Haiku
	            /(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i,                               // AIX
	            /(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,
	                                                                               // Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
	            /(unix)\s?([\w\.]+)*/i                                              // UNIX
	            ], [NAME, VERSION]
	        ]
	    };

	    /////////////////
	    // Constructor
	    ////////////////

	    var UAParser = function (uastring) {

	        var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);

	        this.getBrowser = function () {
	            return mapper.rgx.apply(this, regexes.browser);
	        };
	        this.getEngine = function () {
	            return mapper.rgx.apply(this, regexes.engine);
	        };
	        this.getOS = function () {
	            return mapper.rgx.apply(this, regexes.os);
	        };
	        this.getResult = function() {
	            return {
	                ua      : this.getUA(),
	                browser : this.getBrowser(),
	                engine  : this.getEngine(),
	                os      : this.getOS()
	            };
	        };
	        this.getUA = function () {
	            return ua;
	        };
	        this.setUA = function (uastring) {
	            ua = uastring;
	            return this;
	        };
	        this.setUA(ua);
	    };

	    return UAParser;
	})();

	function version_compare(v1, v2, operator) {
	  // From: http://phpjs.org/functions
	  // +      original by: Philippe Jausions (http://pear.php.net/user/jausions)
	  // +      original by: Aidan Lister (http://aidanlister.com/)
	  // + reimplemented by: Kankrelune (http://www.webfaktory.info/)
	  // +      improved by: Brett Zamir (http://brett-zamir.me)
	  // +      improved by: Scott Baker
	  // +      improved by: Theriault
	  // *        example 1: version_compare('8.2.5rc', '8.2.5a');
	  // *        returns 1: 1
	  // *        example 2: version_compare('8.2.50', '8.2.52', '<');
	  // *        returns 2: true
	  // *        example 3: version_compare('5.3.0-dev', '5.3.0');
	  // *        returns 3: -1
	  // *        example 4: version_compare('4.1.0.52','4.01.0.51');
	  // *        returns 4: 1

	  // Important: compare must be initialized at 0.
	  var i = 0,
	    x = 0,
	    compare = 0,
	    // vm maps textual PHP versions to negatives so they're less than 0.
	    // PHP currently defines these as CASE-SENSITIVE. It is important to
	    // leave these as negatives so that they can come before numerical versions
	    // and as if no letters were there to begin with.
	    // (1alpha is < 1 and < 1.1 but > 1dev1)
	    // If a non-numerical value can't be mapped to this table, it receives
	    // -7 as its value.
	    vm = {
	      'dev': -6,
	      'alpha': -5,
	      'a': -5,
	      'beta': -4,
	      'b': -4,
	      'RC': -3,
	      'rc': -3,
	      '#': -2,
	      'p': 1,
	      'pl': 1
	    },
	    // This function will be called to prepare each version argument.
	    // It replaces every _, -, and + with a dot.
	    // It surrounds any nonsequence of numbers/dots with dots.
	    // It replaces sequences of dots with a single dot.
	    //    version_compare('4..0', '4.0') == 0
	    // Important: A string of 0 length needs to be converted into a value
	    // even less than an unexisting value in vm (-7), hence [-8].
	    // It's also important to not strip spaces because of this.
	    //   version_compare('', ' ') == 1
	    prepVersion = function (v) {
	      v = ('' + v).replace(/[_\-+]/g, '.');
	      v = v.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.');
	      return (!v.length ? [-8] : v.split('.'));
	    },
	    // This converts a version component to a number.
	    // Empty component becomes 0.
	    // Non-numerical component becomes a negative number.
	    // Numerical component becomes itself as an integer.
	    numVersion = function (v) {
	      return !v ? 0 : (isNaN(v) ? vm[v] || -7 : parseInt(v, 10));
	    };

	  v1 = prepVersion(v1);
	  v2 = prepVersion(v2);
	  x = Math.max(v1.length, v2.length);
	  for (i = 0; i < x; i++) {
	    if (v1[i] == v2[i]) {
	      continue;
	    }
	    v1[i] = numVersion(v1[i]);
	    v2[i] = numVersion(v2[i]);
	    if (v1[i] < v2[i]) {
	      compare = -1;
	      break;
	    } else if (v1[i] > v2[i]) {
	      compare = 1;
	      break;
	    }
	  }
	  if (!operator) {
	    return compare;
	  }

	  // Important: operator is CASE-SENSITIVE.
	  // "No operator" seems to be treated as "<."
	  // Any other values seem to make the function return null.
	  switch (operator) {
	  case '>':
	  case 'gt':
	    return (compare > 0);
	  case '>=':
	  case 'ge':
	    return (compare >= 0);
	  case '<=':
	  case 'le':
	    return (compare <= 0);
	  case '==':
	  case '=':
	  case 'eq':
	    return (compare === 0);
	  case '<>':
	  case '!=':
	  case 'ne':
	    return (compare !== 0);
	  case '':
	  case '<':
	  case 'lt':
	    return (compare < 0);
	  default:
	    return null;
	  }
	}

	var can = (function() {
		var caps = {
			access_global_ns: function () {
				return !!window.moxie;
			},

			define_property: (function() {
				/* // currently too much extra code required, not exactly worth it
				try { // as of IE8, getters/setters are supported only on DOM elements
					var obj = {};
					if (Object.defineProperty) {
						Object.defineProperty(obj, 'prop', {
							enumerable: true,
							configurable: true
						});
						return true;
					}
				} catch(ex) {}

				if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) {
					return true;
				}*/
				return false;
			}()),

			create_canvas: function() {
				// On the S60 and BB Storm, getContext exists, but always returns undefined
				// so we actually have to call getContext() to verify
				// github.com/Modernizr/Modernizr/issues/issue/97/
				var el = document.createElement('canvas');
				var isSupported = !!(el.getContext && el.getContext('2d'));
				caps.create_canvas = isSupported;
				return isSupported;
			},

			return_response_type: function(responseType) {
				try {
					if (Basic.inArray(responseType, ['', 'text', 'document']) !== -1) {
						return true;
					} else if (window.XMLHttpRequest) {
						var xhr = new XMLHttpRequest();
						xhr.open('get', '/'); // otherwise Gecko throws an exception
						if ('responseType' in xhr) {
							xhr.responseType = responseType;
							// as of 23.0.1271.64, Chrome switched from throwing exception to merely logging it to the console (why? o why?)
							if (xhr.responseType !== responseType) {
								return false;
							}
							return true;
						}
					}
				} catch (ex) {}
				return false;
			},

			use_blob_uri: function() {
				var URL = window.URL;
				caps.use_blob_uri = (URL &&
					'createObjectURL' in URL &&
					'revokeObjectURL' in URL &&
					(Env.browser !== 'IE' || Env.verComp(Env.version, '11.0.46', '>=')) // IE supports createObjectURL, but not fully, for example it fails to use it as a src for the image
				);
				return caps.use_blob_uri;
			},

			// ideas for this heavily come from Modernizr (http://modernizr.com/)
			use_data_uri: (function() {
				var du = new Image();

				du.onload = function() {
					caps.use_data_uri = (du.width === 1 && du.height === 1);
				};

				setTimeout(function() {
					du.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
				}, 1);
				return false;
			}()),

			use_data_uri_over32kb: function() { // IE8
				return caps.use_data_uri && (Env.browser !== 'IE' || Env.version >= 9);
			},

			use_data_uri_of: function(bytes) {
				return (caps.use_data_uri && bytes < 33000 || caps.use_data_uri_over32kb());
			},

			use_fileinput: function() {
				if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) {
					return false;
				}

				var el = document.createElement('input');
				el.setAttribute('type', 'file');
				return caps.use_fileinput = !el.disabled;
			},

			use_webgl: function() {
				var canvas = document.createElement('canvas');
				var gl = null, isSupported;
				try {
					gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
				}
				catch(e) {}

				if (!gl) { // it seems that sometimes it doesn't throw exception, but still fails to get context
					gl = null;
				}

				isSupported = !!gl;
				caps.use_webgl = isSupported; // save result of our check
				canvas = undefined;
				return isSupported;
			}
		};

		return function(cap) {
			var args = [].slice.call(arguments);
			args.shift(); // shift of cap
			return Basic.typeOf(caps[cap]) === 'function' ? caps[cap].apply(this, args) : !!caps[cap];
		};
	}());

	var uaResult = new UAParser().getResult();

	var Env = {
		can: can,

		uaParser: UAParser,

		browser: uaResult.browser.name,
		version: uaResult.browser.version,
		os: uaResult.os.name, // everybody intuitively types it in a lowercase for some reason
		osVersion: uaResult.os.version,

		verComp: version_compare,

		swf_url: "../flash/Moxie.swf",
		xap_url: "../silverlight/Moxie.xap",
		global_event_dispatcher: "moxie.core.EventTarget.instance.dispatchEvent"
	};

	// for backward compatibility
	// @deprecated Use `Env.os` instead
	Env.OS = Env.os;

	if (MXI_DEBUG) {
		Env.debug = {
			runtime: true,
			events: false
		};

		Env.log = function() {

			function logObj(data) {
				// TODO: this should recursively print out the object in a pretty way
				console.appendChild(document.createTextNode(data + "\n"));
			}

			// if debugger present, IE8 might have window.console.log method, but not be able to apply on it (why...)
			if (window && window.console && window.console.log && window.console.log.apply) {
				window.console.log.apply(window.console, arguments);
			} else if (document) {
				var console = document.getElementById('moxie-console');
				if (!console) {
					console = document.createElement('pre');
					console.id = 'moxie-console';
					//console.style.display = 'none';
					document.body.appendChild(console);
				}

				var data = arguments[0];
				if (Basic.typeOf(data) === 'string') {
					data = Basic.sprintf.apply(this, arguments);
				} else if (Basic.inArray(Basic.typeOf(data), ['object', 'array']) !== -1) {
					logObj(data);
					return;
				}

				console.appendChild(document.createTextNode(data + "\n"));
			}
		};
	}

	return Env;
});


/**
 * EventTarget.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/core/EventTarget', [
	'moxie/core/utils/Env',
	'moxie/core/Exceptions',
	'moxie/core/utils/Basic'
], function(Env, x, Basic) {

	// hash of event listeners by object uid
	var eventpool = {};

	/**
	Parent object for all event dispatching components and objects

	@class moxie/core/EventTarget
	@constructor EventTarget
	*/
	function EventTarget() {
		/**
		Unique id of the event dispatcher, usually overriden by children

		@property uid
		@type String
		*/
		this.uid = Basic.guid();
	}

	Basic.extend(EventTarget.prototype, {

		/**
		Can be called from within a child  in order to acquire uniqie id in automated manner

		@method init
		*/
		init: function() {
			if (!this.uid) {
				this.uid = Basic.guid('uid_');
			}
		},

		/**
		Register a handler to a specific event dispatched by the object

		@method addEventListener
		@param {String} type Type or basically a name of the event to subscribe to
		@param {Function} fn Callback function that will be called when event happens
		@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
		@param {Object} [scope=this] A scope to invoke event handler in
		*/
		addEventListener: function(type, fn, priority, scope) {
			var self = this, list;

			// without uid no event handlers can be added, so make sure we got one
			if (!this.hasOwnProperty('uid')) {
				this.uid = Basic.guid('uid_');
			}

			type = Basic.trim(type);

			if (/\s/.test(type)) {
				// multiple event types were passed for one handler
				Basic.each(type.split(/\s+/), function(type) {
					self.addEventListener(type, fn, priority, scope);
				});
				return;
			}

			type = type.toLowerCase();
			priority = parseInt(priority, 10) || 0;

			list = eventpool[this.uid] && eventpool[this.uid][type] || [];
			list.push({fn : fn, priority : priority, scope : scope || this});

			if (!eventpool[this.uid]) {
				eventpool[this.uid] = {};
			}
			eventpool[this.uid][type] = list;
		},

		/**
		Check if any handlers were registered to the specified event

		@method hasEventListener
		@param {String} [type] Type or basically a name of the event to check
		@return {Mixed} Returns a handler if it was found and false, if - not
		*/
		hasEventListener: function(type) {
			var list;
			if (type) {
				type = type.toLowerCase();
				list = eventpool[this.uid] && eventpool[this.uid][type];
			} else {
				list = eventpool[this.uid];
			}
			return list ? list : false;
		},

		/**
		Unregister the handler from the event, or if former was not specified - unregister all handlers

		@method removeEventListener
		@param {String} type Type or basically a name of the event
		@param {Function} [fn] Handler to unregister
		*/
		removeEventListener: function(type, fn) {
			var self = this, list, i;

			type = type.toLowerCase();

			if (/\s/.test(type)) {
				// multiple event types were passed for one handler
				Basic.each(type.split(/\s+/), function(type) {
					self.removeEventListener(type, fn);
				});
				return;
			}

			list = eventpool[this.uid] && eventpool[this.uid][type];

			if (list) {
				if (fn) {
					for (i = list.length - 1; i >= 0; i--) {
						if (list[i].fn === fn) {
							list.splice(i, 1);
							break;
						}
					}
				} else {
					list = [];
				}

				// delete event list if it has become empty
				if (!list.length) {
					delete eventpool[this.uid][type];

					// and object specific entry in a hash if it has no more listeners attached
					if (Basic.isEmptyObj(eventpool[this.uid])) {
						delete eventpool[this.uid];
					}
				}
			}
		},

		/**
		Remove all event handlers from the object

		@method removeAllEventListeners
		*/
		removeAllEventListeners: function() {
			if (eventpool[this.uid]) {
				delete eventpool[this.uid];
			}
		},

		/**
		Dispatch the event

		@method dispatchEvent
		@param {String/Object} Type of event or event object to dispatch
		@param {Mixed} [...] Variable number of arguments to be passed to a handlers
		@return {Boolean} true by default and false if any handler returned false
		*/
		dispatchEvent: function(type) {
			var uid, list, args, tmpEvt, evt = {}, result = true, undef;

			if (Basic.typeOf(type) !== 'string') {
				// we can't use original object directly (because of Silverlight)
				tmpEvt = type;

				if (Basic.typeOf(tmpEvt.type) === 'string') {
					type = tmpEvt.type;

					if (tmpEvt.total !== undef && tmpEvt.loaded !== undef) { // progress event
						evt.total = tmpEvt.total;
						evt.loaded = tmpEvt.loaded;
					}
					evt.async = tmpEvt.async || false;
				} else {
					throw new x.EventException(x.EventException.UNSPECIFIED_EVENT_TYPE_ERR);
				}
			}

			// check if event is meant to be dispatched on an object having specific uid
			if (type.indexOf('::') !== -1) {
				(function(arr) {
					uid = arr[0];
					type = arr[1];
				}(type.split('::')));
			} else {
				uid = this.uid;
			}

			type = type.toLowerCase();

			list = eventpool[uid] && eventpool[uid][type];

			if (list) {
				// sort event list by prority
				list.sort(function(a, b) { return b.priority - a.priority; });

				args = [].slice.call(arguments);

				// first argument will be pseudo-event object
				args.shift();
				evt.type = type;
				args.unshift(evt);

				if (MXI_DEBUG && Env.debug.events) {
					Env.log("%cEvent '%s' fired on %s", 'color: #999;', evt.type, (this.ctorName ? this.ctorName + '::' : '') + uid);
				}

				// Dispatch event to all listeners
				var queue = [];
				Basic.each(list, function(handler) {
					// explicitly set the target, otherwise events fired from shims do not get it
					args[0].target = handler.scope;
					// if event is marked as async, detach the handler
					if (evt.async) {
						queue.push(function(cb) {
							setTimeout(function() {
								cb(handler.fn.apply(handler.scope, args) === false);
							}, 1);
						});
					} else {
						queue.push(function(cb) {
							cb(handler.fn.apply(handler.scope, args) === false); // if handler returns false stop propagation
						});
					}
				});
				if (queue.length) {
					Basic.inSeries(queue, function(err) {
						result = !err;
					});
				}
			}
			return result;
		},

		/**
		Register a handler to the event type that will run only once

		@method bindOnce
		@since >1.4.1
		@param {String} type Type or basically a name of the event to subscribe to
		@param {Function} fn Callback function that will be called when event happens
		@param {Number} [priority=0] Priority of the event handler - handlers with higher priorities will be called first
		@param {Object} [scope=this] A scope to invoke event handler in
		*/
		bindOnce: function(type, fn, priority, scope) {
			var self = this;
			self.bind.call(this, type, function cb() {
				self.unbind(type, cb);
				return fn.apply(this, arguments);
			}, priority, scope);
		},

		/**
		Alias for addEventListener

		@method bind
		@protected
		*/
		bind: function() {
			this.addEventListener.apply(this, arguments);
		},

		/**
		Alias for removeEventListener

		@method unbind
		@protected
		*/
		unbind: function() {
			this.removeEventListener.apply(this, arguments);
		},

		/**
		Alias for removeAllEventListeners

		@method unbindAll
		@protected
		*/
		unbindAll: function() {
			this.removeAllEventListeners.apply(this, arguments);
		},

		/**
		Alias for dispatchEvent

		@method trigger
		@protected
		*/
		trigger: function() {
			return this.dispatchEvent.apply(this, arguments);
		},

		/**
		Handle properties of on[event] type.

		@method handleEventProps
		@private
		*/
		handleEventProps: function(dispatches) {
			var self = this;

			this.bind(dispatches.join(' '), function(e) {
				var prop = 'on' + e.type.toLowerCase();
				if (Basic.typeOf(this[prop]) === 'function') {
					this[prop].apply(this, arguments);
				}
			});

			// object must have defined event properties, even if it doesn't make use of them
			Basic.each(dispatches, function(prop) {
				prop = 'on' + prop.toLowerCase(prop);
				if (Basic.typeOf(self[prop]) === 'undefined') {
					self[prop] = null;
				}
			});
		}

	});

	EventTarget.instance = new EventTarget();

	return EventTarget;
});


/**
 * Runtime.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

define('moxie/runtime/Runtime', [
	"moxie/core/utils/Env",
	"moxie/core/utils/Basic",
	"moxie/core/utils/Dom",
	"moxie/core/EventTarget"
], function(Env, Basic, Dom, EventTarget) {
	var runtimeConstructors = {}, runtimes = {};

	/**
	Common set of methods and properties for every runtime instance

	@class moxie/runtime/Runtime

	@param {Object} options
	@param {String} type Sanitized name of the runtime
	@param {Object} [caps] Set of capabilities that differentiate specified runtime
	@param {Object} [modeCaps] Set of capabilities that do require specific operational mode
	@param {String} [preferredMode='browser'] Preferred operational mode to choose if no required capabilities were requested
	*/
	function Runtime(options, type, caps, modeCaps, preferredMode) {
		/**
		Dispatched when runtime is initialized and ready.
		Results in RuntimeInit on a connected component.

		@event Init
		*/

		/**
		Dispatched when runtime fails to initialize.
		Results in RuntimeError on a connected component.

		@event Error
		*/

		var self = this
		, _shim
		, _uid = Basic.guid(type + '_')
		, defaultMode = preferredMode || 'browser'
		;

		options = options || {};

		// register runtime in private hash
		runtimes[_uid] = this;

		/**
		Default set of capabilities, which can be redifined later by specific runtime

		@private
		@property caps
		@type Object
		*/
		caps = Basic.extend({
			// Runtime can:
			// provide access to raw binary data of the file
			access_binary: false,
			// provide access to raw binary data of the image (image extension is optional)
			access_image_binary: false,
			// display binary data as thumbs for example
			display_media: false,
			// make cross-domain requests
			do_cors: false,
			// accept files dragged and dropped from the desktop
			drag_and_drop: false,
			// filter files in selection dialog by their extensions
			filter_by_extension: true,
			// resize image (and manipulate it raw data of any file in general)
			resize_image: false,
			// periodically report how many bytes of total in the file were uploaded (loaded)
			report_upload_progress: false,
			// provide access to the headers of http response
			return_response_headers: false,
			// support response of specific type, which should be passed as an argument
			// e.g. runtime.can('return_response_type', 'blob')
			return_response_type: false,
			// return http status code of the response
			return_status_code: true,
			// send custom http header with the request
			send_custom_headers: false,
			// pick up the files from a dialog
			select_file: false,
			// select whole folder in file browse dialog
			select_folder: false,
			// select multiple files at once in file browse dialog
			select_multiple: true,
			// send raw binary data, that is generated after image resizing or manipulation of other kind
			send_binary_string: false,
			// send cookies with http request and therefore retain session
			send_browser_cookies: true,
			// send data formatted as multipart/form-data
			send_multipart: true,
			// slice the file or blob to smaller parts
			slice_blob: false,
			// upload file without preloading it to memory, stream it out directly from disk
			stream_upload: false,
			// programmatically trigger file browse dialog
			summon_file_dialog: false,
			// upload file of specific size, size should be passed as argument
			// e.g. runtime.can('upload_filesize', '500mb')
			upload_filesize: true,
			// initiate http request with specific http method, method should be passed as argument
			// e.g. runtime.can('use_http_method', 'put')
			use_http_method: true
		}, caps);

		// default to the mode that is compatible with preferred caps
		if (options.preferred_caps) {
			defaultMode = Runtime.getMode(modeCaps, options.preferred_caps, defaultMode);
		}

		if (MXI_DEBUG && Env.debug.runtime) {
			Env.log("\tdefault mode: %s", defaultMode);
		}

		// small extension factory here (is meant to be extended with actual extensions constructors)
		_shim = (function() {
			var objpool = {};
			return {
				exec: function(uid, comp, fn, args) {
					if (_shim[comp]) {
						if (!objpool[uid]) {
							objpool[uid] = {
								context: this,
								instance: new _shim[comp]()
							};
						}
						if (objpool[uid].instance[fn]) {
							return objpool[uid].instance[fn].apply(this, args);
						}
					}
				},

				removeInstance: function(uid) {
					delete objpool[uid];
				},

				removeAllInstances: function() {
					var self = this;
					Basic.each(objpool, function(obj, uid) {
						if (Basic.typeOf(obj.instance.destroy) === 'function') {
							obj.instance.destroy.call(obj.context);
						}
						self.removeInstance(uid);
					});
				}
			};
		}());

		// public methods
		Basic.extend(this, {
			/**
			Specifies whether runtime instance was initialized or not

			@property initialized
			@type {Boolean}
			@default false
			*/
			initialized: false, // shims require this flag to stop initialization retries

			/**
			Unique ID of the runtime

			@property uid
			@type {String}
			*/
			uid: _uid,

			/**
			Runtime type (e.g. flash, html5, etc)

			@property type
			@type {String}
			*/
			type: type,

			/**
			Runtime (not native one) may operate in browser or client mode.

			@property mode
			@private
			@type {String|Boolean} current mode or false, if none possible
			*/
			mode: Runtime.getMode(modeCaps, (options.required_caps), defaultMode),

			/**
			id of the DOM container for the runtime (if available)

			@property shimid
			@type {String}
			*/
			shimid: _uid + '_container',

			/**
			Number of connected clients. If equal to zero, runtime can be destroyed

			@property clients
			@type {Number}
			*/
			clients: 0,

			/**
			Runtime initialization options

			@property options
			@type {Object}
			*/
			options: options,

			/**
			Checks if the runtime has specific capability

			@method can
			@param {String} cap Name of capability to check
			@param {Mixed} [value] If passed, capability should somehow correlate to the value
			@param {Object} [refCaps] Set of capabilities to check the specified cap against (defaults to internal set)
			@return {Boolean} true if runtime has such capability and false, if - not
			*/
			can: function(cap, value) {
				var refCaps = arguments[2] || caps;

				// if cap var is a comma-separated list of caps, convert it to object (key/value)
				if (Basic.typeOf(cap) === 'string' && Basic.typeOf(value) === 'undefined') {
					cap = Runtime.parseCaps(cap);
				}

				if (Basic.typeOf(cap) === 'object') {
					for (var key in cap) {
						if (!this.can(key, cap[key], refCaps)) {
							return false;
						}
					}
					return true;
				}

				// check the individual cap
				if (Basic.typeOf(refCaps[cap]) === 'function') {
					return refCaps[cap].call(this, value);
				} else {
					return (value === refCaps[cap]);
				}
			},

			/**
			Returns container for the runtime as DOM element

			@method getShimContainer
			@return {DOMElement}
			*/
			getShimContainer: function() {
				var container, shimContainer = Dom.get(this.shimid);

				// if no container for shim, create one
				if (!shimContainer) {
					container = Dom.get(this.options.container) || document.body;

					// create shim container and insert it at an absolute position into the outer container
					shimContainer = document.createElement('div');
					shimContainer.id = this.shimid;
					shimContainer.className = 'moxie-shim moxie-shim-' + this.type;

					Basic.extend(shimContainer.style, {
						position: 'absolute',
						top: '0px',
						left: '0px',
						width: '1px',
						height: '1px',
						overflow: 'hidden'
					});

					container.appendChild(shimContainer);
					container = null;
				}

				return shimContainer;
			},

			/**
			Returns runtime as DOM element (if appropriate)

			@method getShim
			@return {DOMElement}
			*/
			getShim: function() {
				return _shim;
			},

			/**
			Invokes a method within the runtime itself (might differ across the runtimes)

			@method shimExec
			@param {Mixed} []
			@protected
			@return {Mixed} Depends on the action and component
			*/
			shimExec: function(component, action) {
				var args = [].slice.call(arguments, 2);
				return self.getShim().exec.call(this, this.uid, component, action, args);
			},

			/**
			Operaional interface that is used by components to invoke specific actions on the runtime
			(is invoked in the scope of component)

			@method exec
			@param {Mixed} []*
			@protected
			@return {Mixed} Depends on the action and component
			*/
			exec: function(component, action) { // this is called in the context of component, not runtime
				var args = [].slice.call(arguments, 2);

				if (self[component] && self[component][action]) {
					return self[component][action].apply(this, args);
				}
				return self.shimExec.apply(this, arguments);
			},

			/**
			Destroys the runtime (removes all events and deletes DOM structures)

			@method destroy
			*/
			destroy: function() {
				if (!self) {
					return; // obviously already destroyed
				}

				var shimContainer = Dom.get(this.shimid);
				if (shimContainer) {
					shimContainer.parentNode.removeChild(shimContainer);
				}

				if (_shim) {
					_shim.removeAllInstances();
				}

				this.unbindAll();
				delete runtimes[this.uid];
				this.uid = null; // mark this runtime as destroyed
				_uid = self = _shim = shimContainer = null;
			}
		});

		// once we got the mode, test against all caps
		if (this.mode && options.required_caps && !this.can(options.required_caps)) {
			this.mode = false;
		}
	}

	/**
	Default order to try different runtime types

	@property order
	@type String
	@static
	*/
	Runtime.order = 'html5,flash,silverlight,html4';

	/**
	Retrieves runtime from private hash by it's uid

	@method getRuntime
	@private
	@static
	@param {String} uid Unique identifier of the runtime
	@return {Runtime|Boolean} Returns runtime, if it exists and false, if - not
	*/
	Runtime.getRuntime = function(uid) {
		return runtimes[uid] ? runtimes[uid] : false;
	};

	/**
	Register constructor for the Runtime of new (or perhaps modified) type

	@method addConstructor
	@static
	@param {String} type Runtime type (e.g. flash, html5, etc)
	@param {Function} construct Constructor for the Runtime type
	*/
	Runtime.addConstructor = function(type, constructor) {
		constructor.prototype = EventTarget.instance;
		runtimeConstructors[type] = constructor;
	};

	/**
	Get the constructor for the specified type.

	method getConstructor
	@static
	@param {String} type Runtime type (e.g. flash, html5, etc)
	@return {Function} Constructor for the Runtime type
	*/
	Runtime.getConstructor = function(type) {
		return runtimeConstructors[type] || null;
	};

	/**
	Get info about the runtime (uid, type, capabilities)

	@method getInfo
	@static
	@param {String} uid Unique identifier of the runtime
	@return {Mixed} Info object or null if runtime doesn't exist
	*/
	Runtime.getInfo = function(uid) {
		var runtime = Runtime.getRuntime(uid);

		if (runtime) {
			return {
				uid: runtime.uid,
				type: runtime.type,
				mode: runtime.mode,
				can: function() {
					return runtime.can.apply(runtime, arguments);
				}
			};
		}
		return null;
	};

	/**
	Convert caps represented by a comma-separated string to the object representation.

	@method parseCaps
	@static
	@param {String} capStr Comma-separated list of capabilities
	@return {Object}
	*/
	Runtime.parseCaps = function(capStr) {
		var capObj = {};

		if (Basic.typeOf(capStr) !== 'string') {
			return capStr || {};
		}

		Basic.each(capStr.split(','), function(key) {
			capObj[key] = true; // we assume it to be - true
		});

		return capObj;
	};

	/**
	Test the specified runtime for specific capabilities.

	@method can
	@static
	@param {String} type Runtime type (e.g. flash, html5, etc)
	@param {String|Object} caps Set of capabilities to check
	@return {Boolean} Result of the test
	*/
	Runtime.can = function(type, caps) {
		var runtime
		, constructor = Runtime.getConstructor(type)
		, mode
		;
		if (constructor) {
			runtime = new constructor({
				required_caps: caps
			});
			mode = runtime.mode;
			runtime.destroy();
			return !!mode;
		}
		return false;
	};

	/**
	Figure out a runtime that supports specified capabilities.

	@method thatCan
	@static
	@param {String|Object} caps Set of capabilities to check
	@param {String} [runtimeOrder] Comma-separated list of runtimes to check against
	@return {String} Usable runtime identifier or null
	*/
	Runtime.thatCan = function(caps, runtimeOrder) {
		var types = (runtimeOrder || Runtime.order).split(/\s*,\s*/);
		for (var i in types) {
			if (Runtime.can(types[i], caps)) {
				return types[i];
			}
		}
		return null;
	};

	/**
	Figure out an operational mode for the specified set of capabilities.

	@method getMode
	@static
	@param {Object} modeCaps Set of capabilities that depend on particular runtime mode
	@param {Object} [requiredCaps] Supplied set of capabilities to find operational mode for
	@param {String|Boolean} [defaultMode='browser'] Default mode to use
	@return {String|Boolean} Compatible operational mode
	*/
	Runtime.getMode = function(modeCaps, requiredCaps, defaultMode) {
		var mode = null;

		if (Basic.typeOf(defaultMode) === 'undefined') { // only if not specified
			defaultMode = 'browser';
		}

		if (requiredCaps && !Basic.isEmptyObj(modeCaps)) {
			// loop over required caps and check if they do require the same mode
			Basic.each(requiredCaps, function(value, cap) {
				if (modeCaps.hasOwnProperty(cap)) {
					var capMode = modeCaps[cap](value);

					// make sure we always have an array
					if (typeof(capMode) === 'string') {
						capMode = [capMode];
					}

					if (!mode) {
						mode = capMode;
					} else if (!(mode = Basic.arrayIntersect(mode, capMode))) {
						// if cap requires conflicting mode - runtime cannot fulfill required caps

						if (MXI_DEBUG && Env.debug.runtime) {
							Env.log("\t\t%s: %s (conflicting mode requested: %s)", cap, value, capMode);
						}

						return (mode = false);
					}
				}

				if (MXI_DEBUG && Env.debug.runtime) {
					Env.log("\t\t%s: %s (compatible modes: %s)", cap, value, mode);
				}
			});

			if (mode) {
				return Basic.inArray(defaultMode, mode) !== -1 ? defaultMode : mode[0];
			} else if (mode === false) {
				return false;
			}
		}
		return defaultMode;
	};

	/**
	 * Third party shims (Flash and Silverlight) require global event target against which they
	 * will fire their events. However when moxie is not loaded to global namespace, default
	 * event target is not accessible and we have to create artificial ones.
	 *
	 * @method getGlobalEventTarget
	 * @static
	 * @return {String} Name of the global event target
	 */
	Runtime.getGlobalEventTarget = function() {
		if (/^moxie\./.test(Env.global_event_dispatcher) && !Env.can('access_global_ns')) {
			var uniqueCallbackName = Basic.guid('moxie_event_target_');

			window[uniqueCallbackName] = function(e, data) {
				EventTarget.instance.dispatchEvent(e, data);
			};

			Env.global_event_dispatcher = uniqueCallbackName;
		}

		return Env.global_event_dispatcher;
	};

	/**
	Capability check that always returns true

	@private
	@static
	@return {True}
	*/
	Runtime.capTrue = function() {
		return true;
	};

	/**
	Capability check that always returns false

	@private
	@static
	@return {False}
	*/
	Runtime.capFalse = function() {
		return false;
	};

	/**
	Evaluate the expression to boolean value and create a function that always returns it.

	@private
	@static
	@param {Mixed} expr Expression to evaluate
	@return {Function} Function returning the result of evaluation
	*/
	Runtime.capTest = function(expr) {
		return function() {
			return !!expr;
		};
	};

	return Runtime;
});


/**
 * Runtime.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/*global ActiveXObject:true */

/**
Defines constructor for Flash runtime.

@class moxie/runtime/flash/Runtime
@private
*/
define("moxie/runtime/flash/Runtime", [
	"moxie/core/utils/Basic",
	"moxie/core/utils/Env",
	"moxie/core/utils/Dom",
	"moxie/core/Exceptions",
	"moxie/runtime/Runtime"
], function(Basic, Env, Dom, x, Runtime) {

	var type = 'flash', extensions = {};

	/**
	Get the version of the Flash Player

	@method getShimVersion
	@private
	@return {Number} Flash Player version
	*/
	function getShimVersion() {
		var version;

		try {
			version = navigator.plugins['Shockwave Flash'];
			version = version.description;
		} catch (e1) {
			try {
				version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
			} catch (e2) {
				version = '0.0';
			}
		}
		version = version.match(/\d+/g);
		return parseFloat(version[0] + '.' + version[1]);
	}

	/**
	Cross-browser SWF removal
    	- Especially needed to safely and completely remove a SWF in Internet Explorer

   	Originated from SWFObject v2.2 <http://code.google.com/p/swfobject/>
	*/
	function removeSWF(id) {
        var obj = Dom.get(id);
        if (obj && obj.nodeName == "OBJECT") {
            if (Env.browser === 'IE') {
                obj.style.display = "none";
                (function onInit(){
                	// http://msdn.microsoft.com/en-us/library/ie/ms534360(v=vs.85).aspx
                    if (obj.readyState == 4) {
                        removeObjectInIE(id);
                    }
                    else {
                        setTimeout(onInit, 10);
                    }
                })();
            }
            else {
                obj.parentNode.removeChild(obj);
            }
        }
    }

	function removeObjectInIE(id) {
        var obj = Dom.get(id);
        if (obj) {
            for (var i in obj) {
                if (typeof obj[i] == "function") {
                    obj[i] = null;
                }
            }
            obj.parentNode.removeChild(obj);
        }
    }

	/**
	Constructor for the Flash Runtime
	*/
	function FlashRuntime(options) {
		var I = this, initTimer;

		options = Basic.extend({ swf_url: Env.swf_url }, options);

		Runtime.call(this, options, type, {
			access_binary: function(value) {
				return value && I.mode === 'browser';
			},
			access_image_binary: function(value) {
				return value && I.mode === 'browser';
			},
			display_media: Runtime.capTest(defined('moxie/image/Image')),
			do_cors: Runtime.capTrue,
			drag_and_drop: false,
			report_upload_progress: function() {
				return I.mode === 'client';
			},
			resize_image: Runtime.capTrue,
			return_response_headers: false,
			return_response_type: function(responseType) {
				if (responseType === 'json' && !!window.JSON) {
					return true;
				}
				return !Basic.arrayDiff(responseType, ['', 'text', 'document']) || I.mode === 'browser';
			},
			return_status_code: function(code) {
				return I.mode === 'browser' || !Basic.arrayDiff(code, [200, 404]);
			},
			select_file: Runtime.capTrue,
			select_multiple: Runtime.capTrue,
			send_binary_string: function(value) {
				return value && I.mode === 'browser';
			},
			send_browser_cookies: function(value) {
				return value && I.mode === 'browser';
			},
			send_custom_headers: function(value) {
				return value && I.mode === 'browser';
			},
			send_multipart: Runtime.capTrue,
			slice_blob: function(value) {
				return value && I.mode === 'browser';
			},
			stream_upload: function(value) {
				return value && I.mode === 'browser';
			},
			summon_file_dialog: false,
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) <= 2097152 || I.mode === 'client';
			},
			use_http_method: function(methods) {
				return !Basic.arrayDiff(methods, ['GET', 'POST']);
			}
		}, {
			// capabilities that require specific mode
			access_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			access_image_binary: function(value) {
				return value ? 'browser' : 'client';
			},
			report_upload_progress: function(value) {
				return value ? 'browser' : 'client';
			},
			return_response_type: function(responseType) {
				return Basic.arrayDiff(responseType, ['', 'text', 'json', 'document']) ? 'browser' : ['client', 'browser'];
			},
			return_status_code: function(code) {
				return Basic.arrayDiff(code, [200, 404]) ? 'browser' : ['client', 'browser'];
			},
			send_binary_string: function(value) {
				return value ? 'browser' : 'client';
			},
			send_browser_cookies: function(value) {
				return value ? 'browser' : 'client';
			},
			send_custom_headers: function(value) {
				return value ? 'browser' : 'client';
			},
			slice_blob: function(value) {
				return value ? 'browser' : 'client';
			},
			stream_upload: function(value) {
				return value ? 'client' : 'browser';
			},
			upload_filesize: function(size) {
				return Basic.parseSizeStr(size) >= 2097152 ? 'client' : 'browser';
			}
		}, 'client');

		// minimal requirement for Flash Player version
		if (getShimVersion() < 11.3) {
			if (MXI_DEBUG && Env.debug.runtime) {
				Env.log("\tFlash didn't meet minimal version requirement (11.3).");
			}

			this.mode = false; // with falsy mode, runtime won't operable, no matter what the mode was before
		}

		Basic.extend(this, {

			getShim: function() {
				return Dom.get(this.uid);
			},

			shimExec: function(component, action) {
				var args = [].slice.call(arguments, 2);
				return I.getShim().exec(this.uid, component, action, args);
			},

			init: function() {
				var html, el, container;

				container = this.getShimContainer();

				// if not the minimal height, shims are not initialized in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
				Basic.extend(container.style, {
					position: 'absolute',
					top: '-8px',
					left: '-8px',
					width: '9px',
					height: '9px',
					overflow: 'hidden'
				});

				// insert flash object
				html = '<object id="' + this.uid + '" type="application/x-shockwave-flash" data="' +  options.swf_url + '" ';

				if (Env.browser === 'IE') {
					html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
				}

				html += 'width="100%" height="100%" style="outline:0">'  +
					'<param name="movie" value="' + options.swf_url + '" />' +
					'<param name="flashvars" value="uid=' + escape(this.uid) + '&target=' + Runtime.getGlobalEventTarget() + '" />' +
					'<param name="wmode" value="transparent" />' +
					'<param name="allowscriptaccess" value="always" />' +
				'</object>';

				if (Env.browser === 'IE') {
					el = document.createElement('div');
					container.appendChild(el);
					el.outerHTML = html;
					el = container = null; // just in case
				} else {
					container.innerHTML = html;
				}

				// Init is dispatched by the shim
				initTimer = setTimeout(function() {
					if (I && !I.initialized) { // runtime might be already destroyed by this moment
						I.trigger("Error", new x.RuntimeError(x.RuntimeError.NOT_INIT_ERR));

						if (MXI_DEBUG && Env.debug.runtime) {
							Env.log("\tFlash failed to initialize within a specified period of time (typically 5s).");
						}
					}
				}, 5000);
			},

			destroy: (function(destroy) { // extend default destroy method
				return function() {
					removeSWF(I.uid); // SWF removal requires special care in IE

					destroy.call(I);
					clearTimeout(initTimer); // initialization check might be still onwait
					options = initTimer = destroy = I = null;
				};
			}(this.destroy))

		}, extensions);
	}

	Runtime.addConstructor(type, FlashRuntime);

	return extensions;
});


/**
 * Url.js
 *
 * Copyright 2013, Moxiecode Systems AB
 * Released under GPL License.
 *
 * License: http://www.plupload.com/license
 * Contributing: http://www.plupload.com/contributing
 */

/**
@class moxie/core/utils/Url
@public
@static
*/

define('moxie/core/utils/Url', [
	'moxie/core/utils/Basic'
], function(Basic) {
	/**
	Parse url into separate components and fill in absent parts with parts from current url,
	based on https://raw.github.com/kvz/phpjs/master/functions/url/parse_url.js

	@method parseUrl
	@static
	@param {String} url Url to parse (defaults to empty string if undefined)
	@return {Object} Hash containing extracted uri components
	*/
	var parseUrl = function(url, currentUrl) {
		var key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment']
		, i = key.length
		, ports = {
			http: 80,
			https: 443
		}
		, uri = {}
		, regex = /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@\/]*):?([^:@\/]*))?@)?(\[[\da-fA-F:]+\]|[^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/
		, m = regex.exec(url || '')
		, isRelative
		, isSchemeLess = /^\/\/\w/.test(url)
		;

		switch (Basic.typeOf(currentUrl)) {
			case 'undefined':
				currentUrl = parseUrl(document.location.href, false);
				break;

			case 'string':
				currentUrl = parseUrl(currentUrl, false);
				break;
		}

		while (i--) {
			if (m[i]) {
				uri[key[i]] = m[i];
			}
		}

		isRelative = !isSchemeLess && !uri.scheme;

		if (isSchemeLess || isRelative) {
			uri.scheme = currentUrl.scheme;
		}

		// when url is relative, we set the origin and the path ourselves
		if (isRelative) {
			uri.host = currentUrl.host;
			uri.port = currentUrl.port;

			var path = '';
			// for urls without trailing slash we need to figure out the path
			if (/^[^\/]/.test(uri.path)) {
				path = currentUrl.path;
				// if path ends with a filename, strip it
				if (/\/[^\/]*\.[^\/]*$/.test(path)) {
					path = path.replace(/\/[^\/]+$/, '/');
				} else {
					// avoid double slash at the end (see #127)
					path = path.replace(/\/?$/, '/');
				}
			}
			uri.path = path + (uri.path || ''); // site may reside at domain.com or domain.com/subdir
		}

		if (!uri.port) {
			uri.port = ports[uri.scheme] || 80;
		}

		uri.port = parseInt(uri.port, 10);

		if (!uri.path) {
			uri.path = "/";
		}

		delete uri.source;

		return uri;
	};

	/**
	Resolve url - among other things will turn relative url to absolute

	@method resolveUrl
	@static
	@param {String|Object} url Either absolute or relative, or a result of parseUrl call
	@return {String} Resolved, absolute url
	*/
	var resolveUrl = function(url) {
		var ports = { // we ignore default ports
			http: 80,
			https: 443
		}
		, urlp = typeof(url) === 'object' ? url : parseUrl(url);
		;

		return urlp.scheme + '://' + urlp.host + (urlp.port !== ports[urlp.scheme] ? ':' + urlp.port : '') + urlp.path + (urlp.query ? urlp.query : '');
	};

	/**
	Check if specified url has the same origin as the current document

	@method hasSameOrigin
	@static
	@param {String|Object} url
	@return {Boolean}
	*/
	var hasSameOrigin = function(url) {
		function origin(url) {
			return [url.scheme, url.host, url.port].join('/');
		}

		if (typeof url === 'string') {
			url = parseUrl(url);
		}

		return origin(parseUrl()) === origin(url);
	};

	return {
		parseUrl: parseUrl,
		resolveUrl: resolveUrl,
		hasSameOrigin: hasSameOrigin
	};
});