(function ($) {

	// AjaxManager for handling calls to Ajax controller methods.
	AjaxManager = function () {

		function _processResponse(response, options, responsesResult) {

			// Process the json WebPartResponse object.       
			var isSuccess = false;
			switch (response.ResponseType.Value) {
				case 'Success':
					isSuccess = true;
					break;

				case 'Redirect':
					if (response.RedirectUrl) {
						window.location = response.RedirectUrl;
					}
					break;

				case 'Error':
					if (response.ActionErrors && response.ActionErrors.length > 0) {
						// Show the errors in the error div.
						_showErrors(response.ActionErrors);
					}

					// failed
					isSuccess = false;
					break;

				case 'Views':
					var isRenderSuccessful = true;
					if (response.Views && response.Views.length > 0) {
						// Render all the views
						for (var i = 0; i < response.Views.length; i++) {
							var view = response.Views[i];
							isRenderSuccessful = isRenderSuccessful && _renderView(view.Content, view.InsertionMode, view.Region);
						}

						if (isRenderSuccessful) {
							if (response.Resources && response.Resources.length > 0) {
								_renderViewResources(response.CombinatorUri, response.Resources, options, responsesResult);
							}
							isSuccess = true;
						}
					}
					break;

				default:
					// We can't recognize the type.
					isSuccess = false;
					break;
			}

			// Flag any response failure in the overall result.
			if (!isSuccess) {
				responsesResult.isSuccess = false;
			}
		}

		function _renderView(content, insertionMode, region) {

			// Places the content into the given region.            
			var isRenderSuccessful = false;
			var $region = $('#' + region);

			switch (insertionMode.Value) {
				case 'InsertBefore':
					$region.prepend(content);
					isRenderSuccessful = true;
					break;

				case 'InsertAfter':
					$region.append(content);
					isRenderSuccessful = true;
					break;

				case 'Replace':
				default:
					$region.empty().append(content);
					isRenderSuccessful = true;
					break;
			}

			if (isRenderSuccessful) {
				$region.trigger($.extend($.Event(), { type: 'regionupdated', target: $region }));
			}

			return isRenderSuccessful;
		}

		function _renderViewResources(cdnCombinatorUrl, resources, options, responsesResult) {

			// Separate the Link resources from the Block resources.
			var resourceLinks = [];
			var resourceBlocks = [];

			$(resources).each(function () {
				// Push on to link or block resource collection based on if there is a src.
				if (this.Src !== null && this.Src.length > 0) {
					resourceLinks.push(this.Src);

				} // Must be a block resource (script block).
				else {
					resourceBlocks.push(this);
				}
			});

			// Add resources to result queue to wait for load before calling complete.
			var queueItem = {
				id: (new Date()).getTime() + '' + Math.floor(Math.random() * 100) // Unique ID.
			};
			responsesResult.queuedResources.push(queueItem);

			//console.log('setup: ' + options.url);

			var onResourcesLoaded = function (resBl, opt, resp, qItem) {
				return function () {
					//console.log('callback: ' + opt.url);
					// Output resource blocks.
					_outputResourceBlocks(resBl);

					// Remove resource queue item from queue to wait for.
					var queue = resp.queuedResources;
					for (var i = 0; i < queue.length; i++) {
						var item = queue[i];
						if (item.id === qItem.id) {
							queue.splice(i, 1);
							i = queue.length; // break;
						}
					}
					// Call complete, when responsesResult.queuedResources.length === 0 it will run.
					_responsesComplete(opt, resp);
				};
			};

			// Output resource links.
			ResourceManager.addResources(cdnCombinatorUrl, resourceLinks, onResourcesLoaded(resourceBlocks, options, responsesResult, queueItem));
		}

		function _outputResourceBlocks(resourceBlocks) {

			if (resourceBlocks && resourceBlocks.length > 0) {
				var scriptBlock = '';

				for (var i = 0; i < resourceBlocks.length; i++) {
					var resource = resourceBlocks[i];

					// Build the Script Block.
					if (resource.Type.Value === 'Script' && resource.Content) {
						scriptBlock += resource.Content;
					}
				}

				// Get the document head to get ready to spit the content out.
				var head = document.getElementsByTagName("head")[0];

				// Add the script block to the head.
				if (scriptBlock !== '') {
					var scriptBlockTag;
					if ($.browser.msie) {
						// This works in IE and Firefox
						scriptBlockTag = '<script type="text/javascript">' + scriptBlock + '</script>';
						$(scriptBlockTag).appendTo($('head'));
					} else {
						// This works in Firefox and Webkit
						scriptBlockTag = document.createElement('script');
						scriptBlockTag.type = 'text/javascript';
						scriptBlockTag.appendChild(document.createTextNode(scriptBlock));
						head.appendChild(scriptBlockTag);
					}
				}
			}
		}

		function _showErrors(errors) {

			// Add the errors to the correct error Div and display it.
			var newCosmeticErrors = false;
			var newActionPreventedOrCriticalErrors = false;

			// Template string for errors
			var errorMsg = '<li><strong>{ExceptionMessage}</strong> - {ExceptionType}</li>';

			// Build up the error lists and add HTML
			for (var i = 0; i < errors.length; i++) {
				var error = errors[0];
				if (error.Severity.Value === 'Cosmetic') {
					// Show the cosmetic error dialog.
					$('#cosmeticErrorContainer ul').empty().append($.substitute(errorMsg, error));
					newCosmeticErrors = true;

				} else if (error.Severity.Value === 'ActionPrevented' || error.Severity.Value === 'CriticalFailure') {
					// Show the actionPrevented error dialog.
					$('#ErrorDialog ul').append($.substitute(errorMsg, error));
					newActionPreventedOrCriticalErrors = true;
				}
			}

			// Show error div if there are errors.
			if (newCosmeticErrors) {
				$('#cosmeticErrorContainer').css({ 'display': 'block' });
			}

			// Show action prevented and critical errors in a modal dialog.
			if (newActionPreventedOrCriticalErrors) {
				alert('errors');
			}
		}

		function _processError(xhr, options) {
			_public.log(xhr);
			options.failure(null, xhr);
			options.after();
		}

		function _processResponses(jsonResponse, options) {

			// If the jsonResponse has not been parsed, parse it now.
			if (typeof (jsonResponse) === "string") {
				jsonResponse = $.parseJSON(jsonResponse);
			}
			// When each response is processed, if it needs to wait for resources they will be added to the queue.
			// Track if any response processing fails.
			var responsesResult = { isSuccess: true, queuedResources: [], jsonResponse: jsonResponse, areResponsesProcessed: false };
			$(jsonResponse).each(function () {
				_processResponse(this, options, responsesResult);
			});
			// Call complete function immediately if there is no need to wait for resources; otherwise wait for it to be called by _renderViewResources.
			responsesResult.areResponsesProcessed = true;
			_responsesComplete(options, responsesResult);
		}

		function _responsesComplete(options, responsesResult) {

			// This is separated out because all scripts should be loaded before success is called.
			// If there are no resources waiting && all responses have been processed, call success or failure.
			if (responsesResult.areResponsesProcessed && responsesResult.queuedResources.length === 0) {
				// Call the success or failure callback function.
				if (responsesResult.isSuccess) {
					options.success(responsesResult.jsonResponse);
				} else {
					options.failure(responsesResult.jsonResponse);
				}

				options.after();
			}
		}

		var _public = {

			initForm: function (options) {

				// Merge options and defaults.
				var settings = $.extend({}, {
					formSelector: '',
					success: function () { },
					failure: function () { },
					before: function () { },
					after: function () { },
					error: function () { }
				}, options);

				if (settings.formSelector) {
					// Only select the first form element.
					var $form = $(settings.formSelector);
					$form.bind('submit', function (e) {

						settings.before();

						var $upload = $form.find('input[type=file]');

						// Use Ijax (iFrame Ajax) if there are files to be uploaded
						if ($upload.length > 0) {

							// Check to see if any input has value. 
							var filesSelected = $upload.filter(function () {
								return $(this).val();
							}).length > 0;

							if (filesSelected) {

								var iFrameId = $form.attr('id') + '_async-iframe';
								var $iFrame;

								$form.attr({
									enctype: 'multipart/form-data',
									encoding: 'multipart/form-data',
									target: iFrameId
								}).append($('<input/>').attr({
									type: 'hidden',
									name: 'webpart-request-type',
									value: 'iframe'
								}));

								$(document.body).append('<iframe id="' + iFrameId + '" name="' + iFrameId + '" src="about:blank" class="am_ijaxIFrame">');

								// Requires ijax.
								$iFrame = $('#' + iFrameId).bind('load', function () {

									var doc = undefined;
									var iFrameElem = $iFrame[0];

									if (iFrameElem.contentDocument) {
										doc = iFrameElem.contentDocument;
									}
									else if (iFrameElem.contentWindow) {
										doc = iFrameElem.contentWindow.document;
									}
									else {
										doc = window.frames[iFrameId].document;
									}

									if (doc.location.href !== 'about:blank') {
										// this would be a postback.
										// todo: this will return html if failure.

										try {
											// Response is populated on to document variable by HTML.
											_processResponses(doc.webPartResponse, settings);
										} catch (e) {
											settings.failure(null, e);
										}

										settings.after();
									}
								}).appendTo('body');

								// Submit the form, allow default behavior to take place. 
								return true;
							}
							else {
								settings.error();
								settings.after();
							}
						}
						else {
							// No files to upload, do regular Ajax request.

							var url = $form.attr('action');
							var method = $form.attr('method');
							var formData = {};

							// Populate formData object inputs.
							var $inputs = $form.find('input');
							$inputs.each(function (i) {
								formData[$inputs[i].name] = $inputs[i].value;
							});

							// Populate formData object text areas.
							var $textareas = $form.find('textarea');
							$textareas.each(function (i) {
								formData[$textareas[i].name] = $textareas[i].value;
							});

							_public.send({
								url: url,
								data: formData,
								method: method,
								success: settings.success,
								failure: settings.failure
							});

							settings.after();

							// Stop form from submitting.
							return false;
						}

						return false;
					});
				}
			},
			log: function (xhr) {
				// Function used to log when there is an Ajax response error. Overload AjaxManager.log in your code to have this function log somewhere useful.
			},
			send: function (params) {
				// Submits the specified form and renders the results to the view.
				// url: Url to send request to.
				// data: Hash or object of values to submit.               
				// method: Method to send request as ('get' or 'post').
				// success: The function to execute on success.
				// failure: The function to execute on failure.

				var options = $.extend({
					success: function () { },
					failure: function () { },
					before: function () { },
					after: function () { },
					cache: false,
					url: '',
					method: 'get',
					dataType: 'jsonp'
				}, params);
				$.ajax({
					beforeSend: function (xhr) {
						// Set header to indicate to server that request is ajax.                        
						xhr.setRequestHeader('webpart-request-type', 'ajax');
						options.before();
					},
					cache: options.cache,
					success: function (json) {
						_processResponses(json, options);
					},
					error: function (xhr) {
						_processError(xhr, options);
					},
					complete: function () {
						// Complete is called twice when using JSONP with the same domain. jquery Bug #5383
					},
					data: options.data,
					dataType: options.dataType,
					type: options.method,
					url: options.url
				});
			}
		};
		return _public;

	} ();

})(jQuery);

