/**
 * ops_Form_Element_Multiselect Helper function.
 * App
 */
(function ($) { $.fn.extend({
		
	/**
	 * @param object options Structure {strict: int $strict, freeze: int $freeze, limit: int $limit}.
	 *        Note that $strict and $freeze will only be 1 or 0 (as they denote a boolean value).
	 * @param function onComplete Callback to run when the multiselected has finished drawing.
	 */
	multiselect: function (options) {
		
		var search_count = 0;

		// Should only be applied to 1 element which is the root container for the multiselect (element 
		// with an id attribute equal to the name of the multiselect).
		// The function should be invoked once after the multiselect has rendered (any callback events
		// will be binded as appropriate).
		this.each(function () {
			var original_multiselect  = $(this);                   
			var original_name         = $(this).attr('name');
			var original_id           = $(this).attr('id');                     
			var original_class        = $(this).attr('class');
			
			var disabled_class = '' ;
			
			var uiElements = "<div class='ops_form_multiselect_new " + original_class + "' id='" + original_id +"'>";
			
			var limitCounter = "";
			
			if (options.limit != -1) {
				// Output the multi-select counter.
				limitCounter = "<br /><br />"
					+"<span class='ops_form_multiselect_new_label'>"
						+"Please Select " + ((options.strict === 1) ? "Exactly" : "Up To" ) +": "
						+"<span data-limit='"+(options.limit)+"' class='ops_form_multiselect_limit_label'>"+(options.limit)+"</span>"
					+"</span>";
			}
			
			uiElements += 
				"<div class='ops_form_multiselect_new_selected'>"
					+"<span class='ops_form_multiselect_new_label'>Selected</span>"
					+"<br />"
					+"<select multiple class='" + disabled_class + "'></select>"
					+ limitCounter
				+"</div>";
				
			// If the multiselect is frozen, do not show the item transfer buttons.
			if ( options.frozen == 0 ) {
				uiElements +=
					"<div class='ops_form_multiselect_new_buttons'>" +
						"<div class='ops_form_multiselect_new_buttons_add'></div><br />" +
						"<div class='ops_form_multiselect_new_buttons_remove'></div>" +
					"</div>";
			}
			
			uiElements +=
				"<div class='ops_form_multiselect_new_notselected'>"
					+"<span class='ops_form_multiselect_new_label'>Not Selected</span>"
					+"<br />"
					+"<select multiple class='" + disabled_class + "'></select>"
					+"<br/>"
					+"<div class='ops_form_multiselect_new_notselected_search'>"
						+"<span class='ops_form_multiselect_new_label'>Search</span>"
						+"<br />"
						+"<input type='text' size='33'/>"
					+"</div>" 
				+"</div>"
				+"<div class='ops_form_multiselect_new_notselected_holder'>"
					+"<select multiple></select>"
				+"</div>";
			
			uiElements += "</div>";
				
			$(original_multiselect).after( uiElements );
			
			var new_multiselect    = $(original_multiselect).next('.ops_form_multiselect_new ');
			var new_selected       = $(new_multiselect).find('.ops_form_multiselect_new_selected select');
			var new_notselected    = $(new_multiselect).find('.ops_form_multiselect_new_notselected select');
			var new_buttons        = $(new_multiselect).find('.ops_form_multiselect_new_buttons');
			var new_limit_counter  = $(new_multiselect).find('.ops_form_multiselect_limit_label');

			$(original_multiselect).children('option').each(function () {
				if ($(this).is(':selected')) {
					$(new_selected).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
				} else {
					$(new_notselected).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
				}
			});

			$(original_multiselect).remove();

			$(new_buttons).find('.ops_form_multiselect_new_buttons_add').bind('click', function () {
				update_multiselect(new_selected, new_notselected, 'add', new_limit_counter);
			});
			
			$(new_buttons).find('.ops_form_multiselect_new_buttons_remove').bind('click', function () {
				update_multiselect(new_selected, new_notselected, 'remove', new_limit_counter);
			});

			$(new_multiselect).find('.ops_form_multiselect_new_notselected_search input').keyup(function () {
				search_count++;
				var current_count = search_count;
				setTimeout(function () { search_notselected(current_count, new_multiselect); }, 500);
			});

			// Rebuild multiselect before submitting form
			$('form').submit(function (event) {
				//event.preventDefault();
				rebuildMultiselect( new_multiselect, original_name, original_id );
			});
			
			$( new_limit_counter ).text(options.limit - $(new_selected).children('option').length);
		}); 

		function update_multiselect(new_selected, new_notselected, direction, new_limit_counter) {
			var count = $(new_notselected).children('option:selected').length + $(new_selected).children('option').length;
			
			if (direction == 'add') {
				// The sum of the selection + selected
				if (count <= options.limit || options.limit == -1) { // If the selection length made is less than the limit value
					$(new_selected).find('optgroup').each(function () {
						$(this).remove();
					});
					
					$(new_notselected).children('option:selected').each(function () {
						$(new_selected).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
						$(this).remove();
					});
					
					if ($(new_notselected).find('option').length == 0) {
						$(new_notselected).append("<optgroup label='No results'></optgroup>");
					} 

					if (options.l != -1){
						$( new_limit_counter ).text(options.limit - $(new_selected).children('option').length);
					}
				}
				
			} else if (direction == 'remove') {
				$(new_notselected).find('optgroup').each(function () {
					$(this).remove();
				});

				$(new_selected).find('option:selected').each(function () {
					$(new_notselected).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
					$(this).remove();
				});

				if ($(new_selected).find('option').length == 0) {
					$(new_selected).append("<optgroup label='None selected'></optgroup>");
				} 

				if(options.l !== -1){          
					$( new_limit_counter ).text(options.limit - $(new_selected).children('option').length);
				}
			}
		}

		function search_notselected(current_count, new_multiselect) {
			if (current_count == search_count) {
				search_count = 0;
				var input = $(new_multiselect).find('.ops_form_multiselect_new_notselected_search input');
				var search_term = $(input).val();
				var new_notselected = $(new_multiselect).find('.ops_form_multiselect_new_notselected select');
				var new_notselected_holder = $(new_multiselect).find('.ops_form_multiselect_new_notselected_holder select');
				$(new_notselected).find('optgroup').each(function () {
					$(this).remove();
				});
				$(new_notselected_holder).find('option').each(function () {
					$(new_notselected).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
					$(this).remove();
				});
				$(input).closest('.ops_form_multiselect_new_notselected').find('option').each(function () {
					if ($(this).text().toUpperCase().indexOf(search_term.toUpperCase()) == -1) {
						$(new_notselected_holder).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
						$(this).remove();
					}
				});
				if ($(input).closest('.ops_form_multiselect_new_notselected').find('option').length == 0) {
					$(input).closest('.ops_form_multiselect_new_notselected').find('select').append("<optgroup label='No results'></optgroup>");
				}
			}
		}

		function rebuildMultiselect( new_multiselect, original_name, original_id ) {
			var form = $(new_multiselect).closest('form');
			$(form).append("<select multiple class='rebuilt_select' name='" + original_name + "' id='" + original_id + "_rebuilt'></select");
			var select = $('.rebuilt_select');
			$(select).removeClass('rebuilt_select').css({ 'display' : 'none' });
			$(new_multiselect).find('.ops_form_multiselect_new_selected select').each(function () {
				$(select).append($(this).html());
				$(select).find('option').each(function () {
					$(this).attr('selected', 'selected');
				});
				//$(select).append("<option selected value='" + $(this).val() + "'>" + $(this).text() + "</option>");
			});
			$(new_multiselect).find('.ops_form_multiselect_new_notselected select').each(function () {
				$(select).append($(this).html());
				//$(select).append("<option value='" + $(this).val() + "'>" + $(this).text() + "</option>");
			});
			$(new_multiselect).remove();
		}
	}
}); })(jQuery);


/**
 * Multiselect Helper constructor
 * @param jQuery A singleton JQ object addressing the ID of the element holding the multiselect.
 */
var Multiselect = function(element) {
	this.element = element;
	this.selected = this.element.find('.ops_form_multiselect_new_selected select');
	this.unselected = this.element.find('.ops_form_multiselect_new_notselected select');
	
	this.limitCounter = this.element.find('.ops_form_multiselect_limit_label');	
	// If the jQuery selector matched more than zero elements
	if (this.limitCounter.length > 0) {
		this.limit = this.limitCounter.data('limit');
	} else {
		this.limitCounter = null;
	}
};

/**
 * Fetch all items held by this multiselect.
 * @return array Structure: [{"value": string, "text": string, "selected": bool}].
 */
Multiselect.prototype.getItems = function() {
	var items = [];
	
	// Get unselected items.
	this.unselected.children('option').each(function(){
		items.push({
			value:     $(this).attr('value'),
			text:      $(this).html(),
			selected:  false
		});
	});
	
	// Get selected items.
	this.selected.children('option').each(function(){
		items.push({
			value:     $(this).attr('value'),
			text:      $(this).html(),
			selected:  true
		});
	});
	
	return items;
};

/**
 * Delete all items or optionally a set of items existing in the multiselect.]
 * @param  array [optional] Exclude to delete all items, otherwise provide an array storing a set of
 *         item values which need to be deleted. Structure: [ string ].
 * @return bool True if something was deleted, false otherwise.
 */
Multiselect.prototype.removeItems = function(yourItems) {
	if (typeof items === 'undefined') {
		var count = this.selected.length + this.unselected.length;
		this.selected.empty();
		this.unselected.empty();
		
		return count > 0; // If the count was greater than 0 before deletion, something was deleted.
	}
	
	var items = this.checkArray(yourItems);
	
	var somethingWasRemoved = false;
	
	this.element.children('option').each(function(){
		// jQuery.inArray returns the index if in the array, -1 if not in the array.
		if ($.inArray($(this).val(), items) !== -1) {
			somethingWasRemoved = true;
			$(this).remove(); // Delete the option item.
		}
	});
	
	if (somethingWasRemoved) {
		this.updateLimitCounter();
	}
	return somethingWasRemoved;
};

/**
 * Add items to the multiselect.
 * If there is a limit to the number of selected items and that limit has been met, then any items
 * with the 'selected' flag will by default be placed into the 'unselected' bin.
 * @param array Items to add. Must have the following structure: 
 *        [{"value": string, "text": string, "selected": bool}].
 */
Multiselect.prototype.addItems = function(yourItems){
	var items = this.checkArray(yourItems);
	
	var localLimitCount = this.limitCounter ? this.limitCounter.text() : null;
	
	items.forEach(function(item){
		
		itemElement = '<option value="'+ item['value'] +'">'+ item['text'] +'</option>';
		
		// Only set the item as 'selected' if it has the appropriate flag and if the limit is not 
		// already full (at the max number of items).
		if (item.selected && (!this.limitCounter || localLimitCount > 0)) {		
			this.selected.append(itemElement);
			// Make sure to update the local limit counter.
			if (this.localLimitCounter) localLimitCount--; 
		} else {
			this.unselected.append(itemElement);
		}
	}, this);
	
	this.updateLimitCounter();
};

/**
 * Updates the Multiselect 'limit' counter used when you can only selected a certain number of 
 * items.
 * @returns 
 */
Multiselect.prototype.updateLimitCounter = function(){
	if (this.limitCounter) {
		this.limitCounter.text(this.limit - this.selected.children('option').length);
	}
};

/**
 * Helper method to verify that a variable is storing an array, and if not, to add that data type
 * to a single-item array.
 * @param mixed maybeArray
 * @return array The existing argument if it was already an array, or a single-element array if the 
 *         the argument was originally a non-array type.
 */
Multiselect.prototype.checkArray = function(maybeArray){
	if (maybeArray.constructor !== Array) {
		return [ maybeArray ];
	} else {
		return maybeArray;
	}
};