/*
	Mootools CustomSelect
	
	CustomSelect 0.1 - replacement for standard html select element
	Author: Bonch <demerest@gmail.com>
	Date: 23 Sep 2008
	http://imaker.ru/custom-select/
	
	Enjoy
	
	TODO: If there is scroll, then it must automatically be twisted with the selection of elements
	It is necessary to know for this, what elements are located out of the visibility range.
*/

var CustomSelect = new Class({
	Implements     : [Events, Options],
	options        : {
		onChange : $empty,
		onSelect : $empty,
		onShow   : $empty,
		onHide   : $empty,
		theme    : 'aqua',
		max_lines : 15, // Maximum quantity of elements without the warming up
		line_height : 17 // Height of the element
	},
	box            : null, // Boxing (a c of div inside)
	select         : null, // Native selectBox
	selectbox      : null, // Object of selectBox (UL tree)
	selectedIndex  : null, // The running index of selectBox
	currentElement : null, // Current chosen element of selectBox
	currentText    : null, // Current text
	width          : null, // Width of native selectBox
	display        : false, // Status of selectBox (it is shown or not)
	has_scrolling  : false, // There is whether warming up selectBox has
	
	initialize: function(select, options) {
		this.setOptions(options);
		this.select = select;
		
		this.width = this.select.getWidth();
		
		// We remove native selectBox
		this.select.setStyle('display', 'none');
		
		// We create its own selectBox on the basis of select
		this.build_selectbox();
		
		// Pressure on selectBox
		this.box.addEvent('mousedown', function(e) {
			if (this.display) {
				this.hide();
			} else {
				this.show();	
				try{
					this.box.focus();
				}catch(e){}
			}
			
			e.stop();
		}.bind(this) );
		

		// Reverse reaction to onchange of native selectBox
		this.select.addEvent('change', function(e) {
			e.stop();
			this.selectbox.getElements('li').each(function(li) {
				if (li.getProperty('index') == this.select.selectedIndex)
					this.change_item.run(li, this);
			}.bind(this) )
		}.bind(this) );
		
		// With the clique to document we shut selectBox
		window.document.addEvent('click', function(e) {
			if(e!=undefined){
				var obj = e.target;
				while (obj.parentNode && obj != this.box) obj = obj.parentNode;
				if (obj != this.box) this.hide();
			}
		}.bind( this ));
	},
	
	/* To remove the falling out list */
	hide: function() {
		if (this.display) {
			this.selectbox.setStyle('display', 'none');
			this.display = false;
			this.box.removeClass('focused');
			
			this.selectbox.getElements('li').each(function(li) {
				if (li.getProperty('index') == this.select.selectedIndex) {
					this.unselect_lis();
					this.currentElement = li.addClass('selected');;
				}
			}.bind(this) );
			
			this.fireEvent('onHide', this.currentElement);
		}
	},
	
	/* To show the falling out list */
	show: function() {
		if (!this.display) {
			this.fireEvent('onShow', this.currentElement);
			this.selectbox.setStyle('display', 'block');
			this.display = true;
			this.box.addClass('focused');
			
			// There is whether warming up the element has ?
			this.has_scrolling = this.selectbox.getScrollSize().y > this.selectbox.getHeight();
		}
	},
	
	/* To create its own selectBox from the standard */
	build_selectbox: function() {
		// To create element A and DIV inside A
		this.box = new Element('a', {
			'class'  : this.options.theme,
			'href'   :  'javascript:;',
			'styles' : {
				'width' : this.width+'px'
			}
		}).inject(this.select, 'after');
		
		this.box_div = new Element('div').inject(this.box);
		
		// To create list UL
		this.selectbox     = new Element('ul', {
			'class' : this.options.theme
		}).inject( this.box, 'after' );
		this.selectedIndex = this.select.selectedIndex;
		
		// We establish the width of the falling out menu
		if (this.selectbox.getWidth() < this.width)
			this.selectbox.setStyle('width', this.width + 'px');
		
		// We create by li the elements of list, we separate the current element
		// and we hang up the events
		this.build_options();
		
		// Reaction of element A to the buttons
		this.box.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ?
			'keydown' : 'keypress', this.change_item_on_keyup.bind( this ));
		
		// Reaction to the focus
		this.box.addEvent('focus', function() {
			this.box.addClass('focused');
		}.bind(this) );
		
		this.box.addEvent('blur', function() {
			this.box.removeClass('focused');
		}.bind(this) );
	},
	
	/* To construct the list LI of the elements */
	build_options: function() {
		this.select.getElements('option').each(function(option) {
			var li = new Element('li')
			  .set('text', option.get('text') )
			  .inject( this.selectbox )
			  .setProperty('index', option.index); // We establish the index
			
			// We establish the noted element
			if (option.selected) {
				this.currentElement = li.addClass( 'selected' );
				this.currentText = option.get('text');
			}
			
			// We establish event on the selection of element in created [dropbokse]
			li.addEvent('mouseup', function(e) {
				e.stop();
				this.change_item.run(li, this);
				this.hide();
			}.bind(this) );
		}.bind(this));
		
		// We limit the height of selectBox, if in it is more
		if (this.select.options.length > this.options.max_lines) {
			// We calculate the height of selectBox
			// TODO: this.options.line_height // to calculate dynamically
			var height = this.options.line_height * this.options.max_lines;
			
			this.selectbox.setStyles({
				'height'     : height+'px',
				'overflow-y' : 'auto'
			});
		}
		
		/* We establish isolation during the guidance of the mouse */
		this.toggle_selection();
		
		/* We establish the current text */
		this.box_div.set('text', this.currentText );
	},
	
	/* To reconstruct selectBox */
	rebuild: function() {
		// [Ochishchaeim] of selectBox
		this.selectbox.getElements('li').each(function(li) {
			li.dispose();
		});
		
		this.build_options();
	},
	
	/* We establish isolation during the guidance of the mouse */
	toggle_selection: function() {
		this.selectbox.getElements('li').each(function(li) {
			li.addEvent('mouseenter', function() {
				this.unselect_lis();
				this.currentElement = li.addClass('selected');
				this.selectedIndex = this.currentElement.getProperty('index');
				this.fireEvent('onSelect', this.currentElement);
			}.bind( this ) );
		}.bind( this ));
	},
	
	/* To remove isolation from all elements li */
	unselect_lis: function() {
		this.selectbox.getElements('li').each(function(li) {
			li.removeClass('selected');
		});
	},
	
	/* Change item from selectbox */
	change_item: function(li) {
		// We clean everything
		this.unselect_lis(this.selectbox);
		
		this.selectedIndex  = li.getProperty('index');
		this.currentElement = li;
		
		// We establish class to the selected element
		this.currentElement.addClass('selected');

		// We establish the selected text
		this.box.getElement('div').set('text', this.currentElement.get('text') );
		
		// We change index in native [selektbokse]
		this.select.selectedIndex = this.selectedIndex;
		
		// If into selectbox appeared the warming up ...
		//for (i in this.selectbox) console.log(i)
		//alert( this.selectbox.getScrollSize().y + ' - ' + this.selectbox.getHeight())
		if (this.has_scrolling) {
			//console.log('has scrolling')
		}
		//console.log(this.selectbox.getScroll().y);

		
		this.fireEvent('onChange', [this.currentElement,
			this.select.options[ this.selectedIndex ].get('value')]);
	},
	
	/* [Khendling] of pressures on the buttons up down left right esc */
	change_item_on_keyup: function(e) {
		if (e.key == 'tab' || e.key == 'esc' || e.key == 'enter') {
			this.hide();
			return true;
		}
		if ((e.key == 'up' || e.key == 'down') && e.alt) {
			this.display ? this.hide() : this.show();
			return true;
		}
		
		var li = null;
		
		/* Navigation and the search */
		if (e.key == 'up' || e.key == 'left') {
			li = this.change_element_by_method('getPrevious');
		} else if (e.key == 'down' || e.key == 'right') {
			li = this.change_element_by_method('getNext');
		} else if (e.code == 36 || e.code == 33) {
			li = this.change_element_by_method('getFirst', true);
		} else if (e.code == 35 || e.code == 34) {
			li = this.change_element_by_method('getLast', true);
		} else {
			// Or we search for element on the introduced letter
			li = this.change_element_by_char(e.key);
		}
		
		if (li != null) {
			this.fireEvent('onSelect', li);
			this.change_item.run(li, this);
		}
	},
	
	/* To obtain the element of list according to the method indicated */
	change_element_by_method: function(method, from_child) {
		return from_child ? this.currentElement.getParent()[method]() : 
			this.currentElement[method]();
	},
	
	/* To obtain the element of list on the letter indicated */
	change_element_by_char: function(key) {
		// We search for the necessary element
		var length  = this.selectbox.getElements('li').length;
		var li   = null;
		var patt = new RegExp('^'+key, 'i');
		
		var i = 0;
		var el = this.currentElement;
		while ((el = el.getNext() || el.getParent().getFirst()) && li == null) {
			i++;
			if (i > length) { break;} // Ugly, but works
			if (patt.test( el.get('text') )) {
				li = el;
			}
		}
		
		return li;
	}
});