/*global window, document, Option, jQuery, $ */
/*jslint strict: true, bitwise: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true, plusplus: true, regexp: true, undef: true, white: true */

"use strict";


// $Id: $
/**
 * GexIT Droplist Filter
 * It is rewrite of Joshua Chan's Droplist Filter
 * (http://nihilex.com/droplist-filter)
 *
 * Code is now JSLinted, searching string is
 * put as OPTGROUP instead of OPTION
 * HTML markup is cleaner
 *
 * Tested against jQuery 1.2.6
 *
 * @autor Rafał Treffler, Gex IT
 * @link http://www.gexit.pl
 *
 */
/**
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function ($) {
	/**
	 * This method adds a search filter widget to one or more SELECT lists
	 * selected by jQuery.
	 * @param minSize integer
	 *    (Optional) A list must have at least this many items or else no
	 *    widget will be added to it.
	 * @param maxInstances integer
	 *    (Optiona) No more widgets will be added if this number of widgets
	 *    already exist on the page.
	 */
	$.fn.droplistFilter = function (minSize, maxInstances) {
		return this.each(function () {
			if (this.tagName === 'SELECT') {
				// Check for minimum list size
				if (minSize && this.options.length < minSize) {
					return;
				}

				// Check for maximum widget instances
				if (!$.fn.droplistFilter.prototype.instances) {
					$.fn.droplistFilter.prototype.instances = 0;
				}
				if (maxInstances && ($.fn.droplistFilter.prototype.instances >= maxInstances)) {
					return;
				}
				++$.fn.droplistFilter.prototype.instances;

				// Create semi-private namespace within the node
				this.priv_df = {};

				// variables declaration
				var i, thisDF;

				// Save original list items
				this.priv_df.Options = [];
				for (i = 0; i < this.options.length; ++i) {
					this.priv_df.Options[i] = {
						text: this.options[i].text,
						value: this.options[i].value,
						title: this.options[i].title ? this.options[i].title : undefined,
						className: this.options[i].className ? this.options[i].className : undefined,
						style: this.options[i].style.length ? this.options[i].style : undefined
					};
				}

				// Save original width and selected item for later restoring
				this.priv_df.Selected = this.selectedIndex;
				this.priv_df.Width = this.offsetWidth;

				//// Create the HTML elements
				// Create the components
				this.priv_df.Text = $('<input>').addClass('dfText');
				this.priv_df.OK = $('<div>').addClass('dfOK');
				this.priv_df.Reset = $('<div>').addClass('dfReset');
				this.priv_df.Activate = $('<div>').addClass('dfActivate');
				this.priv_df.Container = $('<div>').addClass('dfContainer').css('display', 'none');

				// Create the hidden search box
				$(this).css('float', 'left').after(
					(this.priv_df.Container)
					.append(this.priv_df.Text)
					.append(this.priv_df.Reset)
					.append(this.priv_df.OK)
				);

				// Insert the activation button
				$(this).after(this.priv_df.Activate);

				// Create alias of "this" for use in event functions
				thisDF = this;

				/**
				 * Fetch a copy of a given list option from the original list.
				 * @param i integer
				 *    The index of the option to fetch.
				 * @return Option
				 */
				this.priv_df.GetOption = function (i) {
					var j, k, opt = new Option();
					for (j in thisDF.priv_df.Options[i]) {
						// If defined
						if (thisDF.priv_df.Options[i][j]) {
							if (j === 'style') {
								// Deep copy CSS attributes
								for (k in thisDF.priv_df.Options[i].style) {
									if (thisDF.priv_df.Options[i].style.hasOwnProperty(k)) {
										opt.style[k] = thisDF.priv_df.Options[i].style[k];
									}
								}
							}
							else {
								// Copy other normal attributes
								opt[j] = thisDF.priv_df.Options[i][j];
							}
						}
					}
					return opt;
				};

				/**
				 * Repopulate the list options based on the filter string.
				 * @param mixed
				 *    Either '' to reset the list, or the search string.
				 */
				this.priv_df.DoFilter = function (filterString) {
					var i, group = thisDF.getElementsByTagName('OPTGROUP')[0];

					// Convert search string to lowercase if it is in fact a string
					if (filterString.toLowerCase) {
						filterString = filterString.toLowerCase();
					}

					// Clear the list
					while (thisDF.length) {
						thisDF.remove(thisDF.length - 1);
					}

					if (filterString === '') {
						// Remove list header
						if (group) {
							thisDF.removeChild(group);
							group = undefined;
						}
					}
					else {
						// Add list handler
						if (!group) {
							group = document.createElement('OPTGROUP');
							group.className = 'droplistFilter_optgroup';
							thisDF.appendChild(group);
						}
						// Searching string as a label of optgroup
						group.label = filterString;
					}

					// Add matching items
					for (i = 0; i < thisDF.priv_df.Options.length; ++i) {
						if (!group || thisDF.priv_df.Options[i].text.toLowerCase().match(filterString)) {
//							if (group) {
//								group.appendChild(thisDF.priv_df.GetOption(i));
//							}
//							else {
							try {
								// Standard compiliant
								thisDF.add(thisDF.priv_df.GetOption(i), null);
							}
							catch (exception) {
								// IE
								thisDF.add(thisDF.priv_df.GetOption(i));
							}
//							}
						}
					}

					// Maintain the original width
					$(thisDF).width(thisDF.priv_df.Width);

					if (!group) {
						// Restore the original selection on reset
						thisDF.selectedIndex = thisDF.priv_df.Selected;
					}
				};

				//// Event handling
				// Helper toggle function
				this.priv_df.Toggle = function () {
					thisDF.priv_df.Container.toggle();
					thisDF.priv_df.Activate.toggle();
					$(thisDF).toggle();
				};
				// Activation button
				this.priv_df.Activate.click(function () {
					thisDF.priv_df.Toggle();
					thisDF.priv_df.Text.select();
					thisDF.priv_df.Text.focus();
				});
				// Reset button
				this.priv_df.Reset.click(function () {
					thisDF.priv_df.DoFilter('');
					thisDF.priv_df.Toggle();
				});
				// Search button
				this.priv_df.OK.click(function () {
					thisDF.priv_df.DoFilter(thisDF.priv_df.Text.val());
					thisDF.priv_df.Toggle();
				});
				// Keypress
				this.priv_df.Text.keypress(function (e) {
					var keynum;
					// Cross-browser initialization
					if (!e) {
						e = window.event;
					}
					if (e.keyCode) {
						keynum = e.keyCode;
					}
					else if (e.which) {
						keynum = e.which;
					}
					// 'Enter' key
					if (keynum === 13 || keynum === 10 || keynum === 3) {
						thisDF.priv_df.OK.click();
						// Prevent the key from doing anything else
						e.cancelBubble = true;
						if (e.stopPropagation) {
							e.stopPropagation();
						}
						return false;
					}
					// 'Escape' key
					else if (keynum === 27) {
						thisDF.priv_df.Reset.click();
						// Prevent the key from doing anything else
						e.cancelBubble = true;
						if (e.stopPropagation) {
							e.stopPropagation();
						}
						return false;
					}
					return true;
				});
			}
		});
	};
}(jQuery));
