/**
 * @class ep
 *
 */

/**
 * Perform an asynchronous file upload with HTTP (Ajax).
 *
 * The `ep.ajax()` function is a modified alias of `jQuery.ajax()` and supports all options of `jQuery.ajax()`.
 *
 * This method provides file upload with AJAX. File uploads are always asynchrony the `async` options isn't editable.
 * To upload files with `ep.ajax()` us the additional options `file`, `fileSizeMax`, `fileMimeAccept` and `progress`.
 *
 * ====New API standard in 6.14.0====
 * For AJAX use `deferred object` methods instead of setting `success`, `error`, `complete` and `progress` methods as arguments.
 *
 * Properties to methods:
 *
 * + `success` => `.done()`
 * + `error` => `.fail()`
 * + `complete` => `.always()`
 * + `progress` => `.progress()`
 *
 * ### Examples
 * Upload files with ajax on change.
 *
 * JavaScript:
 *
 *     $('#upload').on( 'change', function(event){
 *         ep.ajax({
 *             file:           this,
 *             fileSizeMax:    2097152,
 *             fileMimeAccept: 'image/*',
 *             dataType:       'json'
 *         })
 *         .progress(function( event, statusText, jqXHR ){
 *             // added in verison 6.14.0
 *         })
 *         .done(function( jsonData, statusText, jqXHR ){
 *
 *         })
 *         .fail(function( jqXHR, statusText, errorThrown ){
 *
 *         })
 *     });
 *
 * HTML:
 *
 *     <input id="upload" type="file" multiple="true" name="NewFile" />
 *
 *
 * ### Dependencies
 *
 *  + `jQuery.mime`
 *
 * @param {Object} options A set of key/value pairs that configure the Ajax request. All settings are optional.
 * @param {Object} [options.file] An input element of type file, which contains the file(s) to upload (only in connection with dataType 'json').
 * @param {Number} [options.fileSizeMax] A number of file size limit (bytes).
 * @param {String} [options.fileMimeAccept] A string containing one or more mime types / mime type groups.
 * @param {String} [options.progress] An event method which called on file upload progress. Receives event, statusText and jqXHR as arguments.
 *
 * @method ajax
 * @static
 * @member ep
 *
 * @since 6.12.0
 */

/*
 * @copyright		© Copyright 2006-2010, epages GmbH, All Rights Reserved.
 *
 * @module			de_epages.presentation.ajaxTransport
 *
 * @revision		$Revision: 1.8 $
 */

define("de_epages/presentation/ajaxtransport", [
	"jquery",
	"de_epages",
	"util/mime",
	"util/support",

	"jquery/uid",
	"ep/ajax"
], function ($, de_epages, mime, support) {

	// extend the ajax settings
	if( !$.ajaxSettings.fileSizeMax ){
		$.ajaxSettings.fileSizeMax = 2097152;	// set max size of files default: 2MB
	}

		// use html5 as default
	var html5 = support.ajaxUpload,
		// url query params
		rquery = /\?/,
		// to string method
		toString = Object.prototype.toString,
		// map of advanced errors
		statusMap = {
			102: { status:102, errorThrown:'Processing' },
			200: { status:200, errorThrown:'Ok' },
			290: { status:290, errorThrown:'Ok' },							// Item
			408: { status:408, errorThrown:'Request Timeout' },
			488: { status:488, errorThrown:'Font License Restricted' },		// Item
			489: { status:489, errorThrown:'Invalid Image Size' },			// Item
			490: { status:490, errorThrown:'Bad Item Request' },
			491: { status:491, errorThrown:'Items of Request Failed' },
			492: { status:492, errorThrown:'Empty Request' },				// Item
			493: { status:493, errorThrown:'Mimetype Not Accept Error' },	// Item
			494: { status:494, errorThrown:'File Size Limit Error' },		// Item
			495: { status:495, errorThrown:'Feature Limit Exceeded' },		// Item
			496: { status:496, errorThrown:'Invalid Image File' },			// Item
			497: { status:497, errorThrown:'Image Exceeds Megapixels' },	// Item
			498: { status:498, errorThrown:'Invalid MP3 File' },			// Item
			499: { status:499, errorThrown:'Virus Found Error' },			// Item
			500: { status:500, errorThrown:'Internal Server Error' },
			590: { status:590, errorThrown:'Internal Server Error' },		// Item
			599: { status:599, errorThrown:'Unknown Error' }
		},
		// local Error handle
		errorGet = function( extended, code, errorThrown ){
			if( extended!==true ){
				errorThrown = code;
				code = extended;
				extended = false;
			}

			var data = statusMap[ code ] || {
					status:code,
					errorThrown: errorThrown || 'Unknow Error'
				},
				text;

			if (errorThrown) {
				data.errorThrown = errorThrown.replace(/(^|_)([A-Z])([A-Z]+)/g, function (all, $1, $2, $3) {
					return ($1 ? ' ' : '') + $2 + $3.toLowerCase();
				});
			}

			text = '{"status":'+data.code+',"errorThrown":"'+data.errorThrown+'"}}';

			return extended ? {text:'{"ERROR":'+text+'}',data:{ERROR: data}} : {text:text,data:	data};
		},
		// upload single file with ajax
		singleAJAX = function( o, originalOptions, jqXHR, progressCallback ){
			var xhr = o.xhr(),
				send = xhr.send,
				fileObj = o.file;

			// modify options
			o.data = null;
			o.type = 'post';
			o.async = true;
			o.xhr = function(){
				return xhr;
			};
			// add progress handle
			if( xhr.upload ){
				xhr.upload.onprogress = function(event){
					progressCallback(event);
				};
			}
			// modify send xhr
			xhr.send = function(){
				xhr.setRequestHeader( 'Content-Type', 'application/octet-stream' );
				xhr.setRequestHeader( 'If-Modified-Since', 'Mon, 26 Jul 1997 05:00:00 GMT' );
				xhr.setRequestHeader( 'Cache-Control', 'no-cache' );
				xhr.setRequestHeader( 'content-disposition', 'attachment; filename="'+encodeURIComponent(fileObj.fileName||fileObj.name)+'"; name="'+o['_inputName']+'"' );

				if(xhr.sendAsBinary && typeof fileObj.getAsBinary  == "function") {
					xhr.sendAsBinary(fileObj.getAsBinary());
				}
				else{
					send.call(xhr,fileObj);
				}
			};
		},
		// handle multiple files to upload with ajax
		multiAJAX = function( o, originalOptions, jqXHR, progressCallback ){
			// state whether xhr abort
			var isAbort = false,
			// state whether error-caused
				isError = false;
			// results send and abort handle
			return {
				send: function( headers, completeCallback ){
					var inputName = o['_inputName'],
						fileStack = $.merge([],o.file.files),
						fileStackLength = fileStack.length,
						// response data store
						responseText = [],
						responseData = [],
						headerStack = [],
						// completeCallback
						nextItem = function( i ){
							if( i<fileStackLength ){
								var fileObj = fileStack[i],
									fileItem = {
										name:		inputName,
										fileName:	fileObj.name || fileObj.fileName,
										fileSize:	fileObj.size || fileObj.fileSize,
										fileType:	fileObj.type
									},
									xhrItemProgress = function( event ){
										if( isAbort ){
											// abort item if main xhr is abort
											xhrItem.abort();
										}
										else{
											// set status of main xhr
											jqXHR.status = 102;
											jqXHR.statusText = 'progress-item';
											// call progress handle
											progressCallback(
												// modify progress event object
												$.extend(event,{
													item:				i+1,
													itemTotal:			fileStackLength,
													itemLoaded:			i,
													data:				{item:fileItem}
												}),
												jqXHR.statusText,
												jqXHR
											);
										}
									},
									xhrItemDone = function( data, textStatus, xhrItem ){
										// overwrite original status
										textStatus = 'success-item';
										var status = 290;

										if( isAbort ){
											// abort item if main xhr is abort
											xhrItem.abort();
										}
										else{
											if( data.ERROR ){
												data = {
													item: fileItem,
													ERROR: errorGet(data.ERROR.status || 590, data.ERROR.errorThrown).data
												};
												// set error status
												isError = true;
												status = data.ERROR.status;
												textStatus = 'error-item';
											}
											else{
												// store data
												responseText.push(xhrItem.responseText);
												responseData.push(data);
												data = {success:data, item:fileItem};
											}
											// set status of main xhr
											jqXHR.status = status;
											jqXHR.statusText = textStatus;
											// call progress handle
											progressCallback(
												// simulate progress event object
												$.extend($.Event(),{
													type:				'progress',
													lengthComputable:	true,
													position:			fileItem.fileSize,
													total:				fileItem.fileSize,
													loaded:				fileItem.fileSize,
													item:				i+1,
													itemTotal:			fileStackLength,
													itemLoaded:			i+1,
													data:				data
												}),
												textStatus,
												jqXHR
											);
										}
									},
									xhrItemFail = function( xhrItem, textStatus, errorThrown ){
										// use the advanced error handling from success callback
										var error = errorGet( true, xhrItem.status, errorThrown );
										xhrItem.responseText = error.text;
										xhrItemDone.call(xhrItem, error.data, textStatus, xhrItem );
									},
									xhrItemAlways = function(){
										if( isAbort ){
											xhrItem.abort();
										}
										else{
											// store responseHeaders
											headerStack.push( xhrItem.getAllResponseHeaders() );
											// start xhr for next item
											nextItem(i+1);
										}
									},
									// setup options for AJAX item
									xhrItemOptions = $.extend( {}, originalOptions, {
										_inputName:	inputName,
										file:		fileObj
									}),
									// xhr object for current item
									xhrItem;

								// check accept mimetype of file
								if( !mime.mime(fileItem.fileType,o.fileMimeAccept) ){
									xhrItemOptions.dataType = 'ERROR';
									xhrItemOptions.ERROR = errorGet(493).data;
								}
								else if( (fileItem.fileSize) > o.fileSizeMax ){
									xhrItemOptions.dataType = 'ERROR';
									xhrItemOptions.ERROR = errorGet(494).data;
								}
								// call AJAX
								xhrItem = $.ajax(xhrItemOptions);
								xhrItem
									.progress(xhrItemProgress)
									.done(xhrItemDone)
									.fail(xhrItemFail)
									.always(xhrItemAlways);
							}
							else{
								var status = 200,
									statusText = 'success';
								if(	isError ){
									status = 491;
									statusText = errorGet(491).data.errorThrown;
								}
								else if( !responseData.length ){
									status = 492;
									statusText = errorGet(492).data.errorThrown;
								}
								// build responseText from all responses
								jqXHR.responseText = '['+responseText.join(',')+']';
								jqXHR.responseXML = null;
								// execute callback
								completeCallback(
									status,
									statusText,
									{
										json:	responseData,
										text:	jqXHR.responseText
									},
									headerStack.join('')
								);
							}
						};

					// start first xhr for first item
					nextItem(0);
				},
				abort: function( statusText ){
					// set abort state
					isAbort = true;
				}
			};
		};
//#JSCOVERAGE_IF false
		// container for iframes
	var	container,
		// handle single file upload with iframe
		singleIFRAME = function( o, originalOptions, jqXHR, progressCallback ){
			o.url = originalOptions.url + (rquery.test(originalOptions.url) ? '&' : '?') + $.param( originalOptions.data );
			// state whether xhr abort
			var isAbort = false,
				isTimeout = false,
				isComplete = false,
				isSuccess = false;
			// results send and abort handle
			return {
				send: function( headers, completeCallback ){
					var id = $.uid('upload'),
						// iframe
						io,
						ioNode,
						// form
						form,
						formNode,
						// file input
						file = $(o.file),
						fileNode = o.file,
						fileNodeName = file.attr('name'),
						filePlaceholder,
						// simulate fileObj
						fileItem = {
							fileName: file.val(),
							fileSize: 204800,
							name: fileNodeName || 'NewFile'
						};
						// get mime type of file
						var temp = /\.([^.]+)$/.exec(fileItem.fileName);
						fileItem.fileType = mime.mime(temp ? temp[1] : '');

						// complete handle
					var	complete = function( event, responseText ){
							clearInterval(interval);
							if( !isAbort && !isComplete ){
								isComplete = true;

								var error,
									status,
									statusText,
									responseData;

								// get response text
								if( !responseText ){
									if( isTimeout ){
										responseText = errorGet(true,408).text;
									}
									else if( ioNode.contentWindow && ioNode.contentWindow.document ){
										responseText = ioNode.contentWindow.document.body ? ioNode.contentWindow.document.body.innerHTML : '';
									}
									else if( ioNode.contentDocument && ioNode.contentDocument.document ){
										responseText = ioNode.contentDocument.document.body ? ioNode.contentDocument.document.body.innerHTML : '';
									}
									if( !responseText ){
										responseText = errorGet(true,408).text;
									}
								}

								// try to parse JSON from response text
								try{
									responseText = responseText.replace(/^(<pre[^>]*>)([^]*?)(<[^<]*pre>)$/,"$2");
									responseData = $.parseJSON(responseText || "null");

									if (responseData.ERROR) {
										error = errorGet(true, responseData.ERROR.status, responseData.ERROR.errorThrown);

										responseText = error.text;
										responseData = error.data;
									}
								}
								catch(error){
									error = errorGet( true, 490 );

									responseText = error.text;
									responseData = error.data;
								}

								// set status
								if (responseData.ERROR) {
									status = responseData.ERROR.status;
									statusText = responseData.ERROR.errorThrown;
								}
								else {
									status = 200;
									statusText = 'success';
								}

								// set success state
								isSuccess = status===200;

								// handle progress item success/error
								// set date for progress event
								var progressEventData = {item:fileItem};
								if( responseData.ERROR ){
									progressEventData.ERROR = responseData.ERROR;
								}
								else{
									progressEventData.success = responseData;
								}
								// set status of main xhr
								jqXHR.status =		isSuccess ? 290 : status;
								jqXHR.statusText =	isSuccess ? 'success-item' : 'error-item';
								// call progress handle
								progressCallback(
									// simulate progress event object
									$.extend($.Event(),{
										type:				'progress',
										lengthComputable:	true,
										position:			204800,
										total:				204800,
										loaded:				204800,
										item:				1,
										itemTotal:			1,
										itemLoaded:			1,
										data:				progressEventData
									}),
									jqXHR.statusText,
									jqXHR
								);

								// set status of main xhr
								if( isSuccess ){
									jqXHR.status =		290;
									jqXHR.statusText =	'success';
								}
								else{
									jqXHR.status =		491;
									jqXHR.statusText =	errorGet(491).data.errorThrown;
								}
								// build responseText from all responses
								jqXHR.responseText = '['+responseText+']';
								jqXHR.responseXML = null;
								// execute callback
								completeCallback(
									jqXHR.status,
									jqXHR.statusText,
									{
										json:	responseData ? [responseData] : [],
										text:	jqXHR.responseText
									},
									''
								);
							}
						},
						// progress handle
						interval,
						progressLoaded = 0,
						progress = function(){
							if( !isAbort ){
								jqXHR.status = 102;
								var textStatus = jqXHR.statusText = 'progress-item';

								progressLoaded += 2048;

								if( progressLoaded > 203776 ){
									clearInterval(interval);
									progressLoaded = 203776;
								}

								progressCallback(
									// simulate progress event object
									$.extend($.Event(),{
										type:				'progress',
										lengthComputable:	true,
										position:			progressLoaded,
										total:				204800,
										loaded:				progressLoaded,
										item:				1,
										itemTotal:			1,
										itemLoaded:			0,
										data:				{item:fileItem}
									}),
									textStatus,
									jqXHR
								);
							}
						};

					// check accept mimetype of file
					if( mime.mime(fileItem.fileName,o.fileMimeAccept) ){
						complete( {}, errorGet(true,493).text );
					}
					else{
						// iframe container
						container = container || $('<div>')
							.css('display','none')
							.appendTo( document.body );
						// iframe to send form
						io = $('<iframe name="'+id+'"></iframe>').appendTo( container );

						ioNode = io[0];
						// bind success/error callback event
						if( window.attachEvent ){
							ioNode.attachEvent( 'onload', complete);
						}
						else{
							ioNode.addEventListener( 'load', function(){
								var self = this,
									args = arguments,
									look = function(){
										var body = (ioNode.contentDocument || ioNode.contentWindow && ioNode.contentWindow.document || {}).body;
										if( (body && body.innerHTML) || loop >= 100 ){
											clearInterval(interval);
											complete.apply(self,args);
										}
									},
									loop = 0,
									// fix bug in load event, use own check of load after load is triggered
									interval = setInterval(look,10);
								look();
							}, false );
						}
						// form
						form = $('<form>')
							.attr({
								target:		id,
								method:		'POST',
								action:		o.url + '&handleashtml=1',
								enctype:	'multipart/form-data',
								encoding:	'multipart/form-data'
							})
							.appendTo( container );
						formNode = form[0];

						if( o.timeout > 0 ){
							// Timeout checker
							setTimeout(function(){
								// Check to see if the request is still happening
								if( !isAbort ){
									isTimeout = true;
									complete();
								}
							}, o.timeout);
						}

						// run progress if defined
						if( o.progress ){
							// simulate xhr progress interval
							interval = setInterval(progress,25);
						}

						// send file
						filePlaceholder = file.clone().insertAfter(file).attr('disabled',true);
						if( !fileNodeName ){
							file.attr('name','NewFile');
						}
						form.append(file);
						formNode.submit();
						if( !fileNodeName ){
							file.removeAttr('name');
						}
						file.insertBefore(filePlaceholder);
						filePlaceholder.remove();
						form.remove();
					}
				},
				abort: function( statusText ){
					// set abort state
					isAbort = true;
				}
			};
		};
//#JSCOVERAGE_ENDIF
	$.ajaxTransport( 'json', function( o, originalOptions, jqXHR ){
		var progressHook = function(){
				var context = o.context || jqXHR,
					progress = jqXHR.progress,
					deferred = $.Deferred();

				jqXHR.progress = function(){
					deferred.progress.apply( deferred, arguments );
					return jqXHR;
				}

				if( o.progress ){
					jqXHR.progress(o.progress);
				}

				return (function(){
						deferred.notifyWith( context, arguments );
					});
			};

		// transport file
		if( o.file ){
				// be sure o.file is not modified
			var fileData = o.file = originalOptions.file,
				fileObj  = toString.call(fileData)==='[object File]',
				fileElem = fileObj ? $() : $(fileData);

			if( fileElem.is(':file') ){
				// prepare options
				o['_inputName'] = fileElem.attr('name') || 'NewFile';
				o.fileMimeAccept = o.fileMimeAccept || fileElem.attr('accept') || '*/*';
				originalOptions.data = $.extend( true, {ErrorAction:'JSONAjaxTransportFileError'}, originalOptions.data );
				if( html5 ){
					return multiAJAX( o, originalOptions, jqXHR, progressHook() );
				}
				else{
					return singleIFRAME( o, originalOptions, jqXHR, progressHook() );
				}
			}
			else if( fileObj && o['_inputName'] ){
				return singleAJAX( o, originalOptions, jqXHR, progressHook() );
			}
		}
	});

	// handle client error to prevent call a real xhr
	$.ajaxTransport( 'ERROR', function( o, originalOptions, jqXHR ){
		return {
			send: function( headers, completeCallback ){
				var error = o.ERROR || errorGet(490).data;
				completeCallback( error.status, error.errorThrown, {}, '' );
			}
		};
	});

	return de_epages;

});