/*
---
description: MooChoose is a replacement of the standard form Select UI Element. Style it however you want.

license:
  MIT-style

authors:
- Tomas Rainbolt

requires:
- core/1.2.4/Native: Event
- core/1.2.4/Class: Class
- core/1.2.4/Class: Class.Extras
- core/1.2.4/Element: Element
- core/1.2.4/Element: Element.Event
- core/1.2.4/Element: Element.Style
- core/1.2.4/Element: Element.Dimensions
- core/1.2.4/Utilities: Selectors
- core/1.2.4/Utilities: DomReady
- more/1.2.4/Element: Element.Measure

provides: [Element, Elements]

...
*/

var mooChoose = new Class({
	
	Implements: [Options, Events],
	options: {
		selectClass: 'moochoose',	// what, you don't like moochoose?
		selectId: null,			// For initializing dynamic selects e.g. new mooChoose({'selectId': ELEMENT_ID});
		baseClass: 'mc_display',		// Set your baseclass, you can have multiple
		textPaddingLeft: 5,			// display text padding
		openerWidth: 20,			// true width is 30, but we add the extra 10 as overlap
		openerFadeOverlap: 10,		// I like the text to fade out as it reaches the opener
		optionHeight: 20,			// height of each option, from your stylesheet (height+padding+margin+border)
		maxShowOptions: 10,			// how many options to show max before scroll?
		optionsZIndex: 10,			// You can also set this on each select prior to replacing
		optionsOffsetX: 0,			// x offset from select
		optionsOffsetY: 3,			// y offset from select
		optionsShadow: true,		// for newer webkit/css3 browsers
		shadowBlur: 4,
		shadowOffset: 1,
		shadowColor: '#000',
		roundedOptionsCorners: true,	// for newer webkit/css3 browsers
		optionsCornerRadius: 5
		//onFocused: $empty,		// Event to fire when select is focused
		//onBlurred: $empty			// Event to fire when select is blurred
		//onSelected: $empty		// Event to fire when select is blurred
	},
	
	selects: {},
	mc_displays: [],
	searchByChar: false,
	charSearched: false,			// Store the character currently queried
	moochoosed: null,				// Which element currently has focus
	
	initialize: function(options){
		this.setOptions(options);
		this.selects = $$('select.'+this.options.selectClass);
		this.selects.each( function(selector, i){ 
			this.buildDisplay(selector, i);
		}.bind(this) );
	},
	
	buildDisplay: function(selector, i){
		
		// GET SIZE OF ORIGINAL
		var selector_coords = selector.measure(function(){
		    return this.getCoordinates(selector.getParent());
		});
		
		// DISPLAY
		var mc_display = new Element('a', {
			'id': 'mc_display_'+i,
			'class': this.options.baseClass,
			'tabindex': selector.getProperty('tabindex'),
			'rel': 'nofollow',
			'styles': {
				'width': (selector_coords.width - this.options.openerWidth),
				'margin': selector.getStyle('margin'),
				'margin-right': (parseInt(selector.getStyle('margin-right')) + this.options.openerWidth)
			}
		}).inject(selector, 'before');
		
		// DISPLAY TEXT
		var mc_text = new Element('span', { 
			'class': 'mc_text',
			'styles': {
				'margin-left': this.options.textPaddingLeft
			}
		}).inject(mc_display);
		
		// OPENER
		var mc_opener = new Element('span', {
			'class': 'mc_opener',
			'text': ' ',
			'styles': {
				'margin-left': selector_coords.width - this.options.openerWidth - this.options.openerFadeOverlap
			}
		}).inject(mc_display, 'top');
		
		// HIDDEN INPUT
		var mc_hidden_el = new Element('input', {
			'type': 'hidden',
			'id': selector.getProperty('id'),
			'name': selector.getProperty('name'),
			'value': selector.getProperty('value')
		}).inject(selector, 'before');
		
		// GET VALUE AND TEXT OF SELECTED
		var selectedOption = selector.selectedIndex;
		var selected_val = selector.options[selectedOption].value;
		var selected_text = selector.options[selectedOption].text;
		
		// OPTIONS CONTAINER
		var optionsZ = (selector.getStyle('z-index').test(/[0-9]+/))?selector.getStyle('z-index'):this.options.optionsZIndex;
		var mc_options = new Element('span', {
			'class': 'mc_options',
			'styles': {
				'position': 'absolute',
				'z-index': optionsZ
			}
		}).inject(mc_text, 'after');
		
		// STORAGE
		mc_hidden_el.store('mc_display', mc_display);
		mc_display.store('mc_options', mc_options);
		mc_display.store('display_index', i);
		mc_display.store('baseclass', this.options.baseClass);
		mc_display.store('mc_hidden_el', selector.getProperty('id'));
		
		this.mc_displays.push(mc_display);
		
		// GET OPTIONS
		var selector_options = selector.getChildren('option');
		
		// WILL OUR OPTIONS BE SCROLLING?
		if(selector_options.length > this.options.maxShowOptions){
			mc_options.setStyles({
				'height': this.options.maxShowOptions * this.options.optionHeight,
				'overflow': 'auto'
			});
		}
		else{
			mc_options.setStyles({
				'height': selector_options.length * this.options.optionHeight,
				'overflow': 'hidden'
			});
		}
		
		// OPTIONAL SHADOWS TO OPTIONS?
		if(this.options.optionsShadow){
			mc_options.setStyles({
				'-moz-box-shadow': this.options.shadowOffset+'px '+this.options.shadowOffset+'px '+this.options.shadowBlur+'px '+this.options.shadowColor,
				'-webkit-box-shadow': this.options.shadowOffset+'px '+this.options.shadowOffset+'px '+this.options.shadowBlur+'px '+this.options.shadowColor,
				'box-shadow': this.options.shadowOffset+'px '+this.options.shadowOffset+'px '+this.options.shadowBlur+'px '+this.options.shadowColor	
			});
		}
		
		// INJECT OPTIONS INTO OPTIONS CONTAINER
		selector_options.each(function(selector_option, i){
			var option_value = selector.options[i].value;
			var option_text = selector.options[i].text;
			var option = new Element('span', {
				'class': 'mc_option',
				'text': option_text,
				'title': selector.options[i].text,
				'events': {
					'mousedown': function(e){
						
						if(e) e.stopPropagation();
												
						// IF IT'S THE CURRENTLY SELECTED OPTION JUST BLUR & HIDE OPTIONS
						if(option.hasClass('active')){
							return false;
						}
						else{
							mc_hidden_el.setProperty('value', option_value);
							mc_hidden_el.store('selected_index', i);
							mc_text.setProperty('text', option_text);
							mc_display.setProperty('title', option_text);
							mc_options.getChildren('span.mc_option').each(function(child){
								(child != option)?child.removeClass('active'):child.addClass('active');
							});
							
							// LET'S FIRE THE ORIGINAL ONCHANGE EVENT REPLACING THIS WITH THE NEW HIDDEN EL
							if(selector.getProperty('onchange')){
								var oOnchange = selector.getProperty('onchange');
								var regEx = /this/;
								var newOnchange = (regEx.test(oOnchange))?oOnchange.replace("this", "$('"+selector.getProperty('id')+"')"):oOnchange;
								var selector_onchange = new Function([newOnchange]);
								selector_onchange();
							}
							this.selected();
						}
						this.hideOptions(mc_display, mc_options);
													
					}.bind(this)
				}
			}).inject(mc_options, 'bottom');
			
			// SET SELECTED OPTION ACTIVE AND STORE THE INDEX
			if(mc_hidden_el.getProperty('value') != undefined){
				if(i == selectedOption){
					option.addClass('active');
					mc_hidden_el.store('selected_index', i);
				}
			}
			else{
				if(option.getProperty('value') == mc_hidden_el.getProperty('value')){
					option.addClass('active');
					mc_hidden_el.store('selected_index', i);
				}
			}
		}.bind(this));
		
		// ADD DISPLAY EVENTS
		mc_display.addEvents({
		
			'mousedown': function(){
				this.moochoosed = mc_display;
				this.toggleOptions(mc_display, mc_options, mc_hidden_el, selector_options);
			}.bind(this),
			
			'focus': function(){
				if(this.moochoosed != mc_display){
					this.moochoosed = mc_display;
					this.showOptions(mc_display, mc_options, mc_hidden_el, selector_options);
				}
				if(selector.getProperty('onfocus')){
					var oOnfocus = selector.getProperty('onfocus');
					var regEx = /this/;
					var newOnfocus = (regEx.test(oOnfocus))?oOnfocus.replace("this", "$('"+selector.getProperty('id')+"')"):oOnfocus;
					var selector_onfocus = new Function([newOnfocus]);
					selector_onfocus();
				}
				this.focused();
			}.bind(this),
			
			'blur': function(){
				this.hideOptions(mc_display, mc_options);
				if(selector.getProperty('onblur')){
					var oOnblur = selector.getProperty('onblur');
					var regEx = /this/;
					var newOnblur = (regEx.test(oOnblur))?oOnblur.replace("this", "$('"+selector.getProperty('id')+"')"):oOnblur;
					var selector_onblur = new Function([newOnblur]);
					selector_onblur();
				}
				this.blurred();
			}.bind(this),
			
			'keypress': function(e){
				
				if(e.key == "up" || e.key == "down")
					e.preventDefault();

				var keyPressed = e.key;
				var keyRegEx = /([a-z0-9])/;
				var alphaNumeric = keyRegEx.test(keyPressed);
				var options = mc_options.getChildren('span');
				var count = options.length;
				var selected_index = mc_hidden_el.retrieve('selected_index');
				var highlighted = (mc_hidden_el.retrieve('highlighted') != undefined)?mc_hidden_el.retrieve('highlighted'):selected_index;
				var next = (highlighted == count - 1)?0:(highlighted + 1);
				var prev = (highlighted > 0)?(highlighted - 1):(count - 1);
				
				if (keyPressed == "up"){
					if(mc_options.getStyle('display') == 'none') 
						this.showOptions(mc_display, mc_options, mc_hidden_el, selector_options);
					mc_hidden_el.store('highlighted', prev);
					mc_options.scrollTo(0, (prev * this.options.optionHeight));
					if(!options[prev].hasClass('active')){
						options[prev].addClass('hover');
					}
					options[highlighted].removeClass('hover');
				}
				else if(keyPressed == "down"){
					if(mc_options.getStyle('display') == 'none') 
						this.showOptions(mc_display, mc_options, mc_hidden_el, selector_options);
					mc_hidden_el.store('highlighted', next);
					mc_options.scrollTo(0, (next * this.options.optionHeight));
					if(!options[next].hasClass('active')){
						options[next].addClass('hover');
					}
					options[highlighted].removeClass('hover');
				}
				else if(keyPressed == "enter"){
					options[highlighted].fireEvent('mousedown');
				}
				else if(alphaNumeric){
					
					var matches = [];
					
					if(this.selectedChar != keyPressed)
						this.selectedByChar = false;
						
					// GET ARRAY OF APPLICABLE OPTIONS
					options.each(function(option, i){
						var optionText = option.get('text').toLowerCase();
						if(keyPressed == optionText.charAt(0)){
							matches.push(i);
						}
					});
					
					if(matches.length){
						if(mc_options.getStyle('display') == 'none') 
							this.showOptions(mc_display, mc_options, mc_hidden_el, selector_options);
						if(this.selectedByChar === false){
							mc_hidden_el.store('highlighted', matches[0]);
							mc_options.scrollTo(0, (matches[0] * this.options.optionHeight));
							if(!options[matches[0]].hasClass('active')){
								options[matches[0]].addClass('hover');
							}
							options[highlighted].removeClass('hover');
							this.selectedByChar = 0;
							this.selectedChar = keyPressed;
						}
						else if(matches.length == 1){
							return;
						}
						else{
							var next = (this.selectedByChar == matches.length - 1)?0:(this.selectedByChar + 1);
							mc_hidden_el.store('highlighted', matches[next]);
							mc_options.scrollTo(0, (matches[next] * this.options.optionHeight));
							if(!options[matches[next]].hasClass('active')){
								options[matches[next]].addClass('hover');
							}
							options[highlighted].removeClass('hover');
							this.selectedByChar = next;
							this.selectedChar = keyPressed;
						}
					}
				}
				
			}.bind(this)
			
		});
		
		// OPENER EVENTS
		mc_opener.addEvents({
			'click': function(e){
				mc_display.focus();
			}.bind(this)
		});
		
		// OPTIONS EVENTS
		mc_options.addEvents({
			'mousedown': function(e){ e.stopPropagation(); },
			'mousewheel': function(e){ e.stopPropagation(); }
		});
		
		// ADD WINDOW EVENTS
		window.addEvents({
			'mousewheel': function(){ this.hideOptions(mc_display, mc_options); }.bind(this),
			'resize': function(){ this.hideOptions(mc_display, mc_options); }.bind(this)
		});
		
		// SAFELY REMOVE ORIGINAL SELECT
		selector.destroy();
		
		// SET HIDDEN VALUE & SET TEXT OF SELECTED CONTAINER
		mc_hidden_el.setProperty('value', selected_val);
		mc_hidden_el.store('selected_index', selectedOption);
		mc_text.setProperty('text', selected_text);
		mc_display.setProperty('title', selected_text);
	},
	
	addSelect: function(selector){
		// If it's already converted just return
		if(selector.getProperty('type') == "hidden")
			return;
		// If there are already moochoose selects we need to index correctly
		this.selects.push(selector);
		this.buildDisplay(selector, this.selects.length);
	},
	
	toggleOptions: function(mc_display, mc_options, mc_hidden_el, selector_options){
		(mc_display.hasClass('active'))?this.hideOptions(mc_display, mc_options):this.showOptions(mc_display, mc_options, mc_hidden_el, selector_options);
	},
	
	showOptions: function(mc_display, mc_options, mc_hidden_el, selector_options){
		
		// SET ALL OPTIONS CLOSED
		$$('span.mc_options').each(function(el){
			el.setStyle('display', 'none');
		});
		
		// SET NEW DISPLAY TO ACTIVE
		this.mc_displays.each(function(el){
			(el != this.moochoosed)?el.removeClass('active'):el.addClass('active');
		}.bind(this));
		
		// GET COORDS OF OPTIONS - USED FOR POSITIONING
		var options_coords = mc_options.measure(function(){
		    return this.getSize();
		});
		
		if(mc_options.retrieve('optionsWidth')){
			var optionsWidth = mc_options.retrieve('optionsWidth');
		}
		else{
			var optionsWidth = options_coords.x + 13;
			mc_options.store('optionsWidth', optionsWidth);
		}
		
		// WHERE ARE WE SHOWING THE OPTIONS?
		var display_coords = mc_display.getCoordinates();
		var win_coords = window.getSize();
		
		// PLACE DIV FOR POSITIONING PURPOSES
		var mc_positioner = new Element('div', {
			'styles': { 
				'position': 'absolute',
				'width': 1,
				'height': 1,
				'top': 0,
				'left': 0
			}
		}).inject(mc_display, 'top');
		var positioner_coords = mc_positioner.getCoordinates();
		
		if((win_coords.y - display_coords.top - display_coords.height - options_coords.y - this.options.optionsOffsetY + window.getScroll().y) >= 0)
			var vertical_pos = display_coords.top + display_coords.height + this.options.optionsOffsetY - positioner_coords.top;
		else
			var vertical_pos = display_coords.top - options_coords.y - this.options.optionsOffsetY - positioner_coords.top;
			
		if((win_coords.x - display_coords.left) >= (optionsWidth + this.options.optionsOffsetX))
			var horizontal_pos = display_coords.left + this.options.optionsOffsetX - positioner_coords.left;
		else
			var horizontal_pos = display_coords.left + display_coords.width - options_coords.x - this.options.optionsOffsetX - (positioner_coords.left);
			
		mc_positioner.dispose();
		
		mc_options.setStyles({
			'display': 'inline-block',
			'top': vertical_pos,
			'left': horizontal_pos
		});
		
		// NON-IE BROWSERS NEED A WIDTH SET
		if(!Browser.Engine.trident){
			mc_options.setStyle('width', optionsWidth);
		}
		
		// OPTIONAL ROUNDED CORNERS
		if(this.options.roundedOptionsCorners){
			mc_options.setStyles({
				'-moz-border-radius': this.options.optionsCornerRadius + "px",
				'-webkit-border-radius': this.options.optionsCornerRadius + "px",
				'border-radius': this.options.optionsCornerRadius + "px"
			});
		}
		
		// SCROLL TO SELECTED IF OVER MAX OPTIONS
		if(selector_options.length > this.options.maxShowOptions){
			mc_options.scrollTo(0, (mc_hidden_el.retrieve('selected_index') * this.options.optionHeight));
		}
	},
	
	hideOptions: function(mc_display, mc_options){
		mc_display.removeClass('active');
		mc_options.setStyle('display', 'none');
	},
	
	changeClass: function(selector, newClass){
		var mc_display = selector.retrieve('mc_display');
		mc_display.removeClass(mc_display.retrieve('baseclass'));
		mc_display.addClass(newClass);
		mc_display.store('baseclass', newClass);
	},
	
	focused: function(){
		this.fireEvent('focused');
	},
	
	blurred: function(){
		this.fireEvent('blurred');
	},
	
	selected: function(){
		this.fireEvent('selected');
	}
	
});
