/*

Uniform v1.8.0+f
Copyright © 2009 Josh Pyles / Pixelmatrix Design LLC
http://pixelmatrixdesign.com

Requires jQuery 1.3 or newer

Much thanks to Thomas Reynolds and Buck Wilson for their help and advice on
this.

Disabling text selection is made possible by Mathias Bynens
<http://mathiasbynens.be/> and his noSelect plugin.
<http://github.com/mathiasbynens/noSelect-jQuery-Plugin>.

Also, thanks to David Kaneda and Eugene Bond for their contributions to the
plugin.

This version (+f) is from fidian's forked repository until the changes can
get merged upstream.  See https://github.com/fidian/uniform

License:
MIT License - http://www.opensource.org/licenses/mit-license.php

Enjoy!

*/
/*global jQuery, window, document*/

(function ($, undef) {
	"use strict";

	$.uniform = {
		// Default options that can be overridden globally or when uniformed
		// globally:  $.uniform.defaults.fileButtonHtml = "Pick A File";
		// on uniform:  $('input').uniform({fileButtonHtml: "Pick a File"});
		defaults: {
			activeClass: "active",
			autoHide: true,
			buttonClass: "button",
			checkboxClass: "checker",
			checkedClass: "checked",
			disabledClass: "disabled",
			fileButtonClass: "action",
			fileButtonHtml: "Choose File",
			fileClass: "uploader",
			fileDefaultHtml: "No file selected",
			filenameClass: "filename",
			focusClass: "focus",
			hoverClass: "hover",
			idPrefix: "uniform",
			radioClass: "radio",
			resetDefaultHtml: "Reset",
			resetSelector: false,  // We'll use our own function when you don't specify one
			selectAutoWidth: false,
			selectClass: "selector",
			submitDefaultHtml: "Submit",  // Only text allowed
			useID: true
		},

		// All uniformed elements - DOM objects
		elements: []
	};

	// Change text into HTML
	function htmlify(text) {
		if (!text) {
			return "";
		}
		return $('<span />').text(text).html();
	}

	// For backwards compatibility with older jQuery libraries
	// Also adds our namespace in one consistent location and shrinks the
	// resulting minified code
	function bindMany($el, events) {
		var name, namespaced;

		for (name in events) {
			if (events.hasOwnProperty(name)) {
				namespaced = name.replace(/ |$/g, ".uniform");
				$el.bind(name, events[name]);
			}
		}
	}

	// Test if the element is a multiselect
	function isMultiselect($el) {
		var elSize;

		if ($el[0].multiple) {
			return true;
		}

		elSize = $el.attr("size");

		if (elSize === undef || elSize <= 1) {
			return false;
		}

		return true;
	}

	// Update the filename tag based on $el's value
	function setFilename($el, $filenameTag, options) {
		var filename = $el.val();

		if (filename === "") {
			filename = options.fileDefaultHtml;
		} else {
			filename = filename.split(/[\/\\]+/);
			filename = filename[(filename.length - 1)];
		}

		$filenameTag.text(filename);
	}

	function classClearStandard($el, options) {
		$el.removeClass(options.hoverClass + " " + options.focusClass + " " + options.activeClass);
	}

	function classToggle($el, className, enabled) {
		if (enabled) {
			$el.addClass(className);
		} else {
			$el.removeClass(className);
		}
	}

	function classToggleChecked($tag, $el, options) {
		var isChecked = $el.is(":checked");

		if ($el.prop) {
			// jQuery 1.6+
			$el.prop("checked", isChecked);
		} else {
			// jQuery 1.5 and below
			if (isChecked) {
				$el.attr("checked", "checked");
			} else {
				$el.removeAttr("checked");
			}
		}

		classToggle($tag, options.checkedClass, isChecked);
	}

	function classToggleDisabled($tag, $el, options) {
		classToggle($tag, options.disabledClass, $el.is(":disabled"));
	}

	function divSpanWrap($el, $container, method) {
		switch (method) {
		case "after":
			$el.after($container);
			return $el.next();
		case "before":
			$el.before($container);
			return $el.prev();
		case "wrap":
			$el.wrap($container);
			return $el.parent();
		}
		return null;
	}
	function divSpan($el, options, divSpanConfig) {
		var $div, $span;

		if (!divSpanConfig) {
			divSpanConfig = {};
		}

		divSpanConfig = $.extend({
			bind: {},
			css: null,
			divClass: null,
			divWrap: "wrap",
			spanClass: null,
			spanHtml: null,
			spanWrap: "wrap"
		}, divSpanConfig);

		$div = $('<div />');
		$span = $('<span />');

		if (options.autoHide && !($el.is(':visible'))) {
			$div.hide();
		}

		if (divSpanConfig.divClass) {
			$div.addClass(divSpanConfig.divClass);
		}

		if (divSpanConfig.spanClass) {
			$span.addClass(divSpanConfig.spanClass);
		}

		if (options.useID && $el.attr('id')) {
			$div.attr('id', options.idPrefix + '-' + $el.attr('id'));
		}

		if (divSpanConfig.spanHtml) {
			$span.html(divSpanConfig.spanHtml);
		}

		$div = divSpanWrap($el, $div, divSpanConfig.divWrap);
		$span = divSpanWrap($el, $span, divSpanConfig.spanWrap);

		if (divSpanConfig.css) {
			$el.css(divSpanConfig.css);
		}

		classToggleDisabled($div, $el, options);

		return {
			div: $div,
			span: $span
		};
	}

	var allowStyling = true,
		uniformHandlers = [
			{
				// Buttons
				match: function ($el) {
					return $el.is("button, :submit, :reset, a, input[type='button']");
				},
				apply: function ($el, options) {
					var $div, spanHtml, ds;

					spanHtml = options.submitDefaultHtml;

					if ($el.is(":reset")) {
						spanHtml = options.resetDefaultHtml;
					}

					if ($el.is("a, button")) {
						spanHtml = $el.html() || spanHtml;
					} else if ($el.is(":submit, :reset, input[type=button]")) {
						spanHtml = htmlify($el.attr("value")) || spanHtml;
					}

					ds = divSpan($el, options, {
						css: { display: "none" },
						divClass: options.buttonClass,
						spanHtml: spanHtml
					});
					$div = ds.div;

					bindMany($div, {
						"mouseenter": function () {
							$div.addClass(options.hoverClass);
						},
						"mouseleave": function () {
							$div.removeClass(options.hoverClass);
							$div.removeClass(options.activeClass);
						},
						"mousedown touchbegin": function () {
							$div.addClass(options.activeClass);
						},
						"mouseup touchend": function () {
							$div.removeClass(options.activeClass);
						},
						"click touchend": function (e) {
							var ev;

							if ($(e.target).is("span, div")) {
								if ($el[0].dispatchEvent) {
									ev = document.createEvent("MouseEvents");
									ev.initEvent("click", true, true);
									$el[0].dispatchEvent(ev);
								} else {
									$el.click();
								}
							}
						}
					});
					bindMany($el, {
						"focus": function () {
							$div.addClass(options.focusClass);
						},
						"blur": function () {
							$div.removeClass(options.focusClass);
						}
					});
					$.uniform.noSelect($div);
					return {
						remove: function () {
							return $el.unwrap().unwrap();
						},
						update: function () {
							classClearStandard($div, options);
							classToggleDisabled($div, $el, options);
						}
					};
				}
			},
			{
				// Checkboxes
				match: function ($el) {
					return $el.is(":checkbox");
				},
				apply: function ($el, options) {
					var ds, $div, $span;

					ds = divSpan($el, options, {
						css: { opacity: 0 },
						divClass: options.checkboxClass
					});
					$div = ds.div;
					$span = ds.span;

					// Add focus classes, toggling, active, etc.
					bindMany($el, {
						"focus": function () {
							$div.addClass(options.focusClass);
						},
						"blur": function () {
							$div.removeClass(options.focusClass);
						},
						"click touchend": function () {
							classToggleChecked($span, $el, options);
						},
						"mousedown touchbegin": function () {
							$div.addClass(options.activeClass);
						},
						"mouseup touchend": function () {
							$div.removeClass(options.activeClass);
						},
						"mouseenter": function () {
							$div.addClass(options.hoverClass);
						},
						"mouseleave": function () {
							$div.removeClass(options.hoverClass);
							$div.removeClass(options.activeClass);
						}
					});

					classToggleChecked($span, $el, options);
					return {
						remove: function () {
							return $el.unwrap().unwrap();
						},
						update: function () {
							classClearStandard($div, options);
							$span.removeClass(options.checkedClass);
							classToggleChecked($span, $el, options);
							classToggleDisabled($div, options);
						}
					};
				}
			},
			{
				// File selection / uploads
				match: function ($el) {
					return $el.is(":file");
				},
				apply: function ($el, options) {
					var ds, $div, $filename, $button;

					// The "span" is the button
					ds = divSpan($el, options, {
						css: { opacity: 0 },
						divClass: options.fileClass,
						spanClass: options.fileButtonClass,
						spanHtml: options.fileButtonHtml,
						spanWrap: "after"
					});
					$div = ds.div;
					$button = ds.span;
					$filename = $("<span />").html(options.fileDefaultHtml);
					$filename.addClass(options.filenameClass);
					$filename = divSpanWrap($el, $filename, "after");

					// Set the size
					if (!$el.attr("size")) {
						$el.attr("size", $div.width() / 10);
					}

					// Actions
					function filenameUpdate() {
						setFilename($el, $filename, options);
					}

					// Account for input saved across refreshes
					filenameUpdate();

					bindMany($el, {
						"focus": function () {
							$div.addClass(options.focusClass);
						},
						"blur": function () {
							$div.removeClass(options.focusClass);
						},
						"mousedown": function () {
							if (!$el.is(":disabled")) {
								$div.addClass(options.activeClass);
							}
						},
						"mouseup": function () {
							$div.removeClass(options.activeClass);
						},
						"mouseenter": function () {
							$div.addClass(options.hoverClass);
						},
						"mouseleave": function () {
							$div.removeClass(options.hoverClass);
							$div.removeClass(options.activeClass);
						}
					});

					// IE7 doesn't fire onChange until blur or second fire.
					if ($.browser.msie) {
						// IE considers browser chrome blocking I/O, so it
						// suspends tiemouts until after the file has been selected.
						bindMany($el, {
							"click": function () {
								$el.trigger("change");
								setTimeout(filenameUpdate, 0);
							}
						});
					} else {
						// All other browsers behave properly
						bindMany($el, {
							"change": filenameUpdate
						});
					}

					$.uniform.noSelect($filename);
					$.uniform.noSelect($button);
					return {
						remove: function () {
							// Remove sibling spans
							$el.siblings("span").remove();
							// Unwrap parent div
							$el.unwrap();
							return $el;
						},
						update: function () {
							classClearStandard($div, options);
							setFilename($el, $filename, options);
							classToggleDisabled($div, $el, options);
						}
					};
				}
			},
			{
				// Input fields (text)
				match: function ($el) {
					if ($el.is("input")) {
						var t = $el.attr("type").toLowerCase(),
							allowed = " color date datetime datetime-local email month number password search tel text time url week ";
						return allowed.indexOf(" " + t + " ") >= 0;
					}
					return false;
				},
				apply: function ($el) {
					var elType = $el.attr("type");
					$el.addClass(elType);
					return {
						remove: function () {
							$el.removeClass(elType);
						},
						update: function () {
						}
					};
				}
			},
			{
				// Radio buttons
				match: function ($el) {
					return $el.is(":radio");
				},
				apply: function ($el, options) {
					var ds, $div, $span;

					ds = divSpan($el, options, {
						css: { opacity: 0 },
						divClass: options.radioClass
					});
					$div = ds.div;
					$span = ds.span;

					// Add classes for focus, hanlde active, checked
					bindMany($el, {
						"focus": function () {
							$div.addClass(options.focusClass);
						},
						"blur": function () {
							$div.removeClass(options.focusClass);
						},
						"click touchend": function () {
							// Untoggle the rest of the radios
							var radioClass = options.radioClass.split(" ")[0],
								otherRadioSpans = "." + radioClass + " span." + options.checkedClass + ":has([name='" + $el.attr("name") + "'])";
							$(otherRadioSpans).each(function () {
								var $spanTag = $(this),
									$el = $spanTag.find(":radio");
								classToggleChecked($spanTag, $el, options);
							});

							// Toggle me
							classToggleChecked($span, $el, options);
						},
						"mousedown touchend": function () {
							if (!$el.is(":disabled")) {
								$div.addClass(options.activeClass);
							}
						},
						"mouseup touchbegin": function () {
							$div.removeClass(options.activeClass);
						},
						"mouseenter touchend": function () {
							$div.addClass(options.hoverClass);
						},
						"mouseleave": function () {
							$div.removeClass(options.hoverClass);
							$div.removeClass(options.activeClass);
						}
					});

					classToggleChecked($span, $el, options);
					return {
						remove: function () {
							// Unwrap from span and div
							return $el.unwrap().unwrap();
						},
						update: function () {
							classClearStandard($div, options);
							classToggleChecked($span, $el, options);
							classToggleDisabled($div, $el, options);
						}
					};
				}
			},
			{
				// Select lists, but do not style multiselects
				match: function ($el) {
					var elSize;

					if ($el.is("select") && !isMultiselect($el)) {
						return true;
					}

					return false;
				},
				apply: function ($el, options) {
					var ds, $div, $span, origElemWidth, px;

					origElemWidth = $el.width();
					ds = divSpan($el, options, {
						css: {
							opacity: 0,
							// The next two need some review
							left: "2px",
							width: (origElemWidth + 32) + "px"
						},
						divClass: options.selectClass,
						spanHtml: ($el.find(":selected:first") || $el.find("option:first")).html(),
						spanWrap: "before"
					});
					$div = ds.div;
					$span = ds.span;

					if (options.selectAutoWidth) {
						// This needs some critical review
						$div.width($("<div />").width() - $("<span />").width() + origElemWidth + 25);
						px = parseInt($div.css("paddingLeft"), 10);
						$span.width(origElemWidth - px - 15);
						$el.width(origElemWidth + px);
						$el.css("min-width", origElemWidth + px + "px");
						$div.width(origElemWidth + px);
					} else {
						// Set the width of select behavior
						px = $el.width();
						$div.width(px);
						$span.width(px - 25);
					}

					bindMany($el, {
						"change": function () {
							$span.html($el.find(":selected").html());
							$div.removeClass(options.activeClass);
						},
						"focus": function () {
							$div.addClass(options.focusClass);
						},
						"blur": function () {
							$div.removeClass(options.focusClass);
							$div.removeClass(options.activeClass);
						},
						"mousedown touchbegin": function () {
							$div.addClass(options.activeClass);
						},
						"mouseup touchend": function () {
							$div.removeClass(options.activeClass);
						},
						"click touchend": function () {
							// IE7 and IE8 may not update the value right
							// until click - issue #238
							var selHtml = $el.find(":selected").html();

							if ($span.html() !== selHtml) {
								// Change was detected
								// Fire the change event on the select tag
								$el.trigger('change');
							}
						},
						"mouseenter": function () {
							$div.addClass(options.hoverClass);
						},
						"mouseleave": function () {
							$div.removeClass(options.hoverClass);
							$div.removeClass(options.activeClass);
						},
						"keyup": function () {
							$span.html($el.find(":selected").html());
						}
					});

					$.uniform.noSelect($span);

					return {
						remove: function () {
							// Remove sibling span
							$el.siblings("span").remove();
							// Unwrap parent div
							$el.unwrap();
							return $el;
						},
						update: function () {
							classClearStandard($div, options);

							// Reset current selected text
							$span.html($el.find(":selected").html());

							classToggleDisabled($div, $el, options);
						}
					};
				}
			},
			{
				// Select lists - multiselect lists only
				match: function ($el) {
					var elSize;

					if ($el.is("select") && isMultiselect($el)) {
						return true;
					}

					return false;
				},
				apply: function ($el) {
					$el.addClass("uniform-multiselect");
					return {
						remove: function () {
							$el.removeClass("uniform-multiselect");
						},
						update: function () {
						}
					};
				}
			},
			{
				// Textareas
				match: function ($el) {
					return $el.is("textarea");
				},
				apply: function ($el) {
					$el.addClass("uniform");
					return {
						remove: function () {
							$el.removeClass("uniform");
						},
						update: function () {
						}
					};
				}
			}
		];

	// IE6 can't be styled - can't set opacity on select
	if ($.browser.msie && $.browser.version < 7) {
		allowStyling = false;
	}


	$.fn.uniform = function (options) {
		var el = this;
		options = $.extend({}, $.uniform.defaults, options);

		// Code for specifying a reset button
		if (options.resetSelector !== false) {
			$(options.resetSelector).mouseup(function () {
				window.setTimeout(function () {
					$.uniform.update(el);
				}, 10);
			});
		}

		return this.each(function () {
			var $el = $(this),
				i,
				handler,
				callbacks;

			// Avoid uniforming elements already uniformed - just update
			if ($el.data("uniformed")) {
				$.uniform.update($el);
			}

			// Avoid uniforming browsers that don't work right
			if ($el.data("uniformed") || !allowStyling) {
				return;
			}

			for (i = 0; i < uniformHandlers.length; i = i + 1) {
				handler = uniformHandlers[i];

				if (handler.match($el, options)) {
					callbacks = handler.apply($el, options);

					// Mark the element as uniformed and save options
					$el.data("uniformed", callbacks);

					// Store element in our global array
					$.uniform.elements.push($el.get(0));
					return;
				}
			}
		});
	};

	$.uniform.restore = function (elem) {
		if (elem === undef) {
			elem = $.uniform.elements;
		}

		var $elem = $(elem);

		$elem.each(function () {
			var $el = $(this),
				index,
				elementData;

			elementData = $el.data("uniformed");

			// Skip elements that are not uniformed
			if (!elementData) {
				return;
			}

			elementData.remove();

			// Unbind events
			$el.unbind(".uniform");

			// Remove item from list of uniformed elements
			index = $.inArray(this, $.uniform.elements);

			if (index >= 0) {
				$.uniform.elements.splice(index, 1);
			}

			$el.removeData("uniformed");
		});
	};

	//noSelect v1.0
	$.uniform.noSelect = function (elem) {
		var f = function () {
			return false;
		};

		$(elem).each(function () {
			this.onselectstart = this.ondragstart = f; // Webkit & IE
			// .mousedown() for Webkit and Opera
			// .css for Firefox
			$(this).mousedown(f).css({
				MozUserSelect: "none"
			});
		});
	};

	$.uniform.update = function (elem) {
		if (elem === undef) {
			elem = $.uniform.elements;
		}

		var $elem = $(elem);

		$elem.each(function () {
			var $el = $(this),
				elementData;

			elementData = $el.data("uniformed");

			// Skip elements that are not uniformed
			if (!elementData) {
				return;
			}

			elementData.update($el, elementData.options);
		});
	};
}(jQuery));
