text: /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git
   * FileAPI — a set of  javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF.
   */
  
  /*
   * JavaScript Canvas to Blob 2.0.5
   * https://github.com/blueimp/JavaScript-Canvas-to-Blob
   *
   * Copyright 2012, Sebastian Tschan
   * https://blueimp.net
   *
   * Licensed under the MIT license:
   * http://www.opensource.org/licenses/MIT
   *
   * Based on stackoverflow user Stoive's code snippet:
   * http://stackoverflow.com/q/4998908
   */
  
  /*jslint nomen: true, regexp: true */
  /*global window, atob, Blob, ArrayBuffer, Uint8Array */
  
  (function (window) {
      'use strict';
      var CanvasPrototype = window.HTMLCanvasElement &&
              window.HTMLCanvasElement.prototype,
          hasBlobConstructor = window.Blob && (function () {
              try {
                  return Boolean(new Blob());
              } catch (e) {
                  return false;
              }
          }()),
          hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
              (function () {
                  try {
                      return new Blob([new Uint8Array(100)]).size === 100;
                  } catch (e) {
                      return false;
                  }
              }()),
          BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
              window.MozBlobBuilder || window.MSBlobBuilder,
          dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
              window.ArrayBuffer && window.Uint8Array && function (dataURI) {
                  var byteString,
                      arrayBuffer,
                      intArray,
                      i,
                      mimeString,
                      bb;
                  if (dataURI.split(',')[0].indexOf('base64') >= 0) {
                      // Convert base64 to raw binary data held in a string:
                      byteString = atob(dataURI.split(',')[1]);
                  } else {
                      // Convert base64/URLEncoded data component to raw binary data:
                      byteString = decodeURIComponent(dataURI.split(',')[1]);
                  }
                  // Write the bytes of the string to an ArrayBuffer:
                  arrayBuffer = new ArrayBuffer(byteString.length);
                  intArray = new Uint8Array(arrayBuffer);
                  for (i = 0; i < byteString.length; i += 1) {
                      intArray[i] = byteString.charCodeAt(i);
                  }
                  // Separate out the mime component:
                  mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
                  // Write the ArrayBuffer (or ArrayBufferView) to a blob:
                  if (hasBlobConstructor) {
                      return new Blob(
                          [hasArrayBufferViewSupport ? intArray : arrayBuffer],
                          {type: mimeString}
                      );
                  }
                  bb = new BlobBuilder();
                  bb.append(arrayBuffer);
                  return bb.getBlob(mimeString);
              };
      if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
          if (CanvasPrototype.mozGetAsFile) {
              CanvasPrototype.toBlob = function (callback, type, quality) {
                  if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
                      callback(dataURLtoBlob(this.toDataURL(type, quality)));
                  } else {
                      callback(this.mozGetAsFile('blob', type));
                  }
              };
          } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
              CanvasPrototype.toBlob = function (callback, type, quality) {
                  callback(dataURLtoBlob(this.toDataURL(type, quality)));
              };
          }
      }
      window.dataURLtoBlob = dataURLtoBlob;
  })(window);
  
  /*jslint evil: true */
  /*global window, URL, webkitURL, ActiveXObject */
  
  (function (window, undef){
  	'use strict';
  
  	var
  		gid = 1,
  		noop = function (){},
  
  		document = window.document,
  		doctype = document.doctype || {},
  		userAgent = window.navigator.userAgent,
  
  		// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
  		apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
  
  		Blob = window.Blob,
  		File = window.File,
  		FileReader = window.FileReader,
  		FormData = window.FormData,
  
  
  		XMLHttpRequest = window.XMLHttpRequest,
  		jQuery = window.jQuery,
  
  		html5 =    !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
  				&& !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
  
  		cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
  
  		chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
  
  		// https://github.com/blueimp/JavaScript-Canvas-to-Blob
  		dataURLtoBlob = window.dataURLtoBlob,
  
  
  		_rimg = /img/i,
  		_rcanvas = /canvas/i,
  		_rimgcanvas = /img|canvas/i,
  		_rinput = /input/i,
  		_rdata = /^data:[^,]+,/,
  
  		_toString = {}.toString,
  
  
  		Math = window.Math,
  
  		_SIZE_CONST = function (pow){
  			pow = new window.Number(Math.pow(1024, pow));
  			pow.from = function (sz){ return Math.round(sz * this); };
  			return	pow;
  		},
  
  		_elEvents = {}, // element event listeners
  		_infoReader = [], // list of file info processors
  
  		_readerEvents = 'abort progress error load loadend',
  		_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
  
  		currentTarget = 'currentTarget', // for minimize
  		preventDefault = 'preventDefault', // and this too
  
  		_isArray = function (ar) {
  			return	ar && ('length' in ar);
  		},
  
  		/**
  		 * Iterate over a object or array
  		 */
  		_each = function (obj, fn, ctx){
  			if( obj ){
  				if( _isArray(obj) ){
  					for( var i = 0, n = obj.length; i < n; i++ ){
  						if( i in obj ){
  							fn.call(ctx, obj[i], i, obj);
  						}
  					}
  				}
  				else {
  					for( var key in obj ){
  						if( obj.hasOwnProperty(key) ){
  							fn.call(ctx, obj[key], key, obj);
  						}
  					}
  				}
  			}
  		},
  
  		/**
  		 * Merge the contents of two or more objects together into the first object
  		 */
  		_extend = function (dst){
  			var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
  			for( ; i < args.length; i++ ){
  				_each(args[i], _ext);
  			}
  			return  dst;
  		},
  
  		/**
  		 * Add event listener
  		 */
  		_on = function (el, type, fn){
  			if( el ){
  				var uid = api.uid(el);
  
  				if( !_elEvents[uid] ){
  					_elEvents[uid] = {};
  				}
  
  				var isFileReader = (FileReader && el) && (el instanceof FileReader);
  				_each(type.split(/\s+/), function (type){
  					if( jQuery && !isFileReader){
  						jQuery.event.add(el, type, fn);
  					} else {
  						if( !_elEvents[uid][type] ){
  							_elEvents[uid][type] = [];
  						}
  
  						_elEvents[uid][type].push(fn);
  
  						if( el.addEventListener ){ el.addEventListener(type, fn, false); }
  						else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
  						else { el['on'+type] = fn; }
  					}
  				});
  			}
  		},
  
  
  		/**
  		 * Remove event listener
  		 */
  		_off = function (el, type, fn){
  			if( el ){
  				var uid = api.uid(el), events = _elEvents[uid] || {};
  
  				var isFileReader = (FileReader && el) && (el instanceof FileReader);
  				_each(type.split(/\s+/), function (type){
  					if( jQuery && !isFileReader){
  						jQuery.event.remove(el, type, fn);
  					}
  					else {
  						var fns = events[type] || [], i = fns.length;
  
  						while( i-- ){
  							if( fns[i] === fn ){
  								fns.splice(i, 1);
  								break;
  							}
  						}
  
  						if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
  						else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
  						else { el['on'+type] = null; }
  					}
  				});
  			}
  		},
  
  
  		_one = function(el, type, fn){
  			_on(el, type, function _(evt){
  				_off(el, type, _);
  				fn(evt);
  			});
  		},
  
  
  		_fixEvent = function (evt){
  			if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
  			if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
  			return  evt;
  		},
  
  
  		_supportInputAttr = function (attr){
  			var input = document.createElement('input');
  			input.setAttribute('type', "file");
  			return attr in input;
  		},
  
  		/**
  		 * FileAPI (core object)
  		 */
  		api = {
  			version: '2.0.7',
  
  			cors: false,
  			html5: true,
  			media: false,
  			formData: true,
  			multiPassResize: true,
  
  			debug: false,
  			pingUrl: false,
  			multiFlash: false,
  			flashAbortTimeout: 0,
  			withCredentials: true,
  
  			staticPath: './dist/',
  
  			flashUrl: 0, // @default: './FileAPI.flash.swf'
  			flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
  
  			postNameConcat: function (name, idx){
  				return	name + (idx != null ? '['+ idx +']' : '');
  			},
  
  			ext2mime: {
  				  jpg:	'image/jpeg'
  				, tif:	'image/tiff'
  				, txt:	'text/plain'
  			},
  
  			// Fallback for flash
  			accept: {
  				  'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'
  				, 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'
  				, 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'
  			},
  
  			uploadRetry : 0,
  			networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
  
  			chunkSize : 0,
  			chunkUploadRetry : 0,
  			chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
  
  			KB: _SIZE_CONST(1),
  			MB: _SIZE_CONST(2),
  			GB: _SIZE_CONST(3),
  			TB: _SIZE_CONST(4),
  
  			EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
  
  			expando: 'fileapi' + (new Date).getTime(),
  
  			uid: function (obj){
  				return	obj
  					? (obj[api.expando] = obj[api.expando] || api.uid())
  					: (++gid, api.expando + gid)
  				;
  			},
  
  			log: function (){
  				// ngf fix for IE8 #1071
  				if( api.debug && api._supportConsoleLog ){
  					if( api._supportConsoleLogApply ){
  						console.log.apply(console, arguments);
  					}
  					else {
  						console.log([].join.call(arguments, ' '));
  					}
  				}
  			},
  
  			/**
  			 * Create new image
  			 *
  			 * @param {String} [src]
  			 * @param {Function} [fn]   1. error -- boolean, 2. img -- Image element
  			 * @returns {HTMLElement}
  			 */
  			newImage: function (src, fn){
  				var img = document.createElement('img');
  				if( fn ){
  					api.event.one(img, 'error load', function (evt){
  						fn(evt.type == 'error', img);
  						img = null;
  					});
  				}
  				img.src = src;
  				return	img;
  			},
  
  			/**
  			 * Get XHR
  			 * @returns {XMLHttpRequest}
  			 */
  			getXHR: function (){
  				var xhr;
  
  				if( XMLHttpRequest ){
  					xhr = new XMLHttpRequest;
  				}
  				else if( window.ActiveXObject ){
  					try {
  						xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
  					} catch (e) {
  						xhr = new ActiveXObject('Microsoft.XMLHTTP');
  					}
  				}
  
  				return  xhr;
  			},
  
  			isArray: _isArray,
  
  			support: {
  				dnd:     cors && ('ondrop' in document.createElement('div')),
  				cors:    cors,
  				html5:   html5,
  				chunked: chunked,
  				dataURI: true,
  				accept:   _supportInputAttr('accept'),
  				multiple: _supportInputAttr('multiple')
  			},
  
  			event: {
  				  on: _on
  				, off: _off
  				, one: _one
  				, fix: _fixEvent
  			},
  
  
  			throttle: function(fn, delay) {
  				var id, args;
  
  				return function _throttle(){
  					args = arguments;
  
  					if( !id ){
  						fn.apply(window, args);
  						id = setTimeout(function (){
  							id = 0;
  							fn.apply(window, args);
  						}, delay);
  					}
  				};
  			},
  
  
  			F: function (){},
  
  
  			parseJSON: function (str){
  				var json;
  				if( window.JSON && JSON.parse ){
  					json = JSON.parse(str);
  				}
  				else {
  					json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
  				}
  				return json;
  			},
  
  
  			trim: function (str){
  				str = String(str);
  				return	str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
  			},
  
  			/**
  			 * Simple Defer
  			 * @return	{Object}
  			 */
  			defer: function (){
  				var
  					  list = []
  					, result
  					, error
  					, defer = {
  						resolve: function (err, res){
  							defer.resolve = noop;
  							error	= err || false;
  							result	= res;
  
  							while( res = list.shift() ){
  								res(error, result);
  							}
  						},
  
  						then: function (fn){
  							if( error !== undef ){
  								fn(error, result);
  							} else {
  								list.push(fn);
  							}
  						}
  				};
  
  				return	defer;
  			},
  
  			queue: function (fn){
  				var
  					  _idx = 0
  					, _length = 0
  					, _fail = false
  					, _end = false
  					, queue = {
  						inc: function (){
  							_length++;
  						},
  
  						next: function (){
  							_idx++;
  							setTimeout(queue.check, 0);
  						},
  
  						check: function (){
  							(_idx >= _length) && !_fail && queue.end();
  						},
  
  						isFail: function (){
  							return _fail;
  						},
  
  						fail: function (){
  							!_fail && fn(_fail = true);
  						},
  
  						end: function (){
  							if( !_end ){
  								_end = true;
  								fn();
  							}
  						}
  					}
  				;
  				return queue;
  			},
  
  
  			/**
  			 * For each object
  			 *
  			 * @param	{Object|Array}	obj
  			 * @param	{Function}		fn
  			 * @param	{*}				[ctx]
  			 */
  			each: _each,
  
  
  			/**
  			 * Async for
  			 * @param {Array} array
  			 * @param {Function} callback
  			 */
  			afor: function (array, callback){
  				var i = 0, n = array.length;
  
  				if( _isArray(array) && n-- ){
  					(function _next(){
  						callback(n != i && _next, array[i], i++);
  					})();
  				}
  				else {
  					callback(false);
  				}
  			},
  
  
  			/**
  			 * Merge the contents of two or more objects together into the first object
  			 *
  			 * @param	{Object}	dst
  			 * @return	{Object}
  			 */
  			extend: _extend,
  
  
  			/**
  			 * Is file?
  			 * @param  {File}  file
  			 * @return {Boolean}
  			 */
  			isFile: function (file){
  				return _toString.call(file) === '[object File]';
  			},
  
  
  			/**
  			 * Is blob?
  			 * @param   {Blob}  blob
  			 * @returns {Boolean}
  			 */
  			isBlob: function (blob) {
  				return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
  			},
  
  
  			/**
  			 * Is canvas element
  			 *
  			 * @param	{HTMLElement}	el
  			 * @return	{Boolean}
  			 */
  			isCanvas: function (el){
  				return	el && _rcanvas.test(el.nodeName);
  			},
  
  
  			getFilesFilter: function (filter){
  				filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
  				return	filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
  			},
  
  
  
  			/**
  			 * Read as DataURL
  			 *
  			 * @param {File|Element} file
  			 * @param {Function} fn
  			 */
  			readAsDataURL: function (file, fn){
  				if( api.isCanvas(file) ){
  					_emit(file, fn, 'load', api.toDataURL(file));
  				}
  				else {
  					_readAs(file, fn, 'DataURL');
  				}
  			},
  
  
  			/**
  			 * Read as Binary string
  			 *
  			 * @param {File} file
  			 * @param {Function} fn
  			 */
  			readAsBinaryString: function (file, fn){
  				if( _hasSupportReadAs('BinaryString') ){
  					_readAs(file, fn, 'BinaryString');
  				} else {
  					// Hello IE10!
  					_readAs(file, function (evt){
  						if( evt.type == 'load' ){
  							try {
  								// dataURL -> binaryString
  								evt.result = api.toBinaryString(evt.result);
  							} catch (e){
  								evt.type = 'error';
  								evt.message = e.toString();
  							}
  						}
  						fn(evt);
  					}, 'DataURL');
  				}
  			},
  
  
  			/**
  			 * Read as ArrayBuffer
  			 *
  			 * @param {File} file
  			 * @param {Function} fn
  			 */
  			readAsArrayBuffer: function(file, fn){
  				_readAs(file, fn, 'ArrayBuffer');
  			},
  
  
  			/**
  			 * Read as text
  			 *
  			 * @param {File} file
  			 * @param {String} encoding
  			 * @param {Function} [fn]
  			 */
  			readAsText: function(file, encoding, fn){
  				if( !fn ){
  					fn	= encoding;
  					encoding = 'utf-8';
  				}
  
  				_readAs(file, fn, 'Text', encoding);
  			},
  
  
  			/**
  			 * Convert image or canvas to DataURL
  			 *
  			 * @param   {Element}  el      Image or Canvas element
  			 * @param   {String}   [type]  mime-type
  			 * @return  {String}
  			 */
  			toDataURL: function (el, type){
  				if( typeof el == 'string' ){
  					return  el;
  				}
  				else if( el.toDataURL ){
  					return  el.toDataURL(type || 'image/png');
  				}
  			},
  
  
  			/**
  			 * Canvert string, image or canvas to binary string
  			 *
  			 * @param   {String|Element} val
  			 * @return  {String}
  			 */
  			toBinaryString: function (val){
  				return  window.atob(api.toDataURL(val).replace(_rdata, ''));
  			},
  
  
  			/**
  			 * Read file or DataURL as ImageElement
  			 *
  			 * @param	{File|String}	file
  			 * @param	{Function}		fn
  			 * @param	{Boolean}		[progress]
  			 */
  			readAsImage: function (file, fn, progress){
  				if( api.isFile(file) ){
  					if( apiURL ){
  						/** @namespace apiURL.createObjectURL */
  						var data = apiURL.createObjectURL(file);
  						if( data === undef ){
  							_emit(file, fn, 'error');
  						}
  						else {
  							api.readAsImage(data, fn, progress);
  						}
  					}
  					else {
  						api.readAsDataURL(file, function (evt){
  							if( evt.type == 'load' ){
  								api.readAsImage(evt.result, fn, progress);
  							}
  							else if( progress || evt.type == 'error' ){
  								_emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
  							}
  						});
  					}
  				}
  				else if( api.isCanvas(file) ){
  					_emit(file, fn, 'load', file);
  				}
  				else if( _rimg.test(file.nodeName) ){
  					if( file.complete ){
  						_emit(file, fn, 'load', file);
  					}
  					else {
  						var events = 'error abort load';
  						_one(file, events, function _fn(evt){
  							if( evt.type == 'load' && apiURL ){
  								/** @namespace apiURL.revokeObjectURL */
  								apiURL.revokeObjectURL(file.src);
  							}
  
  							_off(file, events, _fn);
  							_emit(file, fn, evt, file);
  						});
  					}
  				}
  				else if( file.iframe ){
  					_emit(file, fn, { type: 'error' });
  				}
  				else {
  					// Created image
  					var img = api.newImage(file.dataURL || file);
  					api.readAsImage(img, fn, progress);
  				}
  			},
  
  
  			/**
  			 * Make file by name
  			 *
  			 * @param	{String}	name
  			 * @return	{Array}
  			 */
  			checkFileObj: function (name){
  				var file = {}, accept = api.accept;
  
  				if( typeof name == 'object' ){
  					file = name;
  				}
  				else {
  					file.name = (name + '').split(/\\|\//g).pop();
  				}
  
  				if( file.type == null ){
  					file.type = file.name.split('.').pop();
  				}
  
  				_each(accept, function (ext, type){
  					ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
  					if( ext.test(file.type) || api.ext2mime[file.type] ){
  						file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
  					}
  				});
  
  				return	file;
  			},
  
  
  			/**
  			 * Get drop files
  			 *
  			 * @param	{Event}	evt
  			 * @param	{Function} callback
  			 */
  			getDropFiles: function (evt, callback){
  				var
  					  files = []
  					, dataTransfer = _getDataTransfer(evt)
  					, entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
  					, queue = api.queue(function (){ callback(files); })
  				;
  
  				_each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
  					queue.inc();
  
  					try {
  						if( entrySupport ){
  							_readEntryAsFiles(item, function (err, entryFiles){
  								if( err ){
  									api.log('[err] getDropFiles:', err);
  								} else {
  									files.push.apply(files, entryFiles);
  								}
  								queue.next();
  							});
  						}
  						else {
  							_isRegularFile(item, function (yes){
  								yes && files.push(item);
  								queue.next();
  							});
  						}
  					}
  					catch( err ){
  						queue.next();
  						api.log('[err] getDropFiles: ', err);
  					}
  				});
  
  				queue.check();
  			},
  
  
  			/**
  			 * Get file list
  			 *
  			 * @param	{HTMLInputElement|Event}	input
  			 * @param	{String|Function}	[filter]
  			 * @param	{Function}			[callback]
  			 * @return	{Array|Null}
  			 */
  			getFiles: function (input, filter, callback){
  				var files = [];
  
  				if( callback ){
  					api.filterFiles(api.getFiles(input), filter, callback);
  					return null;
  				}
  
  				if( input.jquery ){
  					// jQuery object
  					input.each(function (){
  						files = files.concat(api.getFiles(this));
  					});
  					input	= files;
  					files	= [];
  				}
  
  				if( typeof filter == 'string' ){
  					filter	= api.getFilesFilter(filter);
  				}
  
  				if( input.originalEvent ){
  					// jQuery event
  					input = _fixEvent(input.originalEvent);
  				}
  				else if( input.srcElement ){
  					// IE Event
  					input = _fixEvent(input);
  				}
  
  
  				if( input.dataTransfer ){
  					// Drag'n'Drop
  					input = input.dataTransfer;
  				}
  				else if( input.target ){
  					// Event
  					input = input.target;
  				}
  
  				if( input.files ){
  					// Input[type="file"]
  					files = input.files;
  
  					if( !html5 ){
  						// Partial support for file api
  						files[0].blob	= input;
  						files[0].iframe	= true;
  					}
  				}
  				else if( !html5 && isInputFile(input) ){
  					if( api.trim(input.value) ){
  						files = [api.checkFileObj(input.value)];
  						files[0].blob   = input;
  						files[0].iframe = true;
  					}
  				}
  				else if( _isArray(input) ){
  					files	= input;
  				}
  
  				return	api.filter(files, function (file){ return !filter || filter.test(file.name); });
  			},
  
  
  			/**
  			 * Get total file size
  			 * @param	{Array}	files
  			 * @return	{Number}
  			 */
  			getTotalSize: function (files){
  				var size = 0, i = files && files.length;
  				while( i-- ){
  					size += files[i].size;
  				}
  				return	size;
  			},
  
  
  			/**
  			 * Get image information
  			 *
  			 * @param	{File}		file
  			 * @param	{Function}	fn
  			 */
  			getInfo: function (file, fn){
  				var info = {}, readers = _infoReader.concat();
  
  				if( api.isFile(file) ){
  					(function _next(){
  						var reader = readers.shift();
  						if( reader ){
  							if( reader.test(file.type) ){
  								reader(file, function (err, res){
  									if( err ){
  										fn(err);
  									}
  									else {
  										_extend(info, res);
  										_next();
  									}
  								});
  							}
  							else {
  								_next();
  							}
  						}
  						else {
  							fn(false, info);
  						}
  					})();
  				}
  				else {
  					fn('not_support_info', info);
  				}
  			},
  
  
  			/**
  			 * Add information reader
  			 *
  			 * @param {RegExp} mime
  			 * @param {Function} fn
  			 */
  			addInfoReader: function (mime, fn){
  				fn.test = function (type){ return mime.test(type); };
  				_infoReader.push(fn);
  			},
  
  
  			/**
  			 * Filter of array
  			 *
  			 * @param	{Array}		input
  			 * @param	{Function}	fn
  			 * @return	{Array}
  			 */
  			filter: function (input, fn){
  				var result = [], i = 0, n = input.length, val;
  
  				for( ; i < n; i++ ){
  					if( i in input ){
  						val = input[i];
  						if( fn.call(val, val, i, input) ){
  							result.push(val);
  						}
  					}
  				}
  
  				return	result;
  			},
  
  
  			/**
  			 * Filter files
  			 *
  			 * @param	{Array}		files
  			 * @param	{Function}	eachFn
  			 * @param	{Function}	resultFn
  			 */
  			filterFiles: function (files, eachFn, resultFn){
  				if( files.length ){
  					// HTML5 or Flash
  					var queue = files.concat(), file, result = [], deleted = [];
  
  					(function _next(){
  						if( queue.length ){
  							file = queue.shift();
  							api.getInfo(file, function (err, info){
  								(eachFn(file, err ? false : info) ? result : deleted).push(file);
  								_next();
  							});
  						}
  						else {
  							resultFn(result, deleted);
  						}
  					})();
  				}
  				else {
  					resultFn([], files);
  				}
  			},
  
  
  			upload: function (options){
  				options = _extend({
  					  jsonp: 'callback'
  					, prepare: api.F
  					, beforeupload: api.F
  					, upload: api.F
  					, fileupload: api.F
  					, fileprogress: api.F
  					, filecomplete: api.F
  					, progress: api.F
  					, complete: api.F
  					, pause: api.F
  					, imageOriginal: true
  					, chunkSize: api.chunkSize
  					, chunkUploadRetry: api.chunkUploadRetry
  					, uploadRetry: api.uploadRetry
  				}, options);
  
  
  				if( options.imageAutoOrientation && !options.imageTransform ){
  					options.imageTransform = { rotate: 'auto' };
  				}
  
  
  				var
  					  proxyXHR = new api.XHR(options)
  					, dataArray = this._getFilesDataArray(options.files)
  					, _this = this
  					, _total = 0
  					, _loaded = 0
  					, _nextFile
  					, _complete = false
  				;
  
  
  				// calc total size
  				_each(dataArray, function (data){
  					_total += data.size;
  				});
  
  				// Array of files
  				proxyXHR.files = [];
  				_each(dataArray, function (data){
  					proxyXHR.files.push(data.file);
  				});
  
  				// Set upload status props
  				proxyXHR.total	= _total;
  				proxyXHR.loaded	= 0;
  				proxyXHR.filesLeft = dataArray.length;
  
  				// emit "beforeupload"  event
  				options.beforeupload(proxyXHR, options);
  
  				// Upload by file
  				_nextFile = function (){
  					var
  						  data = dataArray.shift()
  						, _file = data && data.file
  						, _fileLoaded = false
  						, _fileOptions = _simpleClone(options)
  					;
  
  					proxyXHR.filesLeft = dataArray.length;
  
  					if( _file && _file.name === api.expando ){
  						_file = null;
  						api.log('[warn] FileAPI.upload() — called without files');
  					}
  
  					if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
  						// Mark active job
  						_complete = false;
  
  						// Set current upload file
  						proxyXHR.currentFile = _file;
  
  						// Prepare file options
  						if (_file && options.prepare(_file, _fileOptions) === false) {
  							_nextFile.call(_this);
  							return;
  						}
  						_fileOptions.file = _file;
  
  						_this._getFormData(_fileOptions, data, function (form){
  							if( !_loaded ){
  								// emit "upload" event
  								options.upload(proxyXHR, options);
  							}
  
  							var xhr = new api.XHR(_extend({}, _fileOptions, {
  
  								upload: _file ? function (){
  									// emit "fileupload" event
  									options.fileupload(_file, xhr, _fileOptions);
  								} : noop,
  
  								progress: _file ? function (evt){
  									if( !_fileLoaded ){
  										// For ignore the double calls.
  										_fileLoaded = (evt.loaded === evt.total);
  
  										// emit "fileprogress" event
  										options.fileprogress({
  											  type:   'progress'
  											, total:  data.total = evt.total
  											, loaded: data.loaded = evt.loaded
  										}, _file, xhr, _fileOptions);
  
  										// emit "progress" event
  										options.progress({
  											  type:   'progress'
  											, total:  _total
  											, loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
  										}, _file, xhr, _fileOptions);
  									}
  								} : noop,
  
  								complete: function (err){
  									_each(_xhrPropsExport, function (name){
  										proxyXHR[name] = xhr[name];
  									});
  
  									if( _file ){
  										data.total = (data.total || data.size);
  										data.loaded	= data.total;
  
  										if( !err ) {
  											// emulate 100% "progress"
  											this.progress(data);
  
  											// fixed throttle event
  											_fileLoaded = true;
  
  											// bytes loaded
  											_loaded += data.size; // data.size != data.total, it's desirable fix this
  											proxyXHR.loaded = _loaded;
  										}
  
  										// emit "filecomplete" event
  										options.filecomplete(err, xhr, _file, _fileOptions);
  									}
  
  									// upload next file
  									setTimeout(function () {_nextFile.call(_this);}, 0);
  								}
  							})); // xhr
  
  
  							// ...
  							proxyXHR.abort = function (current){
  								if (!current) { dataArray.length = 0; }
  								this.current = current;
  								xhr.abort();
  							};
  
  							// Start upload
  							xhr.send(form);
  						});
  					}
  					else {
  						var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
  						options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
  						// Mark done state
  						_complete = true;
  					}
  				};
  
  
  				// Next tick
  				setTimeout(_nextFile, 0);
  
  
  				// Append more files to the existing request
  				// first - add them to the queue head/tail
  				proxyXHR.append = function (files, first) {
  					files = api._getFilesDataArray([].concat(files));
  
  					_each(files, function (data) {
  						_total += data.size;
  						proxyXHR.files.push(data.file);
  						if (first) {
  							dataArray.unshift(data);
  						} else {
  							dataArray.push(data);
  						}
  					});
  
  					proxyXHR.statusText = "";
  
  					if( _complete ){
  						_nextFile.call(_this);
  					}
  				};
  
  
  				// Removes file from queue by file reference and returns it
  				proxyXHR.remove = function (file) {
  				    var i = dataArray.length, _file;
  				    while( i-- ){
  						if( dataArray[i].file == file ){
  							_file = dataArray.splice(i, 1);
  							_total -= _file.size;
  						}
  					}
  					return	_file;
  				};
  
  				return proxyXHR;
  			},
  
  
  			_getFilesDataArray: function (data){
  				var files = [], oFiles = {};
  
  				if( isInputFile(data) ){
  					var tmp = api.getFiles(data);
  					oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
  				}
  				else if( _isArray(data) && isInputFile(data[0]) ){
  					_each(data, function (input){
  						oFiles[input.name || 'file'] = api.getFiles(input);
  					});
  				}
  				else {
  					oFiles = data;
  				}
  
  				_each(oFiles, function add(file, name){
  					if( _isArray(file) ){
  						_each(file, function (file){
  							add(file, name);
  						});
  					}
  					else if( file && (file.name || file.image) ){
  						files.push({
  							  name: name
  							, file: file
  							, size: file.size
  							, total: file.size
  							, loaded: 0
  						});
  					}
  				});
  
  				if( !files.length ){
  					// Create fake `file` object
  					files.push({ file: { name: api.expando } });
  				}
  
  				return	files;
  			},
  
  
  			_getFormData: function (options, data, fn){
  				var
  					  file = data.file
  					, name = data.name
  					, filename = file.name
  					, filetype = file.type
  					, trans = api.support.transform && options.imageTransform
  					, Form = new api.Form
  					, queue = api.queue(function (){ fn(Form); })
  					, isOrignTrans = trans && _isOriginTransform(trans)
  					, postNameConcat = api.postNameConcat
  				;
  
  				// Append data
  				_each(options.data, function add(val, name){
  					if( typeof val == 'object' ){
  						_each(val, function (v, i){
  							add(v, postNameConcat(name, i));
  						});
  					}
  					else {
  						Form.append(name, val);
  					}
  				});
  
  				(function _addFile(file/**Object*/){
  					if( file.image ){ // This is a FileAPI.Image
  						queue.inc();
  
  						file.toData(function (err, image){
  							// @todo: error
  							filename = filename || (new Date).getTime()+'.png';
  
  							_addFile(image);
  							queue.next();
  						});
  					}
  					else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
  						queue.inc();
  
  						if( isOrignTrans ){
  							// Convert to array for transform function
  							trans = [trans];
  						}
  
  						api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
  							if( isOrignTrans && !err ){
  								if( !dataURLtoBlob && !api.flashEngine ){
  									// Canvas.toBlob or Flash not supported, use multipart
  									Form.multipart = true;
  								}
  
  								Form.append(name, images[0], filename,  trans[0].type || filetype);
  							}
  							else {
  								var addOrigin = 0;
  
  								if( !err ){
  									_each(images, function (image, idx){
  										if( !dataURLtoBlob && !api.flashEngine ){
  											Form.multipart = true;
  										}
  
  										if( !trans[idx].postName ){
  											addOrigin = 1;
  										}
  
  										Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
  									});
  								}
  
  								if( err || options.imageOriginal ){
  									Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
  								}
  							}
  
  							queue.next();
  						});
  					}
  					else if( filename !== api.expando ){
  						Form.append(name, file, filename);
  					}
  				})(file);
  
  				queue.check();
  			},
  
  
  			reset: function (inp, notRemove){
  				var parent, clone;
  
  				if( jQuery ){
  					clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
  					if( !notRemove ){
  						jQuery(inp).remove();
  					}
  				} else {
  					parent  = inp.parentNode;
  					clone   = parent.insertBefore(inp.cloneNode(true), inp);
  					clone.value = '';
  
  					if( !notRemove ){
  						parent.removeChild(inp);
  					}
  
  					_each(_elEvents[api.uid(inp)], function (fns, type){
  						_each(fns, function (fn){
  							_off(inp, type, fn);
  							_on(clone, type, fn);
  						});
  					});
  				}
  
  				return  clone;
  			},
  
  
  			/**
  			 * Load remote file
  			 *
  			 * @param   {String}    url
  			 * @param   {Function}  fn
  			 * @return  {XMLHttpRequest}
  			 */
  			load: function (url, fn){
  				var xhr = api.getXHR();
  				if( xhr ){
  					xhr.open('GET', url, true);
  
  					if( xhr.overrideMimeType ){
  				        xhr.overrideMimeType('text/plain; charset=x-user-defined');
  					}
  
  					_on(xhr, 'progress', function (/**Event*/evt){
  						/** @namespace evt.lengthComputable */
  						if( evt.lengthComputable ){
  							fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
  						}
  					});
  
  					xhr.onreadystatechange = function(){
  						if( xhr.readyState == 4 ){
  							xhr.onreadystatechange = null;
  							if( xhr.status == 200 ){
  								url = url.split('/');
  								/** @namespace xhr.responseBody */
  								var file = {
  								      name: url[url.length-1]
  									, size: xhr.getResponseHeader('Content-Length')
  									, type: xhr.getResponseHeader('Content-Type')
  								};
  								file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
  								fn({ type: 'load', result: file }, xhr);
  							}
  							else {
  								fn({ type: 'error' }, xhr);
  							}
  					    }
  					};
  				    xhr.send(null);
  				} else {
  					fn({ type: 'error' });
  				}
  
  				return  xhr;
  			},
  
  			encode64: function (str){
  				var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
  
  				if( typeof str !== 'string' ){
  					str	= String(str);
  				}
  
  				while( i < str.length ){
  					//all three "& 0xff" added below are there to fix a known bug
  					//with bytes returned by xhr.responseText
  					var
  						  byte1 = str.charCodeAt(i++) & 0xff
  						, byte2 = str.charCodeAt(i++) & 0xff
  						, byte3 = str.charCodeAt(i++) & 0xff
  						, enc1 = byte1 >> 2
  						, enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
  						, enc3, enc4
  					;
  
  					if( isNaN(byte2) ){
  						enc3 = enc4 = 64;
  					} else {
  						enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
  						enc4 = isNaN(byte3) ? 64 : byte3 & 63;
  					}
  
  					outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
  				}
  
  				return  outStr;
  			}
  
  		} // api
  	;
  
  
  	function _emit(target, fn, name, res, ext){
  		var evt = {
  			  type:		name.type || name
  			, target:	target
  			, result:	res
  		};
  		_extend(evt, ext);
  		fn(evt);
  	}
  
  
  	function _hasSupportReadAs(as){
  		return	FileReader && !!FileReader.prototype['readAs'+as];
  	}
  
  
  	function _readAs(file, fn, as, encoding){
  		if( api.isBlob(file) && _hasSupportReadAs(as) ){
  			var Reader = new FileReader;
  
  			// Add event listener
  			_on(Reader, _readerEvents, function _fn(evt){
  				var type = evt.type;
  				if( type == 'progress' ){
  					_emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
  				}
  				else if( type == 'loadend' ){
  					_off(Reader, _readerEvents, _fn);
  					Reader = null;
  				}
  				else {
  					_emit(file, fn, evt, evt.target.result);
  				}
  			});
  
  
  			try {
  				// ReadAs ...
  				if( encoding ){
  					Reader['readAs'+as](file, encoding);
  				}
  				else {
  					Reader['readAs'+as](file);
  				}
  			}
  			catch (err){
  				_emit(file, fn, 'error', undef, { error: err.toString() });
  			}
  		}
  		else {
  			_emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as });
  		}
  	}
  
  
  	function _isRegularFile(file, callback){
  		// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
  		if( !file.type && (file.size % 4096) === 0 && (file.size <= 102400) ){
  			if( FileReader ){
  				try {
  					var Reader = new FileReader();
  
  					_one(Reader, _readerEvents, function (evt){
  						var isFile = evt.type != 'error';
  						callback(isFile);
  						if( isFile ){
  							Reader.abort();
  						}
  					});
  
  					Reader.readAsDataURL(file);
  				} catch( err ){
  					callback(false);
  				}
  			}
  			else {
  				callback(null);
  			}
  		}
  		else {
  			callback(true);
  		}
  	}
  
  
  	function _getAsEntry(item){
  		var entry;
  		if( item.getAsEntry ){ entry = item.getAsEntry(); }
  		else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
  		return	entry;
  	}
  
  
  	function _readEntryAsFiles(entry, callback){
  		if( !entry ){
  			// error
  			callback('invalid entry');
  		}
  		else if( entry.isFile ){
  			// Read as file
  			entry.file(function(file){
  				// success
  				file.fullPath = entry.fullPath;
  				callback(false, [file]);
  			}, function (err){
  				// error
  				callback('FileError.code: '+err.code);
  			});
  		}
  		else if( entry.isDirectory ){
  			var reader = entry.createReader(), result = [];
  
  			reader.readEntries(function(entries){
  				// success
  				api.afor(entries, function (next, entry){
  					_readEntryAsFiles(entry, function (err, files){
  						if( err ){
  							api.log(err);
  						}
  						else {
  							result = result.concat(files);
  						}
  
  						if( next ){
  							next();
  						}
  						else {
  							callback(false, result);
  						}
  					});
  				});
  			}, function (err){
  				// error
  				callback('directory_reader: ' + err);
  			});
  		}
  		else {
  			_readEntryAsFiles(_getAsEntry(entry), callback);
  		}
  	}
  
  
  	function _simpleClone(obj){
  		var copy = {};
  		_each(obj, function (val, key){
  			if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
  				val = _extend({}, val);
  			}
  			copy[key] = val;
  		});
  		return	copy;
  	}
  
  
  	function isInputFile(el){
  		return	_rinput.test(el && el.tagName);
  	}
  
  
  	function _getDataTransfer(evt){
  		return	(evt.originalEvent || evt || '').dataTransfer || {};
  	}
  
  
  	function _isOriginTransform(trans){
  		var key;
  		for( key in trans ){
  			if( trans.hasOwnProperty(key) ){
  				if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
  					return	true;
  				}
  			}
  		}
  		return	false;
  	}
  
  
  	// Add default image info reader
  	api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
  		if( !file.__dimensions ){
  			var defer = file.__dimensions = api.defer();
  
  			api.readAsImage(file, function (evt){
  				var img = evt.target;
  				defer.resolve(evt.type == 'load' ? false : 'error', {
  					  width:  img.width
  					, height: img.height
  				});
                  img.src = api.EMPTY_PNG;
  				img = null;
  			});
  		}
  
  		file.__dimensions.then(callback);
  	});
  
  
  	/**
  	 * Drag'n'Drop special event
  	 *
  	 * @param	{HTMLElement}	el
  	 * @param	{Function}		onHover
  	 * @param	{Function}		onDrop
  	 */
  	api.event.dnd = function (el, onHover, onDrop){
  		var _id, _type;
  
  		if( !onDrop ){
  			onDrop = onHover;
  			onHover = api.F;
  		}
  
  		if( FileReader ){
  			// Hover
  			_on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
  				var
  					  types = _getDataTransfer(evt).types
  					, i = types && types.length
  					, debounceTrigger = false
  				;
  
  				while( i-- ){
  					if( ~types[i].indexOf('File') ){
  						evt[preventDefault]();
  
  						if( _type !== evt.type ){
  							_type = evt.type; // Store current type of event
  
  							if( _type != 'dragleave' ){
  								onHover.call(evt[currentTarget], true, evt);
  							}
  
  							debounceTrigger = true;
  						}
  
  						break; // exit from "while"
  					}
  				}
  
  				if( debounceTrigger ){
  					clearTimeout(_id);
  					_id = setTimeout(function (){
  						onHover.call(evt[currentTarget], _type != 'dragleave', evt);
  					}, 50);
  				}
  			});
  
  
  			// Drop
  			_on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
  				evt[preventDefault]();
  
  				_type = 0;
  				onHover.call(evt[currentTarget], false, evt);
  
  				api.getDropFiles(evt, function (files){
  					onDrop.call(evt[currentTarget], files, evt);
  				});
  			});
  		}
  		else {
  			api.log("Drag'n'Drop -- not supported");
  		}
  	};
  
  
  	/**
  	 * Remove drag'n'drop
  	 * @param	{HTMLElement}	el
  	 * @param	{Function}		onHover
  	 * @param	{Function}		onDrop
  	 */
  	api.event.dnd.off = function (el, onHover, onDrop){
  		_off(el, 'dragenter dragleave dragover', onHover.ff);
  		_off(el, 'drop', onDrop.ff);
  	};
  
  
  	// Support jQuery
  	if( jQuery && !jQuery.fn.dnd ){
  		jQuery.fn.dnd = function (onHover, onDrop){
  			return this.each(function (){
  				api.event.dnd(this, onHover, onDrop);
  			});
  		};
  
  		jQuery.fn.offdnd = function (onHover, onDrop){
  			return this.each(function (){
  				api.event.dnd.off(this, onHover, onDrop);
  			});
  		};
  	}
  
  	// @export
  	window.FileAPI  = _extend(api, window.FileAPI);
  
  
  	// Debug info
  	api.log('FileAPI: ' + api.version);
  	api.log('protocol: ' + window.location.protocol);
  	api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
  
  
  	// @detect 'x-ua-compatible'
  	_each(document.getElementsByTagName('meta'), function (meta){
  		if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
  			api.log('meta.http-equiv: ' + meta.getAttribute('content'));
  		}
  	});
  
  
  	// configuration
  	try {
  		api._supportConsoleLog = !!console.log;
  		api._supportConsoleLogApply = !!console.log.apply;
  	} catch (err) {}
  
  	if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
  	if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
  	if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
  })(window, void 0);
  
  /*global window, FileAPI, document */
  
  (function (api, document, undef) {
  	'use strict';
  
  	var
  		min = Math.min,
  		round = Math.round,
  		getCanvas = function () { return document.createElement('canvas'); },
  		support = false,
  		exifOrientation = {
  			  8:	270
  			, 3:	180
  			, 6:	90
  			, 7:	270
  			, 4:	180
  			, 5:	90
  		}
  	;
  
  	try {
  		support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
  	}
  	catch (e){}
  
  
  	function Image(file){
  		if( file instanceof Image ){
  			var img = new Image(file.file);
  			api.extend(img.matrix, file.matrix);
  			return	img;
  		}
  		else if( !(this instanceof Image) ){
  			return	new Image(file);
  		}
  
  		this.file   = file;
  		this.size   = file.size || 100;
  
  		this.matrix	= {
  			sx: 0,
  			sy: 0,
  			sw: 0,
  			sh: 0,
  			dx: 0,
  			dy: 0,
  			dw: 0,
  			dh: 0,
  			resize: 0, // min, max OR preview
  			deg: 0,
  			quality: 1, // jpeg quality
  			filter: 0
  		};
  	}
  
  
  	Image.prototype = {
  		image: true,
  		constructor: Image,
  
  		set: function (attrs){
  			api.extend(this.matrix, attrs);
  			return	this;
  		},
  
  		crop: function (x, y, w, h){
  			if( w === undef ){
  				w	= x;
  				h	= y;
  				x = y = 0;
  			}
  			return	this.set({ sx: x, sy: y, sw: w, sh: h || w });
  		},
  
  		resize: function (w, h, strategy){
  			if( /min|max/.test(h) ){
  				strategy = h;
  				h = w;
  			}
  
  			return	this.set({ dw: w, dh: h || w, resize: strategy });
  		},
  
  		preview: function (w, h){
  			return	this.resize(w, h || w, 'preview');
  		},
  
  		rotate: function (deg){
  			return	this.set({ deg: deg });
  		},
  
  		filter: function (filter){
  			return	this.set({ filter: filter });
  		},
  
  		overlay: function (images){
  			return	this.set({ overlay: images });
  		},
  
  		clone: function (){
  			return	new Image(this);
  		},
  
  		_load: function (image, fn){
  			var self = this;
  
  			if( /img|video/i.test(image.nodeName) ){
  				fn.call(self, null, image);
  			}
  			else {
  				api.readAsImage(image, function (evt){
  					fn.call(self, evt.type != 'load', evt.result);
  				});
  			}
  		},
  
  		_apply: function (image, fn){
  			var
  				  canvas = getCanvas()
  				, m = this.getMatrix(image)
  				, ctx = canvas.getContext('2d')
  				, width = image.videoWidth || image.width
  				, height = image.videoHeight || image.height
  				, deg = m.deg
  				, dw = m.dw
  				, dh = m.dh
  				, w = width
  				, h = height
  				, filter = m.filter
  				, copy // canvas copy
  				, buffer = image
  				, overlay = m.overlay
  				, queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
  				, renderImageToCanvas = api.renderImageToCanvas
  			;
  
  			// Normalize angle
  			deg = deg - Math.floor(deg/360)*360;
  
  			// For `renderImageToCanvas`
  			image._type = this.file.type;
  
  			while(m.multipass && min(w/dw, h/dh) > 2 ){
  				w = (w/2 + 0.5)|0;
  				h = (h/2 + 0.5)|0;
  
  				copy = getCanvas();
  				copy.width  = w;
  				copy.height = h;
  
  				if( buffer !== image ){
  					renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
  					buffer = copy;
  				}
  				else {
  					buffer = copy;
  					renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
  					m.sx = m.sy = m.sw = m.sh = 0;
  				}
  			}
  
  
  			canvas.width  = (deg % 180) ? dh : dw;
  			canvas.height = (deg % 180) ? dw : dh;
  
  			canvas.type = m.type;
  			canvas.quality = m.quality;
  
  			ctx.rotate(deg * Math.PI / 180);
  			renderImageToCanvas(ctx.canvas, buffer
  				, m.sx, m.sy
  				, m.sw || buffer.width
  				, m.sh || buffer.height
  				, (deg == 180 || deg == 270 ? -dw : 0)
  				, (deg == 90 || deg == 180 ? -dh : 0)
  				, dw, dh
  			);
  			dw = canvas.width;
  			dh = canvas.height;
  
  			// Apply overlay
  			overlay && api.each([].concat(overlay), function (over){
  				queue.inc();
  				// preload
  				var img = new window.Image, fn = function (){
  					var
  						  x = over.x|0
  						, y = over.y|0
  						, w = over.w || img.width
  						, h = over.h || img.height
  						, rel = over.rel
  					;
  
  					// center  |  right  |  left
  					x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
  
  					// center  |  bottom  |  top
  					y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
  
  					api.event.off(img, 'error load abort', fn);
  
  					try {
  						ctx.globalAlpha = over.opacity || 1;
  						ctx.drawImage(img, x, y, w, h);
  					}
  					catch (er){}
  
  					queue.next();
  				};
  
  				api.event.on(img, 'error load abort', fn);
  				img.src = over.src;
  
  				if( img.complete ){
  					fn();
  				}
  			});
  
  			if( filter ){
  				queue.inc();
  				Image.applyFilter(canvas, filter, queue.next);
  			}
  
  			queue.check();
  		},
  
  		getMatrix: function (image){
  			var
  				  m  = api.extend({}, this.matrix)
  				, sw = m.sw = m.sw || image.videoWidth || image.naturalWidth ||  image.width
  				, sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
  				, dw = m.dw = m.dw || sw
  				, dh = m.dh = m.dh || sh
  				, sf = sw/sh, df = dw/dh
  				, strategy = m.resize
  			;
  
  			if( strategy == 'preview' ){
  				if( dw != sw || dh != sh ){
  					// Make preview
  					var w, h;
  
  					if( df >= sf ){
  						w	= sw;
  						h	= w / df;
  					} else {
  						h	= sh;
  						w	= h * df;
  					}
  
  					if( w != sw || h != sh ){
  						m.sx	= ~~((sw - w)/2);
  						m.sy	= ~~((sh - h)/2);
  						sw		= w;
  						sh		= h;
  					}
  				}
  			}
  			else if( strategy ){
  				if( !(sw > dw || sh > dh) ){
  					dw = sw;
  					dh = sh;
  				}
  				else if( strategy == 'min' ){
  					dw = round(sf < df ? min(sw, dw) : dh*sf);
  					dh = round(sf < df ? dw/sf : min(sh, dh));
  				}
  				else {
  					dw = round(sf >= df ? min(sw, dw) : dh*sf);
  					dh = round(sf >= df ? dw/sf : min(sh, dh));
  				}
  			}
  
  			m.sw = sw;
  			m.sh = sh;
  			m.dw = dw;
  			m.dh = dh;
  			m.multipass = api.multiPassResize;
  			return	m;
  		},
  
  		_trans: function (fn){
  			this._load(this.file, function (err, image){
  				if( err ){
  					fn(err);
  				}
  				else {
  					try {
  						this._apply(image, fn);
  					} catch (err){
  						api.log('[err] FileAPI.Image.fn._apply:', err);
  						fn(err);
  					}
  				}
  			});
  		},
  
  
  		get: function (fn){
  			if( api.support.transform ){
  				var _this = this, matrix = _this.matrix;
  
  				if( matrix.deg == 'auto' ){
  					api.getInfo(_this.file, function (err, info){
  						// rotate by exif orientation
  						matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
  						_this._trans(fn);
  					});
  				}
  				else {
  					_this._trans(fn);
  				}
  			}
  			else {
  				fn('not_support_transform');
  			}
  
  			return this;
  		},
  
  
  		toData: function (fn){
  			return this.get(fn);
  		}
  
  	};
  
  
  	Image.exifOrientation = exifOrientation;
  
  
  	Image.transform = function (file, transform, autoOrientation, fn){
  		function _transform(err, img){
  			// img -- info object
  			var
  				  images = {}
  				, queue = api.queue(function (err){
  					fn(err, images);
  				})
  			;
  
  			if( !err ){
  				api.each(transform, function (params, name){
  					if( !queue.isFail() ){
  						var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
  
  						if( isFn ){
  							params(img, ImgTrans);
  						}
  						else if( params.width ){
  							ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
  						}
  						else {
  							if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
  								ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
  							}
  						}
  
  						if( params.crop ){
  							var crop = params.crop;
  							ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
  						}
  
  						if( params.rotate === undef && autoOrientation ){
  							params.rotate = 'auto';
  						}
  
  						ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
  
  						if( !isFn ){
  							ImgTrans.set({
  								  deg: params.rotate
  								, overlay: params.overlay
  								, filter: params.filter
  								, quality: params.quality || 1
  							});
  						}
  
  						queue.inc();
  						ImgTrans.toData(function (err, image){
  							if( err ){
  								queue.fail();
  							}
  							else {
  								images[name] = image;
  								queue.next();
  							}
  						});
  					}
  				});
  			}
  			else {
  				queue.fail();
  			}
  		}
  
  
  		// @todo: Оло-ло, нужно рефакторить это место
  		if( file.width ){
  			_transform(false, file);
  		} else {
  			api.getInfo(file, _transform);
  		}
  	};
  
  
  	// @const
  	api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
  		api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
  			Image[x+'_'+y] = i*3 + j;
  			Image[y+'_'+x] = i*3 + j;
  		});
  	});
  
  
  	/**
  	 * Trabsform element to canvas
  	 *
  	 * @param    {Image|HTMLVideoElement}   el
  	 * @returns  {Canvas}
  	 */
  	Image.toCanvas = function(el){
  		var canvas		= document.createElement('canvas');
  		canvas.width	= el.videoWidth || el.width;
  		canvas.height	= el.videoHeight || el.height;
  		canvas.getContext('2d').drawImage(el, 0, 0);
  		return	canvas;
  	};
  
  
  	/**
  	 * Create image from DataURL
  	 * @param  {String}  dataURL
  	 * @param  {Object}  size
  	 * @param  {Function}  callback
  	 */
  	Image.fromDataURL = function (dataURL, size, callback){
  		var img = api.newImage(dataURL);
  		api.extend(img, size);
  		callback(img);
  	};
  
  
  	/**
  	 * Apply filter (caman.js)
  	 *
  	 * @param  {Canvas|Image}   canvas
  	 * @param  {String|Function}  filter
  	 * @param  {Function}  doneFn
  	 */
  	Image.applyFilter = function (canvas, filter, doneFn){
  		if( typeof filter == 'function' ){
  			filter(canvas, doneFn);
  		}
  		else if( window.Caman ){
  			// http://camanjs.com/guides/
  			window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
  				if( typeof filter == 'string' ){
  					this[filter]();
  				}
  				else {
  					api.each(filter, function (val, method){
  						this[method](val);
  					}, this);
  				}
  				this.render(doneFn);
  			});
  		}
  	};
  
  
  	/**
  	 * For load-image-ios.js
  	 */
  	api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
  		try {
  			return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
  		} catch (ex) {
  			api.log('renderImageToCanvas failed');
  			throw ex;
  		}
  	};
  
  
  	// @export
  	api.support.canvas = api.support.transform = support;
  	api.Image = Image;
  })(FileAPI, document);
  
  /*
   * JavaScript Load Image iOS scaling fixes 1.0.3
   * https://github.com/blueimp/JavaScript-Load-Image
   *
   * Copyright 2013, Sebastian Tschan
   * https://blueimp.net
   *
   * iOS image scaling fixes based on
   * https://github.com/stomita/ios-imagefile-megapixel
   *
   * Licensed under the MIT license:
   * http://www.opensource.org/licenses/MIT
   */
  
  /*jslint nomen: true, bitwise: true */
  /*global FileAPI, window, document */
  
  (function (factory) {
  	'use strict';
  	factory(FileAPI);
  }(function (loadImage) {
      'use strict';
  
      // Only apply fixes on the iOS platform:
      if (!window.navigator || !window.navigator.platform ||
               !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
          return;
      }
  
      var originalRenderMethod = loadImage.renderImageToCanvas;
  
      // Detects subsampling in JPEG images:
      loadImage.detectSubsampling = function (img) {
          var canvas,
              context;
          if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images
              canvas = document.createElement('canvas');
              canvas.width = canvas.height = 1;
              context = canvas.getContext('2d');
              context.drawImage(img, -img.width + 1, 0);
              // subsampled image becomes half smaller in rendering size.
              // check alpha channel value to confirm image is covering edge pixel or not.
              // if alpha value is 0 image is not covering, hence subsampled.
              return context.getImageData(0, 0, 1, 1).data[3] === 0;
          }
          return false;
      };
  
      // Detects vertical squash in JPEG images:
      loadImage.detectVerticalSquash = function (img, subsampled) {
          var naturalHeight = img.naturalHeight || img.height,
              canvas = document.createElement('canvas'),
              context = canvas.getContext('2d'),
              data,
              sy,
              ey,
              py,
              alpha;
          if (subsampled) {
              naturalHeight /= 2;
          }
          canvas.width = 1;
          canvas.height = naturalHeight;
          context.drawImage(img, 0, 0);
          data = context.getImageData(0, 0, 1, naturalHeight).data;
          // search image edge pixel position in case it is squashed vertically:
          sy = 0;
          ey = naturalHeight;
          py = naturalHeight;
          while (py > sy) {
              alpha = data[(py - 1) * 4 + 3];
              if (alpha === 0) {
                  ey = py;
              } else {
                  sy = py;
              }
              py = (ey + sy) >> 1;
          }
          return (py / naturalHeight) || 1;
      };
  
      // Renders image to canvas while working around iOS image scaling bugs:
      // https://github.com/blueimp/JavaScript-Load-Image/issues/13
      loadImage.renderImageToCanvas = function (
          canvas,
          img,
          sourceX,
          sourceY,
          sourceWidth,
          sourceHeight,
          destX,
          destY,
          destWidth,
          destHeight
      ) {
          if (img._type === 'image/jpeg') {
              var context = canvas.getContext('2d'),
                  tmpCanvas = document.createElement('canvas'),
                  tileSize = 1024,
                  tmpContext = tmpCanvas.getContext('2d'),
                  subsampled,
                  vertSquashRatio,
                  tileX,
                  tileY;
              tmpCanvas.width = tileSize;
              tmpCanvas.height = tileSize;
              context.save();
              subsampled = loadImage.detectSubsampling(img);
              if (subsampled) {
                  sourceX /= 2;
                  sourceY /= 2;
                  sourceWidth /= 2;
                  sourceHeight /= 2;
              }
              vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
              if (subsampled || vertSquashRatio !== 1) {
                  sourceY *= vertSquashRatio;
                  destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
                  destHeight = Math.ceil(
                      tileSize * destHeight / sourceHeight / vertSquashRatio
                  );
                  destY = 0;
                  tileY = 0;
                  while (tileY < sourceHeight) {
                      destX = 0;
                      tileX = 0;
                      while (tileX < sourceWidth) {
                          tmpContext.clearRect(0, 0, tileSize, tileSize);
                          tmpContext.drawImage(
                              img,
                              sourceX,
                              sourceY,
                              sourceWidth,
                              sourceHeight,
                              -tileX,
                              -tileY,
                              sourceWidth,
                              sourceHeight
                          );
                          context.drawImage(
                              tmpCanvas,
                              0,
                              0,
                              tileSize,
                              tileSize,
                              destX,
                              destY,
                              destWidth,
                              destHeight
                          );
                          tileX += tileSize;
                          destX += destWidth;
                      }
                      tileY += tileSize;
                      destY += destHeight;
                  }
                  context.restore();
                  return canvas;
              }
          }
          return originalRenderMethod(
              canvas,
              img,
              sourceX,
              sourceY,
              sourceWidth,
              sourceHeight,
              destX,
              destY,
              destWidth,
              destHeight
          );
      };
  
  }));
  
  /*global window, FileAPI */
  
  (function (api, window){
  	"use strict";
  
  	var
  		  document = window.document
  		, FormData = window.FormData
  		, Form = function (){ this.items = []; }
  		, encodeURIComponent = window.encodeURIComponent
  	;
  
  
  	Form.prototype = {
  
  		append: function (name, blob, file, type){
  			this.items.push({
  				  name: name
  				, blob: blob && blob.blob || (blob == void 0 ? '' : blob)
  				, file: blob && (file || blob.name)
  				, type:	blob && (type || blob.type)
  			});
  		},
  
  		each: function (fn){
  			var i = 0, n = this.items.length;
  			for( ; i < n; i++ ){
  				fn.call(this, this.items[i]);
  			}
  		},
  
  		toData: function (fn, options){
  		    // allow chunked transfer if we have only one file to send
  		    // flag is used below and in XHR._send
  		    options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
  
  			if( !api.support.html5 ){
  				api.log('FileAPI.Form.toHtmlData');
  				this.toHtmlData(fn);
  			}
  			else if( !api.formData || this.multipart || !FormData ){
  				api.log('FileAPI.Form.toMultipartData');
  				this.toMultipartData(fn);
  			}
  			else if( options._chunked ){
  				api.log('FileAPI.Form.toPlainData');
  				this.toPlainData(fn);
  			}
  			else {
  				api.log('FileAPI.Form.toFormData');
  				this.toFormData(fn);
  			}
  		},
  
  		_to: function (data, complete, next, arg){
  			var queue = api.queue(function (){
  				complete(data);
  			});
  
  			this.each(function (file){
  				next(file, data, queue, arg);
  			});
  
  			queue.check();
  		},
  
  
  		toHtmlData: function (fn){
  			this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
  				var blob = file.blob, hidden;
  
  				if( file.file ){
  					api.reset(blob, true);
  					// set new name
  					blob.name = file.name;
  					blob.disabled = false;
  					data.appendChild(blob);
  				}
  				else {
  					hidden = document.createElement('input');
  					hidden.name  = file.name;
  					hidden.type  = 'hidden';
  					hidden.value = blob;
  					data.appendChild(hidden);
  				}
  			});
  		},
  
  		toPlainData: function (fn){
  			this._to({}, fn, function (file, data, queue){
  				if( file.file ){
  					data.type = file.file;
  				}
  
  				if( file.blob.toBlob ){
  				    // canvas
  					queue.inc();
  					_convertFile(file, function (file, blob){
  						data.name = file.name;
  						data.file = blob;
  						data.size = blob.length;
  						data.type = file.type;
  						queue.next();
  					});
  				}
  				else if( file.file ){
  				    // file
  					data.name = file.blob.name;
  					data.file = file.blob;
  					data.size = file.blob.size;
  					data.type = file.type;
  				}
  				else {
  				    // additional data
  				    if( !data.params ){
  				        data.params = [];
  				    }
  				    data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
  				}
  
  				data.start = -1;
  				data.end = data.file && data.file.FileAPIReadPosition || -1;
  				data.retry = 0;
  			});
  		},
  
  		toFormData: function (fn){
  			this._to(new FormData, fn, function (file, data, queue){
  				if( file.blob && file.blob.toBlob ){
  					queue.inc();
  					_convertFile(file, function (file, blob){
  						data.append(file.name, blob, file.file);
  						queue.next();
  					});
  				}
  				else if( file.file ){
  					data.append(file.name, file.blob, file.file);
  				}
  				else {
  					data.append(file.name, file.blob);
  				}
  
  				if( file.file ){
  					data.append('_'+file.name, file.file);
  				}
  			});
  		},
  
  
  		toMultipartData: function (fn){
  			this._to([], fn, function (file, data, queue, boundary){
  				queue.inc();
  				_convertFile(file, function (file, blob){
  					data.push(
  						  '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
  						+ (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
  						+ '\r\n'
  						+ '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
  						+ '\r\n')
  					);
  					queue.next();
  				}, true);
  			}, api.expando);
  		}
  	};
  
  
  	function _convertFile(file, fn, useBinaryString){
  		var blob = file.blob, filename = file.file;
  
  		if( filename ){
  			if( !blob.toDataURL ){
  				// The Blob is not an image.
  				api.readAsBinaryString(blob, function (evt){
  					if( evt.type == 'load' ){
  						fn(file, evt.result);
  					}
  				});
  				return;
  			}
  
  			var
  				  mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
  				, type = mime[file.type] ? file.type : 'image/png'
  				, ext  = mime[type] || '.png'
  				, quality = blob.quality || 1
  			;
  
  			if( !filename.match(new RegExp(ext+'$', 'i')) ){
  				// Does not change the current extension, but add a new one.
  				filename += ext.replace('?', '');
  			}
  
  			file.file = filename;
  			file.type = type;
  
  			if( !useBinaryString && blob.toBlob ){
  				blob.toBlob(function (blob){
  					fn(file, blob);
  				}, type, quality);
  			}
  			else {
  				fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
  			}
  		}
  		else {
  			fn(file, blob);
  		}
  	}
  
  
  	// @export
  	api.Form = Form;
  })(FileAPI, window);
  
  /*global window, FileAPI, Uint8Array */
  
  (function (window, api){
  	"use strict";
  
  	var
  		  noop = function (){}
  		, document = window.document
  
  		, XHR = function (options){
  			this.uid = api.uid();
  			this.xhr = {
  				  abort: noop
  				, getResponseHeader: noop
  				, getAllResponseHeaders: noop
  			};
  			this.options = options;
  		},
  
  		_xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
  	;
  
  
  	XHR.prototype = {
  		status: 0,
  		statusText: '',
  		constructor: XHR,
  
  		getResponseHeader: function (name){
  			return this.xhr.getResponseHeader(name);
  		},
  
  		getAllResponseHeaders: function (){
  			return this.xhr.getAllResponseHeaders() || {};
  		},
  
  		end: function (status, statusText){
  			var _this = this, options = _this.options;
  
  			_this.end		=
  			_this.abort		= noop;
  			_this.status	= status;
  
  			if( statusText ){
  				_this.statusText = statusText;
  			}
  
  			api.log('xhr.end:', status, statusText);
  			options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
  
  			if( _this.xhr && _this.xhr.node ){
  				setTimeout(function (){
  					var node = _this.xhr.node;
  					try { node.parentNode.removeChild(node); } catch (e){}
  					try { delete window[_this.uid]; } catch (e){}
  					window[_this.uid] = _this.xhr.node = null;
  				}, 9);
  			}
  		},
  
  		abort: function (){
  			this.end(0, 'abort');
  
  			if( this.xhr ){
  				this.xhr.aborted = true;
  				this.xhr.abort();
  			}
  		},
  
  		send: function (FormData){
  			var _this = this, options = this.options;
  
  			FormData.toData(function (data){
  				// Start uploading
  				options.upload(options, _this);
  				_this._send.call(_this, options, data);
  			}, options);
  		},
  
  		_send: function (options, data){
  			var _this = this, xhr, uid = _this.uid, onloadFuncName = _this.uid + "Load", url = options.url;
  
  			api.log('XHR._send:', data);
  
  			if( !options.cache ){
  				// No cache
  				url += (~url.indexOf('?') ? '&' : '?') + api.uid();
  			}
  
  			if( data.nodeName ){
  				var jsonp = options.jsonp;
  
  				// prepare callback in GET
  				url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
  
  				// legacy
  				options.upload(options, _this);
  
  				var
  					onPostMessage = function (evt){
  						if( ~url.indexOf(evt.origin) ){
  							try {
  								var result = api.parseJSON(evt.data);
  								if( result.id == uid ){
  									complete(result.status, result.statusText, result.response);
  								}
  							} catch( err ){
  								complete(0, err.message);
  							}
  						}
  					},
  
  					// jsonp-callack
  					complete = window[uid] = function (status, statusText, response){
  						_this.readyState	= 4;
  						_this.responseText	= response;
  						_this.end(status, statusText);
  
  						api.event.off(window, 'message', onPostMessage);
  						window[uid] = xhr = transport = window[onloadFuncName] = null;
  					}
  				;
  
  				_this.xhr.abort = function (){
  					try {
  						if( transport.stop ){ transport.stop(); }
  						else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
  						else { transport.contentWindow.document.execCommand('Stop'); }
  					}
  					catch (er) {}
  					complete(0, "abort");
  				};
  
  				api.event.on(window, 'message', onPostMessage);
  
  				window[onloadFuncName] = function (){
  					try {
  						var
  							  win = transport.contentWindow
  							, doc = win.document
  							, result = win.result || api.parseJSON(doc.body.innerHTML)
  						;
  						complete(result.status, result.statusText, result.response);
  					} catch (e){
  						api.log('[transport.onload]', e);
  					}
  				};
  
  				xhr = document.createElement('div');
  				xhr.innerHTML = '<form target="'+ uid +'" action="'+ url +'" method="POST" enctype="multipart/form-data" style="position: absolute; top: -1000px; overflow: hidden; width: 1px; height: 1px;">'
  							+ '<iframe name="'+ uid +'" src="javascript:false;" onload="' + onloadFuncName + '()"></iframe>'
  							+ (jsonp && (options.url.indexOf('=?') < 0) ? '<input value="'+ uid +'" name="'+jsonp+'" type="hidden"/>' : '')
  							+ '</form>'
  				;
  
  				// get form-data & transport
  				var
  					  form = xhr.getElementsByTagName('form')[0]
  					, transport = xhr.getElementsByTagName('iframe')[0]
  				;
  
  				form.appendChild(data);
  
  				api.log(form.parentNode.innerHTML);
  
  				// append to DOM
  				document.body.appendChild(xhr);
  
  				// keep a reference to node-transport
  				_this.xhr.node = xhr;
  
  				// send
  				_this.readyState = 2; // loaded
  				form.submit();
  				form = null;
  			}
  			else {
  				// Clean url
  				url = url.replace(/([a-z]+)=(\?)&?/i, '');
  
  				// html5
  				if (this.xhr && this.xhr.aborted) {
  					api.log("Error: already aborted");
  					return;
  				}
  				xhr = _this.xhr = api.getXHR();
  
  				if (data.params) {
  					url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
  				}
  
  				xhr.open('POST', url, true);
  
  				if( api.withCredentials ){
  					xhr.withCredentials = "true";
  				}
  
  				if( !options.headers || !options.headers['X-Requested-With'] ){
  					xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  				}
  
  				api.each(options.headers, function (val, key){
  					xhr.setRequestHeader(key, val);
  				});
  
  
  				if ( options._chunked ) {
  					// chunked upload
  					if( xhr.upload ){
  						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
  							if (!data.retry) {
  								// show progress only for correct chunk uploads
  								options.progress({
  									  type:			evt.type
  									, total:		data.size
  									, loaded:		data.start + evt.loaded
  									, totalSize:	data.size
  								}, _this, options);
  							}
  						}, 100), false);
  					}
  
  					xhr.onreadystatechange = function (){
  						var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
  
  						_this.status     = xhr.status;
  						_this.statusText = xhr.statusText;
  						_this.readyState = xhr.readyState;
  
  						if( xhr.readyState == 4 ){
  							try {
  								for( var k in _xhrResponsePostfix ){
  									_this['response'+k]  = xhr['response'+k];
  								}
  							}catch(_){}
  							xhr.onreadystatechange = null;
  
  							if (!xhr.status || xhr.status - 201 > 0) {
  								api.log("Error: " + xhr.status);
  								// some kind of error
  								// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
  								// up - server error
  								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
  									// let's try again the same chunk
  									// only applicable for recoverable error codes 500 && 416
  									var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
  
  									// inform about recoverable problems
  									options.pause(data.file, options);
  
  									// smart restart if server reports about the last known byte
  									api.log("X-Last-Known-Byte: " + lkb);
  									if (lkb) {
  										data.end = lkb;
  									} else {
  										data.end = data.start - 1;
  										if (416 == xhr.status) {
  											data.end = data.end - options.chunkSize;
  										}
  									}
  
  									setTimeout(function () {
  										_this._send(options, data);
  									}, delay);
  								} else {
  									// no mo retries
  									_this.end(xhr.status);
  								}
  							} else {
  								// success
  								data.retry = 0;
  
  								if (data.end == data.size - 1) {
  									// finished
  									_this.end(xhr.status);
  								} else {
  									// next chunk
  
  									// shift position if server reports about the last known byte
  									api.log("X-Last-Known-Byte: " + lkb);
  									if (lkb) {
  										data.end = lkb;
  									}
  									data.file.FileAPIReadPosition = data.end;
  
  									setTimeout(function () {
  										_this._send(options, data);
  									}, 0);
  								}
  							}
  
  							xhr = null;
  						}
  					};
  
  					data.start = data.end + 1;
  					data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
  
  					// Retrieve a slice of file
  					var
  						  file = data.file
  						, slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
  					;
  
  					if( data.size && !slice.size ){
  						setTimeout(function (){
  							_this.end(-1);
  						});
  					} else {
  						xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
  						xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
  						xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
  
  						xhr.send(slice);
  					}
  
  					file = slice = null;
  				} else {
  					// single piece upload
  					if( xhr.upload ){
  						// https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
  						xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
  							options.progress(evt, _this, options);
  						}, 100), false);
  					}
  
  					xhr.onreadystatechange = function (){
  						_this.status     = xhr.status;
  						_this.statusText = xhr.statusText;
  						_this.readyState = xhr.readyState;
  
  						if( xhr.readyState == 4 ){
  							for( var k in _xhrResponsePostfix ){
  								_this['response'+k]  = xhr['response'+k];
  							}
  							xhr.onreadystatechange = null;
  
  							if (!xhr.status || xhr.status > 201) {
  								api.log("Error: " + xhr.status);
  								if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
  									options.retry = (options.retry || 0) + 1;
  									var delay = api.networkDownRetryTimeout;
  
  									// inform about recoverable problems
  									options.pause(options.file, options);
  
  									setTimeout(function () {
  										_this._send(options, data);
  									}, delay);
  								} else {
  									//success
  									_this.end(xhr.status);
  								}
  							} else {
  								//success
  								_this.end(xhr.status);
  							}
  
  							xhr = null;
  						}
  					};
  
  					if( api.isArray(data) ){
  						// multipart
  						xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
  						var rawData = data.join('') +'--_'+ api.expando +'--';
  
  						/** @namespace  xhr.sendAsBinary  https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
  						if( xhr.sendAsBinary ){
  							xhr.sendAsBinary(rawData);
  						}
  						else {
  							var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
  							xhr.send(new Uint8Array(bytes).buffer);
  
  						}
  					} else {
  						// FormData
  						xhr.send(data);
  					}
  				}
  			}
  		}
  	};
  
  
  	// @export
  	api.XHR = XHR;
  })(window, FileAPI);
  
  /**
   * @class	FileAPI.Camera
   * @author	RubaXa	<trash@rubaxa.org>
   * @support	Chrome 21+, FF 18+, Opera 12+
   */
  
  /*global window, FileAPI, jQuery */
  /** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
  (function (window, api){
  	"use strict";
  
  	var
  		URL = window.URL || window.webkitURL,
  
  		document = window.document,
  		navigator = window.navigator,
  
  		getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
  
  		html5 = !!getMedia
  	;
  
  
  	// Support "media"
  	api.support.media = html5;
  
  
  	var Camera = function (video){
  		this.video = video;
  	};
  
  
  	Camera.prototype = {
  		isActive: function (){
  			return	!!this._active;
  		},
  
  
  		/**
  		 * Start camera streaming
  		 * @param	{Function}	callback
  		 */
  		start: function (callback){
  			var
  				  _this = this
  				, video = _this.video
  				, _successId
  				, _failId
  				, _complete = function (err){
  					_this._active = !err;
  					clearTimeout(_failId);
  					clearTimeout(_successId);
  //					api.event.off(video, 'loadedmetadata', _complete);
  					callback && callback(err, _this);
  				}
  			;
  
  			getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
  				// Success
  				_this.stream = stream;
  
  //				api.event.on(video, 'loadedmetadata', function (){
  //					_complete(null);
  //				});
  
  				// Set camera stream
  				video.src = URL.createObjectURL(stream);
  
  				// Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
  				// See crbug.com/110938.
  				_successId = setInterval(function (){
  					if( _detectVideoSignal(video) ){
  						_complete(null);
  					}
  				}, 1000);
  
  				_failId = setTimeout(function (){
  					_complete('timeout');
  				}, 5000);
  
  				// Go-go-go!
  				video.play();
  			}, _complete/*error*/);
  		},
  
  
  		/**
  		 * Stop camera streaming
  		 */
  		stop: function (){
  			try {
  				this._active = false;
  				this.video.pause();
  				this.stream.stop();
  			} catch( err ){ }
  		},
  
  
  		/**
  		 * Create screenshot
  		 * @return {FileAPI.Camera.Shot}
  		 */
  		shot: function (){
  			return	new Shot(this.video);
  		}
  	};
  
  
  	/**
  	 * Get camera element from container
  	 *
  	 * @static
  	 * @param	{HTMLElement}	el
  	 * @return	{Camera}
  	 */
  	Camera.get = function (el){
  		return	new Camera(el.firstChild);
  	};
  
  
  	/**
  	 * Publish camera element into container
  	 *
  	 * @static
  	 * @param	{HTMLElement}	el
  	 * @param	{Object}		options
  	 * @param	{Function}		[callback]
  	 */
  	Camera.publish = function (el, options, callback){
  		if( typeof options == 'function' ){
  			callback = options;
  			options = {};
  		}
  
  		// Dimensions of "camera"
  		options = api.extend({}, {
  			  width:	'100%'
  			, height:	'100%'
  			, start:	true
  		}, options);
  
  
  		if( el.jquery ){
  			// Extract first element, from jQuery collection
  			el = el[0];
  		}
  
  
  		var doneFn = function (err){
  			if( err ){
  				callback(err);
  			}
  			else {
  				// Get camera
  				var cam = Camera.get(el);
  				if( options.start ){
  					cam.start(callback);
  				}
  				else {
  					callback(null, cam);
  				}
  			}
  		};
  
  
  		el.style.width	= _px(options.width);
  		el.style.height	= _px(options.height);
  
  
  		if( api.html5 && html5 ){
  			// Create video element
  			var video = document.createElement('video');
  
  			// Set dimensions
  			video.style.width	= _px(options.width);
  			video.style.height	= _px(options.height);
  
  			// Clean container
  			if( window.jQuery ){
  				jQuery(el).empty();
  			} else {
  				el.innerHTML = '';
  			}
  
  			// Add "camera" to container
  			el.appendChild(video);
  
  			// end
  			doneFn();
  		}
  		else {
  			Camera.fallback(el, options, doneFn);
  		}
  	};
  
  
  	Camera.fallback = function (el, options, callback){
  		callback('not_support_camera');
  	};
  
  
  	/**
  	 * @class	FileAPI.Camera.Shot
  	 */
  	var Shot = function (video){
  		var canvas	= video.nodeName ? api.Image.toCanvas(video) : video;
  		var shot	= api.Image(canvas);
  		shot.type	= 'image/png';
  		shot.width	= canvas.width;
  		shot.height	= canvas.height;
  		shot.size	= canvas.width * canvas.height * 4;
  		return	shot;
  	};
  
  
  	/**
  	 * Add "px" postfix, if value is a number
  	 *
  	 * @private
  	 * @param	{*}  val
  	 * @return	{String}
  	 */
  	function _px(val){
  		return	val >= 0 ? val + 'px' : val;
  	}
  
  
  	/**
  	 * @private
  	 * @param	{HTMLVideoElement} video
  	 * @return	{Boolean}
  	 */
  	function _detectVideoSignal(video){
  		var canvas = document.createElement('canvas'), ctx, res = false;
  		try {
  			ctx = canvas.getContext('2d');
  			ctx.drawImage(video, 0, 0, 1, 1);
  			res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
  		}
  		catch( e ){}
  		return	res;
  	}
  
  
  	// @export
  	Camera.Shot	= Shot;
  	api.Camera	= Camera;
  })(window, FileAPI);
  
  /**
   * FileAPI fallback to Flash
   *
   * @flash-developer  "Vladimir Demidov" <v.demidov@corp.mail.ru>
   */
  
  /*global window, ActiveXObject, FileAPI */
  (function (window, jQuery, api) {
  	"use strict";
  
  	var
  		  document = window.document
  		, location = window.location
  		, navigator = window.navigator
  		, _each = api.each
  	;
  
  
  	api.support.flash = (function (){
  		var mime = navigator.mimeTypes, has = false;
  
  		if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
  			has	= navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
  		}
  		else {
  			try {
  				has	= !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
  			}
  			catch(er){
  				api.log('Flash -- does not supported.');
  			}
  		}
  
  		if( has && /^file:/i.test(location) ){
  			api.log('[warn] Flash does not work on `file:` protocol.');
  		}
  
  		return	has;
  	})();
  
  
  	   api.support.flash
  	&& (0
  		|| !api.html5 || !api.support.html5
  		|| (api.cors && !api.support.cors)
  		|| (api.media && !api.support.media)
  	)
  	&& (function (){
  		var
  			  _attr  = api.uid()
  			, _retry = 0
  			, _files = {}
  			, _rhttp = /^https?:/i
  
  			, flash = {
  				_fn: {},
  
  
  				/**
  				 * Publish flash-object
  				 *
  				 * @param {HTMLElement} el
  				 * @param {String} id
  				 * @param {Object} [opts]
  				 */
  				publish: function (el, id, opts){
  					opts = opts || {};
  					el.innerHTML = _makeFlashHTML({
  						id: id
  						, src: _getUrl(api.flashUrl, 'r=' + api.version)
  //						, src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
  						, wmode: opts.camera ? '' : 'transparent'
  						, flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
  						+ '&flashId='+ id
  						+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
  						+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
  						+ '&timeout='+api.flashAbortTimeout
  						+ (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
  						+ '&debug='+(api.debug?"1":"")
  					}, opts);
  				},
  
  
  				/**
  				 * Initialization & preload flash object
  				 */
  				init: function (){
  					var child = document.body && document.body.firstChild;
  
  					if( child ){
  						do {
  							if( child.nodeType == 1 ){
  								api.log('FlashAPI.state: awaiting');
  
  								var dummy = document.createElement('div');
  
  								dummy.id = '_' + _attr;
  
  								_css(dummy, {
  									  top: 1
  									, right: 1
  									, width: 5
  									, height: 5
  									, position: 'absolute'
  									, zIndex: 1e6+'' // set max zIndex
  								});
  
  								child.parentNode.insertBefore(dummy, child);
  								flash.publish(dummy, _attr);
  
  								return;
  							}
  						}
  						while( child = child.nextSibling );
  					}
  
  					if( _retry < 10 ){
  						setTimeout(flash.init, ++_retry*50);
  					}
  				},
  
  
  				ready: function (){
  					api.log('FlashAPI.state: ready');
  
  					flash.ready = api.F;
  					flash.isReady = true;
  					flash.patch();
  					flash.patchCamera && flash.patchCamera();
  					api.event.on(document, 'mouseover', flash.mouseover);
  					api.event.on(document, 'click', function (evt){
  						if( flash.mouseover(evt) ){
  							evt.preventDefault
  								? evt.preventDefault()
  								: (evt.returnValue = true)
  							;
  						}
  					});
  				},
  
  
  				getEl: function (){
  					return	document.getElementById('_'+_attr);
  				},
  
  
  				getWrapper: function (node){
  					do {
  						if( /js-fileapi-wrapper/.test(node.className) ){
  							return	node;
  						}
  					}
  					while( (node = node.parentNode) && (node !== document.body) );
  				},
  				
  				disableMouseover: false,
  
  				mouseover: function (evt){
  					if (!flash.disableMouseover) {
  						var target = api.event.fix(evt).target;
  	
  						if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
  							var
  								  state = target.getAttribute(_attr)
  								, wrapper = flash.getWrapper(target)
  							;
  	
  							if( api.multiFlash ){
  								// check state:
  								//   i — published
  								//   i — initialization
  								//   r — ready
  								if( state == 'i' || state == 'r' ){
  									// publish fail
  									return	false;
  								}
  								else if( state != 'p' ){
  									// set "init" state
  									target.setAttribute(_attr, 'i');
  	
  									var dummy = document.createElement('div');
  	
  									if( !wrapper ){
  										api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
  										return;
  									}
  	
  									_css(dummy, {
  										  top:    0
  										, left:   0
  										, width:  target.offsetWidth
  										, height: target.offsetHeight
  										, zIndex: 1e6+'' // set max zIndex
  										, position: 'absolute'
  									});
  	
  									wrapper.appendChild(dummy);
  									flash.publish(dummy, api.uid());
  	
  									// set "publish" state
  									target.setAttribute(_attr, 'p');
  								}
  	
  								return	true;
  							}
  							else if( wrapper ){
  								// Use one flash element
  								var box = _getDimensions(wrapper);
  								_css(flash.getEl(), box);
  	
  								// Set current input
  								flash.curInp = target;
  							}
  						}
  						else if( !/object|embed/i.test(target.nodeName) ){
  							_css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
  						}
  					}
  				},
  
  				onEvent: function (evt){
  					var type = evt.type;
  					
  					if( type == 'ready' ){
  						try {
  							// set "ready" state
  							flash.getInput(evt.flashId).setAttribute(_attr, 'r');
  						} catch (e){
  						}
  
  						flash.ready();
  						setTimeout(function (){ flash.mouseenter(evt); }, 50);
  						return	true;
  					}
  					else if( type === 'ping' ){
  						api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
  					}
  					else if( type === 'log' ){
  						api.log('(flash -> js).log:', evt.target);
  					}
  					else if( type in flash ){
  						setTimeout(function (){
  							api.log('FlashAPI.event.'+evt.type+':', evt);
  							flash[type](evt);
  						}, 1);
  					}
  				},
  				mouseDown: function(evt) {
  					flash.disableMouseover = true;
  				},
  				cancel: function(evt) {
  					flash.disableMouseover = false;
  				},
  				mouseenter: function (evt){
  					var node = flash.getInput(evt.flashId);
  
  					if( node ){
  						// Set multiple mode
  						flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
  
  
  						// Set files filter
  						var accept = [], exts = {};
  
  						_each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
  							api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
  								exts[ext] = 1;
  							});
  						});
  
  						_each(exts, function (i, ext){
  							accept.push( ext );
  						});
  
  						flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
  					}
  				},
  
  
  				get: function (id){
  					return	document[id] || window[id] || document.embeds[id];
  				},
  
  
  				getInput: function (id){
  					if( api.multiFlash ){
  						try {
  							var node = flash.getWrapper(flash.get(id));
  							if( node ){
  								return node.getElementsByTagName('input')[0];
  							}
  						} catch (e){
  							api.log('[err] Can not find "input" by flashId:', id, e);
  						}
  					} else {
  						return	flash.curInp;
  					}
  				},
  
  
  				select: function (evt){
  					try {
  						var
  							  inp = flash.getInput(evt.flashId)
  							, uid = api.uid(inp)
  							, files = evt.target.files
  							, event
  						;
  						_each(files, function (file){
  							api.checkFileObj(file);
  						});
  	
  						_files[uid] = files;
  	
  						if( document.createEvent ){
  							event = document.createEvent('Event');
  							event.files = files;
  							event.initEvent('change', true, true);
  							inp.dispatchEvent(event);
  						}
  						else if( jQuery ){
  							jQuery(inp).trigger({ type: 'change', files: files });
  						}
  						else {
  							event = document.createEventObject();
  							event.files = files;
  							inp.fireEvent('onchange', event);
  						}
  					} finally {
  						flash.disableMouseover = false;
  					}
  				},
  
  				interval: null,
  				cmd: function (id, name, data, last) {
  					if (flash.uploadInProgress && flash.readInProgress) {
  						setTimeout(function() {
  							flash.cmd(id, name, data, last);
  						}, 100);
  					} else {
  						this.cmdFn(id, name, data, last);
  					}
  				},
  				
  				cmdFn: function(id, name, data, last) {
  					try {
  						api.log('(js -> flash).'+name+':', data);
  						return flash.get(id.flashId || id).cmd(name, data);
  					} catch (e){
  						api.log('(js -> flash).onError:', e);
  						if( !last ){
  							// try again
  							setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
  						}
  					}
  				},
  
  				patch: function (){
  					api.flashEngine = true;
  
  					// FileAPI
  					_inherit(api, {
  						readAsDataURL: function (file, callback){
  							if( _isHtmlFile(file) ){
  								this.parent.apply(this, arguments);
  							}
  							else {
  								api.log('FlashAPI.readAsBase64');
  								flash.readInProgress = true;
  								flash.cmd(file, 'readAsBase64', {
  									id: file.id,
  									callback: _wrap(function _(err, base64){
  										flash.readInProgress = false;
  										_unwrap(_);
  
  										api.log('FlashAPI.readAsBase64:', err);
  
  										callback({
  											  type: err ? 'error' : 'load'
  											, error: err
  											, result: 'data:'+ file.type +';base64,'+ base64
  										});
  									})
  								});
  							}
  						},
  
  						readAsText: function (file, encoding, callback){
  							if( callback ){
  								api.log('[warn] FlashAPI.readAsText not supported `encoding` param');
  							} else {
  								callback = encoding;
  							}
  
  							api.readAsDataURL(file, function (evt){
  								if( evt.type == 'load' ){
  									try {
  										evt.result = window.atob(evt.result.split(';base64,')[1]);
  									} catch( err ){
  										evt.type = 'error';
  										evt.error = err.toString();
  									}
  								}
  								callback(evt);
  							});
  						},
  						
  						getFiles: function (input, filter, callback){
  							if( callback ){
  								api.filterFiles(api.getFiles(input), filter, callback);
  								return null;
  							}
  
  							var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
  
  
  							if( !files ){
  								// Файлов нету, вызываем родительский метод
  								return	this.parent.apply(this, arguments);
  							}
  
  
  							if( filter ){
  								filter	= api.getFilesFilter(filter);
  								files	= api.filter(files, function (file){ return filter.test(file.name); });
  							}
  
  							return	files;
  						},
  
  
  						getInfo: function (file, fn){
  							if( _isHtmlFile(file) ){
  								this.parent.apply(this, arguments);
  							}
  							else if( file.isShot ){
  								fn(null, file.info = {
  									width: file.width,
  									height: file.height
  								});
  							}
  							else {
  								if( !file.__info ){
  									var defer = file.__info = api.defer();
  
  //									flash.cmd(file, 'getFileInfo', {
  //										  id: file.id
  //										, callback: _wrap(function _(err, info){
  //											_unwrap(_);
  //											defer.resolve(err, file.info = info);
  //										})
  //									});
  									defer.resolve(null, file.info = null);
  
  								}
  
  								file.__info.then(fn);
  							}
  						}
  					});
  
  
  					// FileAPI.Image
  					api.support.transform = true;
  					api.Image && _inherit(api.Image.prototype, {
  						get: function (fn, scaleMode){
  							this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
  							return this.parent(fn);
  						},
  
  						_load: function (file, fn){
  							api.log('FlashAPI.Image._load:', file);
  
  							if( _isHtmlFile(file) ){
  								this.parent.apply(this, arguments);
  							}
  							else {
  								var _this = this;
  								api.getInfo(file, function (err){
  									fn.call(_this, err, file);
  								});
  							}
  						},
  
  						_apply: function (file, fn){
  							api.log('FlashAPI.Image._apply:', file);
  
  							if( _isHtmlFile(file) ){
  								this.parent.apply(this, arguments);
  							}
  							else {
  								var m = this.getMatrix(file.info), doneFn = fn;
  
  								flash.cmd(file, 'imageTransform', {
  									  id: file.id
  									, matrix: m
  									, callback: _wrap(function _(err, base64){
  										api.log('FlashAPI.Image._apply.callback:', err);
  										_unwrap(_);
  
  										if( err ){
  											doneFn(err);
  										}
  										else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
  											_makeFlashImage({
  												  width:	(m.deg % 180) ? m.dh : m.dw
  												, height:	(m.deg % 180) ? m.dw : m.dh
  												, scale:	m.scaleMode
  											}, base64, doneFn);
  										}
  										else {
  											if( m.filter ){
  												doneFn = function (err, img){
  													if( err ){
  														fn(err);
  													}
  													else {
  														api.Image.applyFilter(img, m.filter, function (){
  															fn(err, this.canvas);
  														});
  													}
  												};
  											}
  
  											api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
  										}
  									})
  								});
  							}
  						},
  
  						toData: function (fn){
  							var
  								  file = this.file
  								, info = file.info
  								, matrix = this.getMatrix(info)
  							;
  							api.log('FlashAPI.Image.toData');
  
  							if( _isHtmlFile(file) ){
  								this.parent.apply(this, arguments);
  							}
  							else {
  								if( matrix.deg == 'auto' ){
  									matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
  								}
  
  								fn.call(this, !file.info, {
  									  id:		file.id
  									, flashId:	file.flashId
  									, name:		file.name
  									, type:		file.type
  									, matrix:	matrix
  								});
  							}
  						}
  					});
  
  
  					api.Image && _inherit(api.Image, {
  						fromDataURL: function (dataURL, size, callback){
  							if( !api.support.dataURI || dataURL.length > 3e4 ){
  								_makeFlashImage(
  									  api.extend({ scale: 'exactFit' }, size)
  									, dataURL.replace(/^data:[^,]+,/, '')
  									, function (err, el){ callback(el); }
  								);
  							}
  							else {
  								this.parent(dataURL, size, callback);
  							}
  						}
  					});
  
  					// FileAPI.Form
  					_inherit(api.Form.prototype, {
  						toData: function (fn){
  							var items = this.items, i = items.length;
  
  							for( ; i--; ){
  								if( items[i].file && _isHtmlFile(items[i].blob) ){
  									return this.parent.apply(this, arguments);
  								}
  							}
  
  							api.log('FlashAPI.Form.toData');
  							fn(items);
  						}
  					});
  
  
  					// FileAPI.XHR
  					_inherit(api.XHR.prototype, {
  						_send: function (options, formData){
  							if(
  								   formData.nodeName
  								|| formData.append && api.support.html5
  								|| api.isArray(formData) && (typeof formData[0] === 'string')
  							){
  								// HTML5, Multipart or IFrame
  								return	this.parent.apply(this, arguments);
  							}
  
  
  							var
  								  data = {}
  								, files = {}
  								, _this = this
  								, flashId
  								, fileId
  							;
  
  							_each(formData, function (item){
  								if( item.file ){
  									files[item.name] = item = _getFileDescr(item.blob);
  									fileId  = item.id;
  									flashId = item.flashId;
  								}
  								else {
  									data[item.name] = item.blob;
  								}
  							});
  
  							if( !fileId ){
  								flashId = _attr;
  							}
  
  							if( !flashId ){
  								api.log('[err] FlashAPI._send: flashId -- undefined');
  								return this.parent.apply(this, arguments);
  							}
  							else {
  								api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
  							}
  
  							_this.xhr = {
  								headers: {},
  								abort: function (){ flash.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); },
  								getResponseHeader: function (name){ return this.headers[name]; },
  								getAllResponseHeaders: function (){ return this.headers; }
  							};
  
  							var queue = api.queue(function (){
  								flash.uploadInProgress = true;
  								flash.cmd(flashId, 'upload', {
  									  url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
  									, data: data
  									, files: fileId ? files : null
  									, headers: options.headers || {}
  									, callback: _wrap(function upload(evt){
  										var type = evt.type, result = evt.result;
  
  										api.log('FlashAPI.upload.'+type);
  
  										if( type == 'progress' ){
  											evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
  											evt.lengthComputable = true;
  											options.progress(evt);
  										}
  										else if( type == 'complete' ){
  											flash.uploadInProgress = false;
  											_unwrap(upload);
  
  											if( typeof result == 'string' ){
  												_this.responseText	= result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
  											}
  
  											_this.end(evt.status || 200);
  										}
  										else if( type == 'abort' || type == 'error' ){
  											flash.uploadInProgress = false;
  											_this.end(evt.status || 0, evt.message);
  											_unwrap(upload);
  										}
  									})
  								});
  							});
  
  
  							// #2174: FileReference.load() call while FileReference.upload() or vice versa
  							_each(files, function (file){
  								queue.inc();
  								api.getInfo(file, queue.next);
  							});
  
  							queue.check();
  						}
  					});
  				}
  			}
  		;
  
  
  		function _makeFlashHTML(opts){
  			return ('<object id="#id#" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'">'
  				+ '<param name="movie" value="#src#" />'
  				+ '<param name="flashvars" value="#flashvars#" />'
  				+ '<param name="swliveconnect" value="true" />'
  				+ '<param name="allowscriptaccess" value="always" />'
  				+ '<param name="allownetworking" value="all" />'
  				+ '<param name="menu" value="false" />'
  				+ '<param name="wmode" value="#wmode#" />'
  				+ '<embed flashvars="#flashvars#" swliveconnect="true" allownetworking="all" allowscriptaccess="always" name="#id#" src="#src#" width="'+(opts.width || '100%')+'" height="'+(opts.height || '100%')+'" menu="false" wmode="transparent" type="application/x-shockwave-flash"></embed>'
  				+ '</object>').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
  			;
  		}
  
  
  		function _css(el, css){
  			if( el && el.style ){
  				var key, val;
  				for( key in css ){
  					val = css[key];
  					if( typeof val == 'number' ){
  						val += 'px';
  					}
  					try { el.style[key] = val; } catch (e) {}
  				}
  				
  			}
  		}
  
  
  		function _inherit(obj, methods){
  			_each(methods, function (fn, name){
  				var prev = obj[name];
  				obj[name] = function (){
  					this.parent = prev;
  					return fn.apply(this, arguments);
  				};
  			});
  		}
  
  		function _isHtmlFile(file){
  			return	file && !file.flashId;
  		}
  
  		function _wrap(fn){
  			var id = fn.wid = api.uid();
  			flash._fn[id] = fn;
  			return	'FileAPI.Flash._fn.'+id;
  		}
  
  
  		function _unwrap(fn){
  			try {
  				flash._fn[fn.wid] = null;
  				delete	flash._fn[fn.wid];
  			}
  			catch(e){}
  		}
  
  
  		function _getUrl(url, params){
  			if( !_rhttp.test(url) ){
  				if( /^\.\//.test(url) || '/' != url.charAt(0) ){
  					var path = location.pathname;
  					path = path.substr(0, path.lastIndexOf('/'));
  					url = (path +'/'+ url).replace('/./', '/');
  				}
  
  				if( '//' != url.substr(0, 2) ){
  					url = '//' + location.host + url;
  				}
  
  				if( !_rhttp.test(url) ){
  					url = location.protocol + url;
  				}
  			}
  
  			if( params ){
  				url += (/\?/.test(url) ? '&' : '?') + params;
  			}
  
  			return	url;
  		}
  
  
  		function _makeFlashImage(opts, base64, fn){
  			var
  				  key
  				, flashId = api.uid()
  				, el = document.createElement('div')
  				, attempts = 10
  			;
  
  			for( key in opts ){
  				el.setAttribute(key, opts[key]);
  				el[key] = opts[key];
  			}
  
  			_css(el, opts);
  
  			opts.width	= '100%';
  			opts.height	= '100%';
  
  			el.innerHTML = _makeFlashHTML(api.extend({
  				  id: flashId
  				, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
  				, wmode: 'opaque'
  				, flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
  					_unwrap(_);
  					if( --attempts > 0 ){
  						_setImage();
  					}
  					return true;
  				})
  			}, opts));
  
  			function _setImage(){
  				try {
  					// Get flash-object by id
  					var img = flash.get(flashId);
  					img.setImage(base64);
  				} catch (e){
  					api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
  				}
  			}
  
  			fn(false, el);
  			el = null;
  		}
  
  
  		function _getFileDescr(file){
  			return	{
  				  id: file.id
  				, name: file.name
  				, matrix: file.matrix
  				, flashId: file.flashId
  			};
  		}
  
  
  		function _getDimensions(el){
  			var
  				  box = el.getBoundingClientRect()
  				, body = document.body
  				, docEl = (el && el.ownerDocument).documentElement
  			;
  			
  			function getOffset(obj) {
  			    var left, top;
  			    left = top = 0;
  			    if (obj.offsetParent) {
  			        do {
  			            left += obj.offsetLeft;
  			            top  += obj.offsetTop;
  			        } while (obj = obj.offsetParent);
  			    }
  			    return {
  			        left : left,
  			        top : top
  			    };
  			};
  			
  			return {
  				  top:		getOffset(el).top
  				, left:		getOffset(el).left
  				, width:	el.offsetWidth
  				, height:	el.offsetHeight
  			};
  		}
  
  		// @export
  		api.Flash = flash;
  
  
  		// Check dataURI support
  		api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
  			api.support.dataURI = !(img.width != 1 || img.height != 1);
  			flash.init();
  		});
  	})();
  })(window, window.jQuery, FileAPI);
  
  /**
   * FileAPI fallback to Flash
   *
   * @flash-developer  "Vladimir Demidov" <v.demidov@corp.mail.ru>
   */
  
  /*global window, FileAPI */
  (function (window, jQuery, api) {
      "use strict";
  
      var _each = api.each,
          _cameraQueue = [];
  
  
      if (api.support.flash && (api.media && !api.support.media)) {
          (function () {
  
              function _wrap(fn) {
                  var id = fn.wid = api.uid();
                  api.Flash._fn[id] = fn;
                  return 'FileAPI.Flash._fn.' + id;
              }
  
  
              function _unwrap(fn) {
                  try {
                      api.Flash._fn[fn.wid] = null;
                      delete api.Flash._fn[fn.wid];
                  } catch (e) {
                  }
              }
  
              var flash = api.Flash;
              api.extend(api.Flash, {
  
                  patchCamera: function () {
                      api.Camera.fallback = function (el, options, callback) {
                          var camId = api.uid();
                          api.log('FlashAPI.Camera.publish: ' + camId);
                          flash.publish(el, camId, api.extend(options, {
                              camera: true,
                              onEvent: _wrap(function _(evt) {
                                  if (evt.type === 'camera') {
                                      _unwrap(_);
  
                                      if (evt.error) {
                                          api.log('FlashAPI.Camera.publish.error: ' + evt.error);
                                          callback(evt.error);
                                      } else {
                                          api.log('FlashAPI.Camera.publish.success: ' + camId);
                                          callback(null);
                                      }
                                  }
                              })
                          }));
                      };
                      // Run
                      _each(_cameraQueue, function (args) {
                          api.Camera.fallback.apply(api.Camera, args);
                      });
                      _cameraQueue = [];
  
  
                      // FileAPI.Camera:proto
                      api.extend(api.Camera.prototype, {
                          _id: function () {
                              return this.video.id;
                          },
  
                          start: function (callback) {
                              var _this = this;
                              flash.cmd(this._id(), 'camera.on', {
                                  callback: _wrap(function _(evt) {
                                      _unwrap(_);
  
                                      if (evt.error) {
                                          api.log('FlashAPI.camera.on.error: ' + evt.error);
                                          callback(evt.error, _this);
                                      } else {
                                          api.log('FlashAPI.camera.on.success: ' + _this._id());
                                          _this._active = true;
                                          callback(null, _this);
                                      }
                                  })
                              });
                          },
  
                          stop: function () {
                              this._active = false;
                              flash.cmd(this._id(), 'camera.off');
                          },
  
                          shot: function () {
                              api.log('FlashAPI.Camera.shot:', this._id());
  
                              var shot = api.Flash.cmd(this._id(), 'shot', {});
                              shot.type = 'image/png';
                              shot.flashId = this._id();
                              shot.isShot = true;
  
                              return new api.Camera.Shot(shot);
                          }
                      });
                  }
              });
  
              api.Camera.fallback = function () {
                  _cameraQueue.push(arguments);
              };
  
          }());
      }
  }(window, window.jQuery, FileAPI));
  if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
  