(function(webshim, $){
	"use strict";
	if(!window.console){return;}
	var mediaelement = webshim.mediaelement;
	var hasFlash = swfmini.hasFlashPlayerVersion('10.0.3');
	var hasNative = webshim.support.mediaelement;
	var url = location.protocol+'//'+location.hostname;
	var tests = {
		urlInValid: {
			level: 1,
			test: (function(){
				var reg = /^[a-z0-9\,\.\:\/\-_\;\?#\+\*\!\(\)\$\;\&\=\+]+$/i;
				return function(src){
					return (src.src && !reg.test(src.src));
				};
			})(),
			srcTest: {poster: 1, srces: 1},
			message: "URL has invalid characters. Remove any special characters and mutated vowels."
		},
		noHeaderTest: {
			level: 5,
			test: function(src){
				return src.computedContainer != 'video/youtube' && !src.ajax && !src.httpError;
			},
			srcTest: {srces: 1},
			message: "Could not run HTTP network tests (cross-domain) for all sources. Check manually."
		},
		hasNoTypeAttribute: {
			level: 4,
			test: function(src){
				return !src.declaredType && !src.typeNotRequired;
			},
			srcTest: {srces: 1},
			message: "The source element has no type attribute specified. Browser needs to download file before testing compatibility. Add a proper type attribute."
		},
		couldNotComputeTypeDeclaredTypeAbsent: {
			level: 1,
			test: function(src){
				return (!src.computedContainer && !src.declaredType);
			},
			srcTest: {srces: 1},
			message: "The source element has no type attribute specified and the extensions seems unknown. Add a proper type attribute."
		},
		httpError: {
			level: 2.5,
			test: function(src){

				if(!src.ajax || src.decode.swf.success || src.decode.native.success){
					return 'not testable';
				} else {
					return !!(src.httpError && !src.httpErrorText);
				}
			},
			srcTest: {srces: 1},
			message: "There was an unknown http error. Check source/URL."
		},
		fileEncoding: {
			test: function(){
				return 'This test does not test file encoding, framerate compatibility, moov index, encoding profiles. So there is room to fail!';
			},
			srcTest: {srces: 1}
		},

		explicitHttpError: {
			level: 1,
			test: function(src){
				if(!src.ajax || src.decode.swf.success || src.decode.native.success){
					return 'not testable';
				} else {
					return !!(src.httpErrorText);
				}
			},
			srcTest: {srces: 1},
			message: "There was a http error. Check source/URL."
		},
		charsetInContentType: {
			level: 2.5,
			test: function(src){
				if(!src.ajax || src.httpError){
					return 'not testable';
				} else {
					return src.headerType && (/charset=/i).test(src.headerType);
				}
			},
			srcTest: {srces: 1},
			message: "Content-Type header of media file sends charset. Remove charset information."
		},
		explicitTypeMix: {
			level: 3,
			test: function(src){
				if(src.declaredContainer && src.headerType){
					return src.headerType != src.declaredType;
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Content-Type header and attribute type do not match. Set same and proper type value."
		},
		noContentType: {
			level: 2.5,
			test: function(src){
				if(src.ajax && !src.httpError){
					return !(src.headerType);
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Content-Type header for media file is either empty or application/octet-stream."
		},
		noContentLength: {
			level: 3,
			test: function(src){
				if(src.ajax && !src.httpError){
					return !(src.headers['Content-Length']);
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Content-Length header for media file does not send value."
		},
		noRange: {
			level: 3,
			test: function(src){
				if(src.ajax && !src.httpError){
					return !(src.headers['Accept-Ranges']);
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Accept-Ranges header for media file does not send value. Make sure server supports Range requests in bytes"
		},
		explicitNoRange: {
			level: 2.5,
			test: function(src){
				if(src.ajax && !src.httpError){
					return (src.headers['Accept-Ranges'] == 'none');
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Server does not support Range requests. Make sure server supports Range requests in bytes"
		},
		doubleEncoded: {
			level: 1,
			test: function(src){
				if(src.ajax && !src.httpError){
					return ((/[defalte|gzip]/i).test(src.headers['Content-Encoding']));
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Content of media file is encoded with gzip/defalte. Make sure to not encode it. It's already encoded."
		},
		mediaAttachment: {
			level: 1,
			test: function(src){
				if(src.ajax && !src.httpError){
					return (/attach/i.test(src.headers['Content-Disposition']));
				} else {
					return 'not testable';
				}
			},
			srcTest: {srces: 1},
			message: "Content-Disposition header wants media file to be downloaded, but not to be played."
		},
		badTypeMix: {
			level: 1,
			test: function(src, infos){
				var ret = false;

				var isPlayableHtml, isPlayableHeader;
				var htmlContainer = src.declaredContainer || src.computedContainer;
				var headerContainer = src.headerContainer;
				if(headerContainer && htmlContainer){
					if(headerContainer != htmlContainer){
						isPlayableHtml = mediaelement.swfMimeTypes.indexOf(htmlContainer) != -1;
						isPlayableHeader = mediaelement.swfMimeTypes.indexOf(headerContainer) != -1;
						if(isPlayableHtml != isPlayableHeader){
							ret = true;
						}

						if(!ret && infos.element.canPlayType){

							isPlayableHtml = !!infos.element.canPlayType(htmlContainer);
							isPlayableHeader = !!infos.element.canPlayType(headerContainer);
							if(isPlayableHtml != isPlayableHeader){
								ret = true;
							}
						}
					}
				} else {
					ret = 'not testable';
				}

				return ret;
			},
			srcTest: {srces: 1},
			message: "Content-Type header and attribute type do not match and are quite different. Set same and proper type value."
		},

		typeMix: {
			level: 2.5,
			test: function(src, infos){
				var ret = false;

				var isPlayableComputed, isPlayableDeclared;
				if(!src.headerContainer && src.declaredContainer && src.computedContainer && src.computedContainer != src.declaredContainer){
					isPlayableComputed = mediaelement.swfMimeTypes.indexOf(src.computedContainer) != -1;
					isPlayableDeclared = mediaelement.swfMimeTypes.indexOf(src.declaredContainer) != -1;
					if(isPlayableComputed != isPlayableDeclared){
						ret = true;
					}

					if(!ret && infos.element.canPlayType){

						isPlayableComputed = !!infos.element.canPlayType(src.computedContainer);
						isPlayableDeclared = !!infos.element.canPlayType(src.declaredContainer);
						if(isPlayableComputed != isPlayableDeclared){
							ret = true;
						}
					}
				}

				return ret;
			},
			srcTest: {srces: 1},
			message: "Computed type and declared type are different. Needs manual check."
		},
		hasNoPlayableSrc: {
			level: 1,
			test: function(infos){
				var hasPlayable = false;

				$.each(infos.srces, function(i, src){
					var pluginContainer = src.declaredContainer || src.computedContainer;
					var nativeContainer = src.headerContainer || pluginContainer;

					if(mediaelement.swfMimeTypes.indexOf(pluginContainer) != -1){
						hasPlayable = true;
						return false;
					}

					if(infos.element.canPlayType && infos.element.canPlayType(pluginContainer) && infos.element.canPlayType(nativeContainer)){
						hasPlayable = true;
						return false;
					}
				});

				return !hasPlayable;
			},
			message: "Mediaelement has no source to be played in browser or by plugin. Use at least a video/mp4 source."
		},
		endJump: {
			level: 2.5,
			test: function(src){
				return src.decode.swf.endJump || src.decode.native.endJump;
			},
			srcTest: {srces: 1},
			message: 'src jumped to end too soon. Check negative timestamps: https://bugzilla.mozilla.org/show_bug.cgi?id=868797'
		},
		swfTimeout: {
			level: 3,
			test: function(src){
				return src.decode.swf.timeout;
			},
			srcTest: {srces: 1},
			message: 'Could not run decode tests. Maybe moovposition is on end?'
		},
		moovPosition: {
			level: 2,
			test: function(src){
				if(src.decode.swf.moovposition){
					return src.decode.swf.moovposition > 300;
				}
				return false;
			},
			srcTest: {srces: 1}
		},
		tabletDecode: {
			level: 2,
			test: function(infos){
				var hasSwfSuccess = false;
				var hasPlayableh264 = false;
				if(hasFlash){
					$.each(infos.srces, function(i, src){
						var swfDecode = src.decode.swf;

						if(('videocodecid' in swfDecode)){
							hasSwfSuccess = true;
						}
						if(swfDecode.videocodecid != 'avc1' || swfDecode.avclevel > 31 || swfDecode.height * swfDecode.width > 921600){
							return;
						}
						hasPlayableh264 = true;
						return false;
					});
				}

				return (!hasSwfSuccess) ?  false : !hasPlayableh264;
			},
			message: 'Not playable on more than 25% of smartphone and more than 15% of tablet devices. In case you want to support 75% of smartphone- and 90% of tablet devices you need to provide a source encoded with H.264, High Profile (HP), Level 3.1, up to 1280 * 720.'
		},
		allTabletDecode: {
			level: 3,
			test: function(infos){
				var hasSwfSuccess = false;
				var hasPlayableh264 = false;
				if(hasFlash){
					$.each(infos.srces, function(i, src){
						var swfDecode = src.decode.swf;

						if(('videocodecid' in swfDecode)){
							hasSwfSuccess = true;
						}
						if(swfDecode.videocodecid != 'avc1' || swfDecode.avcprofile > 77 || swfDecode.avclevel > 31 || swfDecode.height * swfDecode.width > 921600){
							return;
						}
						hasPlayableh264 = true;
						return false;
					});
				}

				return (!hasSwfSuccess) ?  false : !hasPlayableh264;
			},
			message: 'Not playable on more than 15% of smartphone and more than 5% of tablet devices. In case you want to support 90% of smartphone- and 99% of tablet devices you need to provide a source encoded with H.264, Main Profile (HP), Level 3.1, up to 1280 * 720.'
		},
		smartphoneDecode: {
			level: 3.5,
			test: function(infos){
				var hasSwfSuccess = false;
				var hasPlayableh264 = false;
				if(hasFlash){

					$.each(infos.srces, function(i, src){
						var swfDecode = src.decode.swf;

						if(('videocodecid' in swfDecode)){
							hasSwfSuccess = true;
						}
						if(swfDecode.videocodecid != 'avc1' || swfDecode.avcprofile > 77 || swfDecode.avclevel > 30 || swfDecode.height * swfDecode.width > 345600){
							return;
						}
						hasPlayableh264 = true;
						return false;
					});
				}

				return (!hasSwfSuccess) ?  false : !hasPlayableh264;
			},
			message: 'Not playable on more than 10% of smartphones: In case you want to support 90% of smartphone- and 99% of tablet devices you need to provide a source encoded with H.264, Main Profile (HP), Level 3.1, up to 720 * 404 / 640 * 480.'
		},
		notAllSmartphoneDecode: {
			level: 4,
			test: function(infos){
				var hasSwfSuccess = false;
				var hasPlayableh264 = false;

				if(hasFlash){
					$.each(infos.srces, function(i, src){
						var swfDecode = src.decode.swf;
						if(('videocodecid' in swfDecode)){
							hasSwfSuccess = true;
						}
						if(swfDecode.videocodecid != 'avc1' || swfDecode.avcprofile > 66 || swfDecode.avclevel > 30 || swfDecode.height * swfDecode.width > 307200){
							return;
						}
						hasPlayableh264 = true;
						return false;
					});
				}


				return (!hasSwfSuccess) ?  false : !hasPlayableh264;
			},
			message: 'Not playable on more than 1% of smartphones: In case you want to support 99% of all devices you need to provide a source encoded with H.264, Baseline Profile (BP), Level 3.0, up to 720 * 404 / 640 * 480. You might want to use multiple sources to satisfy quality and maximum device compatibility.'
		},
		needsFlashInstalled: {
			level: 1,
			test: function(infos){
				var flashCanPlay = false;
				var nativeCanPlay = false;
				if(!hasFlash){
					$.each(infos.srces, function(i, src){
						var pluginContainer = src.declaredContainer || src.computedContainer;
						var nativeContainer = src.headerContainer || pluginContainer;

						if(mediaelement.swfMimeTypes.indexOf(pluginContainer) != -1){
							flashCanPlay = true;
						}

						if(infos.element.canPlayType && (pluginContainer == 'video/youtube' || (infos.element.canPlayType(pluginContainer) && infos.element.canPlayType(nativeContainer)))){
							nativeCanPlay = true;
							return false;
						}
					});
				}

				return flashCanPlay && !nativeCanPlay;
			},
			message: "While media file could be played by flash plugin, Browser has no flash installed. Use at least a video/mp4 source and install flash. Or add additionally a video/webm file."
		},
		hasNoSwfPlayableSrc: {
			level: 1,
			test: function(infos){
				var hasPlayable = false;

				$.each(infos.srces, function(i, src){
					var pluginContainer = src.declaredContainer || src.computedContainer;

					if(mediaelement.swfMimeTypes.indexOf(pluginContainer) != -1){
						hasPlayable = true;
						return false;
					}

				});

				return !hasPlayable;
			},
			message: "Mediaelement has no source to be played by fallback plugin. Use at least a video/mp4 source."
		},
		hasNoNativePlayableSrc: {
			level: 4,
			test: function(infos){
				var hasPlayable = false;

				if(infos.element.canPlayType){
					$.each(infos.srces, function(i, src){
						var pluginContainer = src.declaredContainer || src.computedContainer;
						var nativeContainer = src.headerContainer || pluginContainer;

						if(pluginContainer == 'video/youtube' || (infos.element.canPlayType(pluginContainer) && infos.element.canPlayType(nativeContainer))){
							hasPlayable = true;
							return false;
						}

					});
				}

				return !hasPlayable;
			},
			message: "Mediaelement has no source to be played native. Use at least a video/mp4 and a video/webm source."
		},
		misLeadingAttrMode: {
			level: 2,
			test: function(infos){
				return (infos.srces.length > 1 && infos.srces[0].attrMode);
			},
			message: "Mediaelement has a src attribute and some source child elements. Only src attribute is used."
		},
		emptySrc: {
			level: 2,
			test: function(src){
				return src.src && !src.attrSrc;
			},
			srcTest: {poster: 1, srces: 1},
			message: "The src or poster attribute is an empty string, which is not allowed."
		}
	};

	function runMediaTest(src, container, provider, infos){
		var timeoutTimer, playTimer;
		var promise = $.Deferred();
		var $container = $('#wsmediatestcontainer');
		var $element = $('<div />').css({width: 320, height: 120, float: 'left'});
		var $media = $(document.createElement(infos.nodeName))
			.attr({
				src: src.src,
				'data-type': container,
				'controls': 'controls',
				preload: 'none'
			})
		;
		var resolvePromise = function(){
			$media.pause();
			setTimeout(function(){
				$element.remove();
				if(!$('video, audio', $container).length){
					$container.remove();
				}
			}, 9);
			setTimeout(function(){
				promise.resolve();
			}, 99);
		};
		var runEnded = function(e){
			var duration = $media.prop('duration');
			var currentTime = $media.prop('currentTime');

			if(duration && duration > 5){
				if(currentTime > 0 && currentTime < 5){
					resolvePromise();
				} else if(e.type == 'ended' || currentTime >= duration -1){
					src.decode[provider].endJump = true;
					resolvePromise();
				}
			} else {
				resolvePromise();
			}
		};
		var resolve = function(e){
			clearTimeout(timeoutTimer);
			if(e){
				if(e.type == 'loadedmetadata'){

					if(provider == 'swf'){
						try {
							src.decode[provider] = 	$media.getShadowElement().find('object, embed')[0].api_get('meta');
						} catch(e){}
					}
					if(!src.decode[provider] || $.isEmptyObject(src.decode[provider])){
						src.decode[provider] = {
							duration: $media.prop('duration'),
							height: $media.prop('videoHeight'),
							width: $media.prop('videoWidth')
							//todo at test for seekable
							//,seekable: ($media.prop('seekable') || []).length
						};
					}
					src.decode[provider].success = true;
				} else {
					src.decode[provider] = {
						error: $media.prop('error'),
						mediaError: $media.data('mediaerror'),
						success: false
					};
				}

			} else {
				src.decode[provider] = {
					success: false,
					timeout: true
				};
			}
			setTimeout(function(){
				$media.play();
			}, 9);
			$media.on('ended timeupdate', runEnded);
			clearTimeout(playTimer);

			setTimeout(resolvePromise, 300);
		};

		if(!$container.length){
			$container = $('<div id="wsmediatestcontainer" />')
				.css({position: 'fixed', top: 0, left: 0, right: 0, padding: 10, zIndex: 9999999999})
				.prependTo('body')
			;
		}

		$media
			.on('mediaerror loadedmetadata', resolve)
			.appendTo($element)
		;

		if(provider == 'native'){
			$media.on('error', resolve);
		}

		$element.appendTo($container);
		timeoutTimer = setTimeout(resolve, 40000);
		playTimer = setTimeout(function(){
			$media.prop('muted', true);
			$media.play();
		}, 200);
		$media.mediaLoad();
		return promise;
	}

	function runDecodeTest(src, infos){
		var promises = [];
		var type = src.declaredContainer || src.computedContainer || src.headerContainer || '';
		var preferFlash = webshim.cfg.mediaelement.preferFlash;
		if(hasNative && infos.element.canPlayType(type)){
			webshim.cfg.mediaelement.preferFlash = false;
			promises.push(runMediaTest(src, type, 'native', infos));
		} else {
			src.decode.native = {success: false, notsupported: true};
		}
		if(hasFlash && !(/youtube|rtmp/i.test(type)) && mediaelement.swfMimeTypes.indexOf(type) != -1){
			webshim.cfg.mediaelement.preferFlash = true;
			promises.push(runMediaTest(src, type, 'swf', infos));
		} else {
			src.decode.swf = {success: false, notsupported: type != 'video/youtube'};
		}
		webshim.cfg.mediaelement.preferFlash = preferFlash;
		return $.when.apply($, promises);
	}

	var runningDecodeTests = 0;
	var decodeObj = {};
	function runDeferredeDcodeTest(src, infos){

		var promise = $.Deferred();
		var onRun = function(){
			if(!runningDecodeTests){
				runningDecodeTests++;
				$(decodeObj).off('finish', onRun);
				runDecodeTest(src, infos).always(function(){
					promise.resolve();
					runningDecodeTests--;
					$(decodeObj).trigger('finish');
				});
			}

		};
		if(runningDecodeTests){
			$(decodeObj).on('finish', onRun);
		} else {
			onRun();
		}

		src.decode.promise = promise.promise();
	}

	function getSrcInfo(elem, infos){
		var ajax;
		var src = {
			src: $.prop(elem, 'src'),
			attrSrc: $.trim($.attr(elem, 'src')),
			declaredType: $.attr(elem, 'type') || $(elem).attr('data-type') || '',
			errors: {},
			decode: {
				native: {},
				swf: {}
			}
		};
		src.declaredContainer = src.declaredType.split(';')[0].trim();
		try {
			src.computedContainer = mediaelement.getTypeForSrc( src.src, infos.nodeName);
		} catch(e){
			src.computedContainer = '';
		}

		if(!src.src.indexOf(url)){

			try {
				src.headerType = '';
				src.headers = {};
				ajax = $.ajax({
					url: src.src,
					type: 'head',

					success: function(){
						src.headerType = ajax.getResponseHeader('Content-Type') || '';
						if((/^\s*application\/octet\-stream\s*$/i).test(src.headerType)){
							src.headerType = '';
							src.errors.octetStream = 'octetStream';
						}
						src.headerContainer = $.trim(src.headerType.split(';')[0]);
						['Location', 'Content-Type', 'Content-Length', 'Accept-Ranges', 'Content-Disposition', 'Content-Encoding'].forEach(function(name){
							src.headers[name] = ajax.getResponseHeader(name) || '';
						});

					},
					error: function(xhr, status, statusText){
						src.httpError = status;
						src.httpErrorText = statusText;
					}
				});
				src.ajax = ajax;
			} catch(e){}
		} else {
			src.cors = true;
		}

		runDeferredeDcodeTest(src, infos);

		return src;
	}

	function resolveSrces(infos){
		var src;
		var srces = [];
		var ajaxes = [];
		var $sources = $('source', infos.element);
		var promises = [];
		var mainPromise = $.Deferred();
		var i = 0;
		var resolve = function(){
			i++;
			if(i > 1){
				mainPromise.resolve();
			}
		};

		if($.prop(infos.element, 'src')){
			src = getSrcInfo(infos.element, infos);
			src.attrMode = true;
			src.typeNotRequired = true;
			srces.push(src);
		}

		$sources.each(function(i){
			var src = getSrcInfo(this, infos);
			src.typeNotRequired = !!(i && i >= $sources.length - 1);

			srces.push(src);

			if(src.ajax){
				ajaxes.push(src.ajax);
			}
			if(src.decode.promise){
				promises.push(src.decode.promise);
			}
		});

		infos.srces = srces;
		$.when.apply($, promises).always(resolve);
		$.when.apply($, ajaxes).done(resolve).fail(function(){
			setTimeout(resolve, 200);
		});

		return mainPromise.promise();
	}



	function runTests(infos){

		$.each(tests, function(name, obj){
			var localMessage;
			var failed = false;
			var message = obj.message || name;
			if(obj.srcTest){
				if(obj.srcTest.poster){
					localMessage = obj.test(infos.poster, infos);
					if(localMessage){
						if(typeof localMessage == 'string'){
							infos.poster.errors[name] = localMessage;
						} else {
							infos.poster.errors[name] = message;
							failed = true;
						}
					}
				}

				if(obj.srcTest.srces){
					infos.srces.forEach(function(src){
						localMessage = obj.test(src, infos);
						if(localMessage){
							if(typeof localMessage == 'string'){
								src.errors[name] = localMessage;
							} else {
								src.errors[name] = message;
								failed = true;
							}
						}
					});

				}
			} else {
				failed = obj.test(infos);
			}

			if(failed){
				infos.errors.push({
					message: message,
					level: obj.level,
					name: name
				});
			}
		});

		infos.errors.sort(function(a, b){
			return a.level > b.level;
		});
		console.log('---- Media Test Start ----');
		console.log('Testing results for mediaelement network + markup debugger. For detailed information expand the following object:', infos);
		if(infos.errors.length){
			if(infos.errors[0].level < 3){
				console.log('Found '+ infos.errors.length + ' errors/warnings with at least 1 critical issue.');
			} else if(infos.errors[0].level < 4) {
				console.log('Found '+ infos.errors.length + ' errors/warnings.');
			} else {
				console.log('Found '+ infos.errors.length + ' warnings but no critical issue.');
			}
			infos.errors.forEach(function(error){
				var type = 'log';
				if(console.error && console.warn){
					if(error.level < 3){
						type = 'error';
					} else if(error.level < 4){
						type = 'warn';
					}
				}
				console[type](error.message, 'priority level: '+ error.level, error.name);
			});
		} else {
			console.log('Congratulations: No errors found for video.');
		}
		console.log('---- Media Test End ----');
		console.log('----');
	}

	function getMediaInfo(elem){
		var infos = {
			element: elem,
			nodeName: elem.nodeName.toLowerCase(),
			errors: [],
			poster: {
				src: $.prop(elem, 'poster'),
				attrSrc: $.trim($.attr(elem, 'poster')),
				errors: {}
			},
			mediaError: $.prop(elem, 'error'),
			wsError: $(elem).data('mediaerror')
		};
		var promise = resolveSrces(infos);
		var initTests = function(){
			runTests(infos);
		};
		promise.always(initTests);
	}

	var timedMediaInfo = function(i){
		var elem = this;
		setTimeout(function(){
			getMediaInfo(elem);
		}, i * 100);
	};

	console.log('Running mediaelement debugger. Only run these tests in development never in production. set webshim.setOptions("debug", false); to remove. Debugger only tests media on same domain and does not test all file encoding issues. So there is still room to fail!');


	if(webshim.cfg.extendNative){
		console.log('mediaelement debugger does not detect all problems with extendNative set to true. Please set webshim.setOptions("extendNative", false);');
	}
	webshim.addReady(function(context, $insertedElement){
		$('video, audio', context)
			.add($insertedElement.filter('video, audio'))
			.each(timedMediaInfo)
		;
	});
	webshim.mediaelement.getMediaInfo = getMediaInfo;
})(webshim, webshim.$);