define([
	"dojo/_base/kernel",
	"dojo/_base/declare",
	"dojo/_base/lang",
	"dojo/_base/array",
	"dojo/_base/connect",
	"dojo/_base/window",
	"dojo/dom-style",
	"dojo/dom-class",
	"dojo/dom-geometry",
	"dojo/dom-attr",
	"dojo/dom-construct",
	"dojo/dom-form",
	"dijit",
	"dijit/form/Button",
	"./uploader/_Base",
	"./uploader/_HTML5",
	"./uploader/_IFrame",
	"./uploader/_Flash",
	"dojo/i18n!./nls/Uploader",
	"dojo/text!./resources/Uploader.html"
],function(kernel, declare, lang, array, connect, win, domStyle, domClass, domGeometry, domAttr, domConstruct,
		   domForm, dijit, Button, Base, HTML5, IFrame, Flash, res, template){

	// TODO:
	//		i18n
	//		label via innerHTML
	//		Doc and or test what can be extended.
	//		Doc custom file events
	//		Use new FileReader() for thumbnails
	//		flashFieldName should default to Flash
	//		get('value'); and set warning
	//		Make it so URL can change (current set to Flash on build)
	//

	return declare("dojox.form.Uploader", [Base, Button, HTML5, IFrame, Flash], {
		// summary:
		//		A widget that creates a stylable file-input button, with optional multi-file selection,
		//		using only HTML elements. Non-HTML5 browsers have fallback options of Flash or an iframe.
		//
		// description:
		//		A bare-bones, stylable file-input button, with optional multi-file selection. The list
		//		of files is not displayed, that is for you to handle by connecting to the onChange
		//		event, or use the dojox.form.uploader.FileList.
		//
		//		Uploader without plugins does not have any ability to upload - it is for use in forms
		//		where you handle the upload either by a standard POST or with Ajax using an iFrame. This
		//		class is for convenience of multiple files only. No progress events are available.
		//
		//		If the browser supports a file-input with the "multiple" attribute, that will be used.
		//		If the browser does not support "multiple" (ergo, IE) multiple inputs are used,
		//		one for each selection.
		//
		//		Version: 1.6
	
	
		// uploadOnSelect: Boolean
		//		If true, uploads immediately after a file has been selected. If false,
		//		waits for upload() to be called.
		uploadOnSelect:false,
	
		// tabIndex: Number|String
		//		The tab order in the DOM.
		tabIndex:0,
	
		// multiple: Boolean
		//		If true and flash mode, multiple files may be selected from the dialog.
		multiple:false,
	
		// label: String
		//		The text used in the button that when clicked, opens a system Browse Dialog.
		label:res.label,
	
		// url: String
		//		The url targeted for upload. An absolute URL is preferred. Relative URLs are
		//		changed to absolute.
		url:"",
	
		// name: String
		//		The name attribute needs to end with square brackets: [] as this is the standard way
		//		of handling an attribute "array". This requires a slightly different technique on the
		//		server.
		name:"uploadedfile",
	
		// flashFieldName: String
		//		If set, this will be the name of the field of the flash uploaded files that the server
		//		is expecting. If not set, "Flash" is appended to the "name" property.
		flashFieldName:"",
		
		//	force: String
		//		options: form, html5, iframe, flash
		//		Empty string defaults to html5 if available, and iframe if not.
		// 		Use "flash" to always use Flash (and hopefully force the user to download the plugin
		//		if they don't have it).
		//		Use "iframe" to always use an iframe, and never flash nor html5. Sometimes preferred
		//		for consistent results.
		//		Use "form" to not use ajax and post to a page.
		force:"",
	
		// uploadType: String [readonly]
		//		The type of uploader being used. As an alternative to determining the upload type on the
		//		server based on the fieldName, this property could be sent to the server to help
		//		determine what type of parsing should be used.
		//		This is set based on the force property and what features are available in the browser.
		uploadType:"",
	
		// showInput: String [const]
		//		Position to show an input which shows selected filename(s). Possible
		//		values are "before", "after", which specifies where the input should
		//		be placed with reference to the containerNode which contains the
		//		label). By default, this is empty string (no such input will be
		//		shown). Specify showInput="before" to mimic the look&feel of a
		//		native file input element.
		showInput: "",
		
		//	focusedClass: String
		//		The class applied to the button when it is focused (via TAB key)
		focusedClass:"dijitButtonHover",
	
		_nameIndex:0,
	
		templateString: template,
	
		baseClass: 'dijitUploader '+Button.prototype.baseClass,
	
		postMixInProperties: function(){
			this._inputs = [];
			this._cons = [];
			this.force = this.force.toLowerCase();
			if(this.supports("multiple")){
				this.uploadType = this.force === 'form' ? 'form' : 'html5';
			}else{
				this.uploadType = this.force === 'flash' ? 'flash' : 'iframe';
			}
			
			this.inherited(arguments);
		},
		buildRendering: function(){
			this.inherited(arguments);
			domStyle.set(this.domNode, {
				overflow:"hidden",
				position:"relative"
			});
			this._buildDisplay();
			//change the button node not occupy tabIndex: the real file input
			//will have tabIndex set
			domAttr.set(this.titleNode, 'tabIndex', -1);
		},
		_buildDisplay: function(){
			if(this.showInput){
				this.displayInput = domConstruct.create('input', {
					  'class':'dijitUploadDisplayInput',
					  'tabIndex':-1, 'autocomplete':'off',
					  'role':'presentation'},
					this.containerNode, this.showInput);
				//schedule the attachpoint to be cleaned up on destroy
				this._attachPoints.push('displayInput');
				this.connect(this,'onChange', function(files){
					var i=0,l=files.length, f, r=[];
					while((f=files[i++])){
						if(f && f.name){
							r.push(f.name);
						}
					}
					this.displayInput.value = r.join(', ');
				});
				this.connect(this,'reset', function(){
					this.displayInput.value = '';
				});
			}
		},
	
		startup: function(){
			if(this._buildInitialized){
				return;
			}
			this._buildInitialized = true;
			this._getButtonStyle(this.domNode);
			this._setButtonStyle();
			this.inherited(arguments);
		},
	
		/*************************
		 *	   Public Events	 *
		 *************************/
	
		onChange: function(/*Array*/ fileArray){
			// summary:
			//		stub to connect
			//		Fires when files are selected
			//		Event is an array of last files selected
		},
	
		onBegin: function(/*Array*/ dataArray){
			// summary:
			//		Fires when upload begins
		},
	
		onProgress: function(/*Object*/ customEvent){
			// summary:
			//		Stub to connect
			//		Fires on upload progress. Event is a normalized object of common properties
			//		from HTML5 uploaders and the Flash uploader. Will not fire for IFrame.
			// customEvent:
			//		- bytesLoaded: Number:
			//			Amount of bytes uploaded so far of entire payload (all files)
			//		- bytesTotal: Number:
			//			Amount of bytes of entire payload (all files)
			//		- type: String:
			//			Type of event (progress or load)
			//		- timeStamp: Number:
			//			Timestamp of when event occurred
		},
	
		onComplete: function(/*Object*/ customEvent){
			// summary:
			//		stub to connect
			//		Fires when all files have uploaded
			//		Event is an array of all files
			this.reset();
		},
	
		onCancel: function(){
			// summary:
			//		Stub to connect
			//		Fires when dialog box has been closed
			//		without a file selection
		},
	
		onAbort: function(){
			// summary:
			//		Stub to connect
			//		Fires when upload in progress was canceled
		},
	
		onError: function(/*Object or String*/ evtObject){
			// summary:
			//		Fires on errors
	
			// FIXME: Unsure of a standard form of error events
		},
	
		/*************************
		 *	   Public Methods	 *
		 *************************/
	
		upload: function(/*Object?*/ formData){				
			// summary:
			//		When called, begins file upload. Only supported with plugins.
			formData = formData || {};
			formData.uploadType = this.uploadType;
			this.inherited(arguments);
		},
	
		submit: function(/*form Node?*/ form){
			// summary:
			//		If Uploader is in a form, and other data should be sent along with the files, use
			//		this instead of form submit.
			form = !!form ? form.tagName ? form : this.getForm() : this.getForm();
			var data = domForm.toObject(form);
			data.uploadType = this.uploadType;
			this.upload(data);
		},
	
		reset: function(){
			// summary:
			//		Resets entire input, clearing all files.
			//		NOTE:
			//		Removing individual files is not yet supported, because the HTML5 uploaders can't
			//		be edited.
			//		TODO:
			//		Add this ability by effectively, not uploading them
			//
			delete this._files;
			this._disconnectButton();
			array.forEach(this._inputs, domConstruct.destroy, dojo);
			this._inputs = [];
			this._nameIndex = 0;
			this._createInput();
		},
	
		getFileList: function(){
			// summary:
			//		Returns a list of selected files.
	
			var fileArray = [];
			if(this.supports("multiple")){
				array.forEach(this._files, function(f, i){
					fileArray.push({
						index:i,
						name:f.name,
						size:f.size,
						type:f.type
					});
				}, this);
			}else{
				array.forEach(this._inputs, function(n, i){
					if(n.value){
						fileArray.push({
							index:i,
							name:n.value.substring(n.value.lastIndexOf("\\")+1),
							size:0,
							type:n.value.substring(n.value.lastIndexOf(".")+1)
						});
					}
				}, this);
	
			}
			return fileArray; // Array
		},
	
		/*********************************************
		 *	   Private Property. Get off my lawn.	 *
		 *********************************************/
	
		_getValueAttr: function(){
			// summary:
			//		Internal. To get disabled use: uploader.get("disabled");
			return this.getFileList();
		},
	
		_setValueAttr: function(disabled){
			console.error("Uploader value is read only");
		},
	
		_setDisabledAttr: function(disabled){
			// summary:
			//		Internal. To set disabled use: uploader.set("disabled", true);
			if(this.disabled == disabled || !this.inputNode){ return; }
			this.inherited(arguments);
			domStyle.set(this.inputNode, "display", disabled ? "none" : "");
		},
	
		_getButtonStyle: function(node){
			this.btnSize = {w:domStyle.get(node,'width'), h:domStyle.get(node,'height')};
		},
	
		_setButtonStyle: function(){
			this.inputNodeFontSize = Math.max(2, Math.max(Math.ceil(this.btnSize.w / 60), Math.ceil(this.btnSize.h / 15)));
			this._createInput();
		},
	
		_getFileFieldName: function(){
			var name;
			if(this.supports("multiple") && this.multiple){
				name = this.name+"s[]";
			}else{
				// <=IE8
				name = this.name + (this.multiple ? this._nameIndex : "");
			}
			return name;
		},
	
		_createInput: function(){
			if(this._inputs.length){
				domStyle.set(this.inputNode, {
					top:"500px"
				});
				this._disconnectButton();
				this._nameIndex++;
			}
			var name = this._getFileFieldName();
			// reset focusNode to the inputNode, so when the button is clicked,
			// the focus is properly moved to the input element
			this.focusNode = this.inputNode = domConstruct.create("input", {type:"file", name:name, "aria-labelledby":this.id+"_label"}, this.domNode, "first");
			if(this.supports("multiple") && this.multiple){
				domAttr.set(this.inputNode, "multiple", true);
			}
			this._inputs.push(this.inputNode);
	
			domStyle.set(this.inputNode, {
				position:"absolute",
				fontSize:this.inputNodeFontSize+"em",
				top:"-3px",
				right:"-3px",
				opacity:0
			});
			this._connectButton();
		},
	
		_connectButton: function(){
			this._cons.push(connect.connect(this.inputNode, "change", this, function(evt){
				this._files = this.inputNode.files;
				this.onChange(this.getFileList(evt));
				if(!this.supports("multiple") && this.multiple) this._createInput();
			}));
	
			if(this.tabIndex > -1){
				this.inputNode.tabIndex = this.tabIndex;
	
				this._cons.push(connect.connect(this.inputNode, "focus", this, function(){
					domClass.add(this.domNode, this.focusedClass);
				}));
				this._cons.push(connect.connect(this.inputNode, "blur", this, function(){
					domClass.remove(this.domNode, this.focusedClass);
				}));
			}
		},
	
		_disconnectButton: function(){
			array.forEach(this._cons, connect.disconnect);
			this._cons.splice(0,this._cons.length);
		}
	});

});
