/**
 * jQuery.betterSelects plugin v0.5
 * Date: 2010.10.09
 * Web: http://rogerpadilla.wordpress.com/betterSelects
 * Copyright 2010, Roger Padilla Camacho - rogerjose81@gmail.com
 * Licensed under the Apache License version 2
 */

/**
 * This plug-in is intended to "transform" the old-fashioned HTML select-elements in much more sophisticated
 * and flexible controls.
 * 
 * Background: a normal HTML select element is too much rigid regarding the customization of its look and feel,
 * as it does not support the coolest CSS effects (aka rounded corners, transparency, shadows, columns,
 * animations, etc.). Thus, to count with such features in that control is the purpose of this plug-in.
 * 
 * Currently supported features: displaying items in columns, transparency, rounded corners, animations,
 * shadows, customization of the expansion button, and in general, a full customization through CSS.
 * The plug-in degrades successfully when JavaScript is not enabled.
 
 * Planned features for future versions: navigation by using arrows keys, support for select-multiple,
 * auto completion (useful?), among other minor improvements.
 * 
 * The usage is very straightforward; just include the provided CSS file and the JavaScript file, then:
 * <code>$('select').betterSelects();</code>
 */
(function($) {

	/**
	 * Main function; used to initialize the plugin
	 */
	$.fn.betterSelects = function() {

		if(this.length >= 0) {
			// keep a reference of the jQuery object to access it on other functions
			$.fn.betterSelects.self = this;
			// Creates a 'betterSelect' control for each 'select' element in the matched set using the given configuration
			$.fn.betterSelects.opts = $.extend({}, $.fn.betterSelects.defaults, arguments[0]);
			$.fn.betterSelects.createBetterSelects();
		}

		return this;
	};
	
	
	/**
	 * Default configuration
	 */
	$.fn.betterSelects.defaults = {
		zIndexMax: 99, // The first betterSelect control will use this value, it will be smaller for following controls
		expandTime: 100, // The time it takes to expand the options
		marginTop: 1, // Margin between the preview and the expanded options list
		itemsPerCol: 6 // Number of items per column. Use 0 for a single column
	};


	/**
	 * Creates a 'betterSelect' control for each 'select' HTML-element in the matched set using the given configuration
	 */
	$.fn.betterSelects.createBetterSelects = function(){

		var selectsData = [];
		var bsHTML, bs, bsExpandedHeight, pos, ul, itMinHeight, ele;

		/** Loop through the matched set of 'select' HTML-elements to build a 'betterSelect' control emulating each 'select' in the set **/
		$.fn.betterSelects.self.each(function(){
			
			// TODO remove this when support for select-multiple get added
			if(!this.multiple){

				ele = $(this);
				
				classname = this.className;

				// Hide the original 'select' HTML-element
				this.className += 'accesible-hidden-element';
	
				// the select's index being processed
				pos = selectsData.length;
	
				// Build the HTML for the 'betterSelect' control'
				bsHTML = createBetterSelectHTML(this, pos, classname);
	
				// Inserts the 'betterSelect' control in the DOM
				bs = this.parentNode.insertBefore(bsHTML, this.nextSibling);
				bs = $(bs);
	
				bs.children('.betterSelect-preview').css({minWidth: ele.width()});
				
				// Obtains the height of the betterSelect's options on expanded state
				ul = bs.children('.betterSelect');
				bsExpandedHeight = ul.height();
				// Collapse the betterSelect by default
				ul[0].style.height = 0;
				ul[0].style.zIndex = $.fn.betterSelects.opts.zIndexMax - pos;
				itMinHeight = bs.height();
				// Store data related to the original 'select' DOM-element to reference it later
				selectsData.push({height: bsExpandedHeight, itMinHeight: itMinHeight});
				ul.addClass('betterSelect-processed');
			}
		});


		/**
		 * Obtains the data source from the given select HTML-element
		 * @param srcElem Source select-element
		 * @return Extracted select's data
		 */
		function getSelectData(srcElem){

			var bsData = [];
			var selectedIndex = 0;

			for(var i=0, l=srcElem.options.length; i<l; i++) {
				if(srcElem.options[i].selected) {
					selectedIndex = i;
				}
				bsData.push(srcElem.options[i].innerHTML);
			}

			return {options: bsData, selectedIndex: selectedIndex};
		}


		/**
		 * Build the HTML displaying the options
		 * @param src Data source
		 * @param pos The index of the data source being processed
		 * @return Options's data
		 */
		function createBetterSelectOptionsHTML(src, pos) {

			var optionsHTML = '<div class="betterSelect-nc">';
			var classes = '';

			// if the data source is a select HTML-element
			if(src.nodeType) {
				src = getSelectData(src);
			}

			for(var i=0, l=src.options.length; i<l; i++){
				classes = 'betterSelect-it betterSelect-it-i-' + i;
				// marks the selected option
				if(i == src.selectedIndex) {
					classes += ' betterSelect-it-state-selected';
				}
				// close the previous colum and open a new one
				if(i > 0 && (i % $.fn.betterSelects.opts.itemsPerCol) === 0){
					optionsHTML += '</div>\
									<div class="betterSelect-nc">';
				}
				// adds an option to the list of options
				optionsHTML +=
					'<li id="betterSelect_it:' + pos + '.' + i + '" class="' + classes + '">\
						<span class="betterSelect-it-label">' + src.options[i] + '</span>\
					</li>';
			}

			optionsHTML += '</div>';

			return {options: optionsHTML, selectedIndex: src.selectedIndex, selected: src.options[src.selectedIndex]};
		}


		/**
		 * Build the betterSelect's HTML using the given datasource
		 * @param src Datasource
		 * @param pos The index of the datasource being processed
		 * @return betterSelect's HTML
		 */
		function createBetterSelectHTML(src, pos, classname){

			var bsData = createBetterSelectOptionsHTML(src, pos);
			bsHTML = document.createElement('div');
			bsHTML.id = 'betterSelect_box:' + pos;
			bsHTML.className = 'betterSelect-box' + ' ' + classname;
			bsHTML.innerHTML = '\
				<span id="betterSelect_preview:' + pos + '" class="betterSelect-preview">' + bsData.selected + '</span>\
				<span id="betterSelect_maximize:' + pos +'" class="betterSelect-maximize"></span>\
				<ul id="betterSelect:' + pos +'" class="betterSelect">' + bsData.options + '</ul>\
			';

			return bsHTML;
		}


		/**
		 * Expands/collapses the 'betterSelect' component in the given index
		 * @param pos Index of the 'betterSelect' component
		 */
		function changeSelectMaximizeState(pos){
			var ul = $('#betterSelect\\:' + pos);
			ul.stop();
			if(ul.is('.betterSelect-state-expanded')){
				minimizeSelect(pos, ul);
			} else {
				maximizeSelect(pos, ul);
			}
		}


		/**
		 * Expands the 'betterSelect' component in the given index
		 * @param pos Index of the 'betterSelect' component
		 * @param ul Options of the 'betterSelect' component
		 */
		function maximizeSelect(pos, ul){
			$('#betterSelect_maximize\\:' + pos).addClass('betterSelect-maximize-state-expanded');
			var bs = $(ul[0].parentNode);
			var bsOffset = bs.offset();
			bsOffset.top += selectsData[pos].itMinHeight + $.fn.betterSelects.opts.marginTop;
			ul.css({top: bsOffset.top, left: bsOffset.left});
			ul.addClass('betterSelect-state-expanded');				
			ul.animate({height: selectsData[pos].height}, $.fn.betterSelects.opts.expandTime, function(){
				// TODO move the following line to single-selects handlers
				ul.find('.betterSelect-it-state-selected').addClass('betterSelect-it-hover');
				ul.focus();
			});
		}


		/**
		 * Collapses the 'betterSelect' control specified by the given index
		 * @param pos Index of the 'betterSelect' control
		 * @param ul Options container of the 'betterSelect' control
		 */
		function minimizeSelect(pos, ul){
			ul[0].style.height = 0;
			$('#betterSelect_maximize\\:' + pos).removeClass('betterSelect-maximize-state-expanded');
			ul.removeClass('betterSelect-state-expanded');
			ul.find('.betterSelect-it').removeClass('betterSelect-it-hover');
			ul.css('display','none');
		}


		/**
		 * Processes a click on an item of a 'betteSelect' control
		 * @param elem The clicked item
		 */
		function changeSelectSingle(elem) {
			var ul = $(elem[0].parentNode.parentNode);
			var id = elem[0].id;
			pos = id.substr(id.indexOf(':') + 1).split('.');
			var it = $.fn.betterSelects.self[pos[0]].options[pos[1]];
			// updates the preview with the content of label of the selected item
			ul.siblings('.betterSelect-preview')[0].innerHTML = it.innerHTML;
			ul.find('.betterSelect-it').removeClass('betterSelect-it-state-selected');
			elem.addClass('betterSelect-it-state-selected');
			// updates the original select HTML-element with the current selection
			it.selected = 'selected';
		}


		/**
		 * Adds handlers for common behaviors for both select types: single and multiple
		 */
		function initCommon(selectsBoxes, items){

			// Handler to expand/collapse the 'betterSelect' controls
			selectsBoxes.click(function(evt){
				pos = this.id.substr(this.id.indexOf(':') + 1);
				changeSelectMaximizeState(pos);
			});

			items.filter(':last-child').addClass('betterSelect-it-last');
		}

		
		/**
		 * Adds handlers for single-selects' peculiar behaviors
		 */
		function initSingles(selectBoxes, items){
			
			items.click(function(evt){
				changeSelectSingle($(this));
			});
			
			items.hover(function(){
				$(this.parentNode.parentNode).find('.betterSelect-it').removeClass('betterSelect-it-hover');
				$(this).addClass('betterSelect-it-hover');
			}, function(){
				$(this.parentNode.parentNode).find('.betterSelect-it').removeClass('betterSelect-it-hover');
			});
		}
		

		// Read all the created 'betterSelect' controls	which have not been yet processed
		selectsBoxes = $('.betterSelect-box:not(.betterSelect-box-processed)');
		items = selectsBoxes.find('.betterSelect-it');

		// Adds handlers for common behaviors of both select types: single and multiple
		initCommon(selectsBoxes, items);
		
		// Adds handlers for single-selects' peculiar behaviors
		initSingles(selectsBoxes, items);

		// Marks controls as processed
		selectsBoxes.addClass('betterSelect-box-processed');
	};

})(jQuery);

