/**
 * @version 0.0.1.2
 * @class Select
 * @constructor
 * @param  {String} elementID The source HTMLSelect ID
 * @param  {Array} options The source HTMLSelect ID
 */
function DropDownList(elementID, options) {
	var select = document.getElementById(elementID);	
	var self = this;
	if (!options)
		options = {};
	
    // source <select /> element
	this.select = select;
	if (!this.select || this.select.tagName.toLowerCase() != 'select' || this.select.isInitialized)
		return;
	
	// puts current dropdown list into global collection
	var i = 0;
	while (i< DropDownList.selects.length) {
		if (DropDownList.selects[i].select.id == elementID) {
			DropDownList.selects[i] = this;
			break;
		}
		i++;
	}
	if (i == DropDownList.selects.length)
		DropDownList.selects.push(this);	

	options.width = options.width || getDimensions(this.select).width;
	// hide the select field
    this.select.style.display = 'none'; 
	this.options = this.select.options;
	
	// initialize options
	this._initializeOptions(options);
	this.select.isInitialized = true;
	
	// create and build div structure
	this.selectArea = document.createElement('div');
	var leftDiv = document.createElement('div');
	var rightDiv = document.createElement('div');
	this.textContainer = document.createElement('div');
	this.textContainer.id = this.select.id + 'Text';
	this.textContainer.style.paddingLeft = '4px';
	this.textContainer.style.cssFloat = 'left';
	var text = document.createTextNode(this.emptyText);
	this.selectArea.id = this.select.id + 'SelectArea';
	this.selectArea.style.width = parseInt(this.width) + 'px';
	this.selectArea.style.height = parseInt(this.height) + 'px';
	addClass(this.selectArea, this.selectAreaStyle);
	addClass(leftDiv, this.selectAreaLeftStyle);
	addClass(rightDiv, this.selectAreaRightStyle);
	addClass(this.textContainer, this.selectAreaCenterStyle)
	this.textContainer.appendChild(text);
	this.selectArea.appendChild(leftDiv);
	this.selectArea.appendChild(rightDiv);
	this.selectArea.appendChild(this.textContainer);
	//insert select div
	this.select.parentNode.insertBefore(this.selectArea, this.select);
	var padding = 4;
	if (navigator.appVersion.indexOf('MSIE 6') > 0)
		padding = 10;
	// fix IE6 bug
	var containerWidth = this.width - padding - getDimensions(leftDiv).width - getDimensions(rightDiv).width;
	this.textContainer.style.width =  (containerWidth > 0) ? containerWidth + 'px': '0';
	//build & place options div
	this.optionsArea = document.createElement('ul');
	this.optionsArea.id = this.select.id + 'Options';
	if (this.dropDownSize > 0 && this.options.length > this.dropDownSize) {
		this.optionsArea.style.height = (this.dropDownSize + 4) * this.optionHeight + 'px';
	}
	this.optionsArea.style.width = parseInt(this.width) - 2 + 'px';
	this.optionsArea.className = this.optionsInvisibleStyle;
	//get select's options and add to options div
	for(var w = 0; w < this.options.length; w++) {
		var optionHolder = document.createElement('li');
		optionHolder.id = this.select.id + 'Option' + w;
		optionHolder.style.paddingLeft = '4px';
		if (this.options[w].text.length == 0)
			this.options[w].text = this.emptyText;
		if (w == 0)
			this.defaultValue = this.options[w].text;
		var optionTxt = document.createTextNode(this.options[w].text);
		optionHolder.position = w;
		optionHolder.onclick = function() {
			self.selectOption(this.position);	
			self.fireItemChanged();
			self.close();
		}
		optionHolder.onmouseover = function() {
			self.unhoverOption(self.hoveredIndex);
			self.hoveredIndex = this.position;
			self.hoverOption(self.hoveredIndex);
		}
		optionHolder.onmouseout = function() {
			self.unhoverOption(this.position);
			self.hoveredIndex = -1;
		}
		optionHolder.appendChild(optionTxt);
		
		this.optionsArea.appendChild(optionHolder);
		//check for pre-selected items
		if(this.options[w].selected) {
			this.selectOption(w);
			addClass(optionHolder, this.optionSelectedStyle);
		}	
	}

	//insert options div
	//this.selectArea.appendChild(this.optionsArea);
	this.select.parentNode.insertBefore(this.optionsArea, this.select);
	
	// disables dropdown if it was disabled before creation
	i = 0;
	while (i< DropDownList.disable.length){
		if (DropDownList.disable[i] == elementID){		
			this.disable();
			DropDownList.disable.splice(i,1);
			break;
		}
		i++;
	}	
	
	this._initializeEventHandlers();
}

if (!DropDownList.selects)
	DropDownList.selects = new Array();

if (!DropDownList.disable)
	DropDownList.disable = new Array();
	
 /*
 * @description Returnd select entity.
 * @method findControl
 * @return {DropDownList entity}
 */
DropDownList.findControl = function(selectId){
	for (var i = 0; i< DropDownList.selects.length; i++){
		if (DropDownList.selects[i].select.id == selectId){
			return DropDownList.selects[i];
		}
	}
	return null;
};

/*
 * @description disable select entity.
 * @method disableControl
 * @return {void}
 */
DropDownList.disableControl = function(selectId){
	for (var i = 0; i< DropDownList.selects.length; i++){
		if (DropDownList.selects[i].select.id == selectId){
			DropDownList.selects[i].disable();
		}
	}
	DropDownList.disable.push(selectId);
};

DropDownList.prototype = {
    /**
    * @description Initializes options.
    * @method _initializeOptions
    * @param {Array}	options	The options to initialize from.
    * @return {void}
    * @private
    */
    _initializeOptions: function(options) {
        // initialize CSS styles
        var options = options || {};
        this.selectAreaStyle = options.selectAreaStyle || 'selectArea';
        this.selectAreaOpenedStyle = options.selectAreaOpenedStyle || 'selectAreaOpened';
        this.selectAreaLeftStyle = options.selectAreaLeftStyle || 'selectAreaLeft';
        this.selectAreaRightStyle = options.selectAreaRightStyle || 'selectAreaRight';
        this.selectAreaCenterStyle = options.selectAreaCenterStyle || 'selectAreaCenter';
        this.optionsVisibleStyle = options.optionsVisibleStyle || 'selectOptionsVisible';
        this.optionsInvisibleStyle = options.optionsInvisibleStyle || 'selectOptionsInvisible';
        this.optionSelectedStyle = options.optionSelectedStyle || 'selectOptionSelected';
        this.optionHoveredStyle = options.optionHoveredStyle || 'selectOptionHovered';
        // initialize other options
        this.emptyText = options.emptyText || 'Select';
        this.optionsSeparator = options.optionsSeparator || ',';
        this.optionsOverlap = options.optionsOverlap || 1;
        this.width = options.width;
        this.height = options.selectHeight || 19;
        this.optionHeight = parseInt(options.optionHeight) || 15;
        this.opened = false;
        this.hoveredIndex = -1;
        this.dropDownSize = parseInt(options.dropDownSize) || 0;
    },

    /**
    * @description Initializes select event handlers.
    * @method _initializeEventHandlers
    * @return {void}
    * @private
    */
    _initializeEventHandlers: function() {
        var self = this;
        var body = document.getElementsByTagName('body')[0];

        var selectKeyDownHandler = function(e) {
            var e = e || window.event;
            self._handleKeyDownEvent(e);
        }

        var bodyClickHandler = function() {
            self.close();
            if (self.addedKeyDownHandler) {
                removeEventHandler(document, 'keydown', selectKeyDownHandler);
                self.addedKeyDownHandler = false;
            }
        }

        var selectAreaClickHandler = function() {
            self.toggle();
            if (self.opened) {
                if (!self.addedKeyDownHandler) {
                    addEventHandler(document, 'keydown', selectKeyDownHandler);
                    self.addedKeyDownHandler = true;
                }
            }
            else {
                if (self.addedKeyDownHandler) {
                    removeEventHandler(document, 'keydown', selectKeyDownHandler);
                    self.addedKeyDownHandler = false;
                }
            }
        }


        var selectMouseOutHandler = function() {
            if (!self.addedbodyClickHandler) {
                addEventHandler(body, 'click', bodyClickHandler);
                self.addedbodyClickHandler = true;
            }
        }

        var selectMouseOverHandler = function() {
            if (self.addedbodyClickHandler) {
                removeEventHandler(body, 'click', bodyClickHandler);
                self.addedbodyClickHandler = false;
            }
        }

        addEventHandler(this.selectArea, 'click', selectAreaClickHandler);
        addEventHandler(this.selectArea, 'mouseover', selectMouseOverHandler);
        addEventHandler(this.selectArea, 'mouseout', selectMouseOutHandler);
        addEventHandler(this.optionsArea, 'mouseover', selectMouseOverHandler);
        addEventHandler(this.optionsArea, 'mouseout', selectMouseOutHandler);

    },

    /**
    * @description Handles select key down events.
    * @method _handleKeyDownEvent
    * @param {Event}	e	The event to be handled from.
    * @return {void}
    * @private
    */
    _handleKeyDownEvent: function(e) {
        var keyCode = e.keyCode;
        switch (keyCode) {
            case 40: // down
                this.unhoverOption(this.hoveredIndex);
                this.hoveredIndex++;
                if (this.hoveredIndex >= this.options.length)
                    this.hoveredIndex = 0;
                this.hoverOption(this.hoveredIndex);
                break;
            case 38: // up
                this.unhoverOption(this.hoveredIndex);
                this.hoveredIndex--;
                if (this.hoveredIndex < 0)
                    this.hoveredIndex = this.options.length - 1;
                this.hoverOption(this.hoveredIndex);
                break;
            case 27: // escape
                this.close();
                break;
            case 32: // space
                this.selectOption(this.hoveredIndex);
                this.fireItemChanged();
                break;
            case 13: // enter
                if (!this.options[this.hoveredIndex].selected) {
                    this.selectOption(this.hoveredIndex);
                    this.fireItemChanged();
                }
                this.close();
                break;
            default:
                break;
        }
    },

    /**
    * @description Opens select.
    * @method open
    * @return {void}
    */
    open: function() {
        if (!this.disabled) {
            if (hasClass(this.optionsArea, this.optionsInvisibleStyle))
                replaceClass(this.optionsArea, this.optionsInvisibleStyle, this.optionsVisibleStyle);
            addClass(this.selectArea, this.selectAreaOpenedStyle);
            this.opened = true;
        }
    },

    /**
    * @description Closes select.
    * @method close
    * @return {void}
    */
    close: function() {
        if (hasClass(this.optionsArea, this.optionsVisibleStyle))
            replaceClass(this.optionsArea, this.optionsVisibleStyle, this.optionsInvisibleStyle);
        removeClass(this.selectArea, this.selectAreaOpenedStyle);
        this.opened = false;
    },

    /**
    * @description Disables select.
    * @method disable
    * @return {void}
    */
    disable: function() {
        this.disabled = true;
        this.textContainer.innerHTML = this.defaultValue;
    },

    /*
    * @description Enables select.
    * @method enable
    * @return {void}
    */
    enable: function() {
        this.disabled = false;
    },

    /**
    * @description Toggle select.
    * @method toggle
    * @return {void}
    */
    toggle: function() {
        this.opened ? this.close() : this.open();
    },

    /**
    * @description Selects specified option.
    * @method selectOption
    * @param {int}	selectedIndex	The option index to be selected.
    * @return {void}
    */
    selectOption: function(selectedIndex) {
        //feed selected option to the actual select field

        if (this.select.multiple) {
            var option = document.getElementById(this.select.id + 'Option' + selectedIndex);
            if (option) {
                this.options[selectedIndex].selected = !this.options[selectedIndex].selected;
                this.options[selectedIndex].selected ? addClass(option, this.optionSelectedStyle) : removeClass(option, this.optionSelectedStyle);
            }
            // modify selected option
            var text = '';
            for (var k = 0; k < this.options.length; k++) {
                if (this.options[k].selected)
                    text += this.options[k].text + this.optionsSeparator;
            }
            if (text.length > this.optionsSeparator.length)
                text = text.substring(0, text.length - this.optionsSeparator.length);
            else if (text.length == 0)
                text = this.emptyText;
            var newText = document.createTextNode(text);
            this.textContainer.replaceChild(newText, this.textContainer.childNodes[0]);
            //			if((selectedIndex>0)&&(this.options[selectedIndex].attributes["value"]))
            //			document.location.href=this.options[selectedIndex].attributes["value"].nodeValue;
        }
        else {
            for (var k = 0; k < this.options.length; k++) {
                if (k == selectedIndex) {
                    this.options[k].selected = true;
                    this.select.selectedIndex = selectedIndex;
                    var option = document.getElementById(this.select.id + 'Option' + k);
                    if (option)
                        addClass(option, this.optionSelectedStyle);
                }
                else {
                    this.options[k].selected = false;
                    var option = document.getElementById(this.select.id + 'Option' + k);
                    if (option)
                        removeClass(option, this.optionSelectedStyle);
                }


                //				if ((selectedIndex > 0) && (this.options[selectedIndex].attributes["value"]))
                //				    document.location.href = this.options[selectedIndex].attributes["value"].nodeValue;
            }

            //show selected option
            this.textContainer.innerHTML = this.options[selectedIndex].text;
        }

    },

    /**
    * @description Fires 'change' event for select control.
    * @method fireItemChanged
    * @return {void}
    */
    fireItemChanged: function() {
        if (this.select.onchange) {
            var oldDisplay = this.select.style.display;
            this.select.style.display = 'block';
            if (this.select.fireEvent) // IE 5.5(WIN)
            {
                this.select.fireEvent("onChange");
            }
            else // Mozilla, Safari etc.
            {
                var evt = document.createEvent("HTMLEvents");
                evt.initEvent("change", true, true);
                this.select.dispatchEvent(evt);
            }
            this.select.style.display = oldDisplay;
        }
    },

    /**
    * @description Hovers specified option.
    * @method hoverOption
    * @param {int}	hoveredIndex	The option index to be hovered.
    * @return {void}
    */
    hoverOption: function(hoveredIndex) {
        if (hoveredIndex >= 0 && hoveredIndex < this.options.length) {
            var hoveredOption = document.getElementById(this.select.id + 'Option' + hoveredIndex);
            addClass(hoveredOption, this.optionHoveredStyle);
        }
    },

    /**
    * @description Unhovers specified option.
    * @method unhoverOption
    * @param {int}	hoveredIndex	The option index to be unhovered.
    * @return {void}
    */
    unhoverOption: function(hoveredIndex) {
        if (hoveredIndex >= 0 && hoveredIndex < this.options.length) {
            var hoveredOption = document.getElementById(this.select.id + 'Option' + hoveredIndex);
            removeClass(hoveredOption, this.optionHoveredStyle);
        }
    }
}

//Useful functions

/**
 * @description Returns element dimensions.
 * @method getDimensions
 * @param {HTMLElement}	el	The DOM elementement to get dimensions of.
 * @return {Array} HTMLElement offset.
 */
function getDimensions(el) {
	if (el.style.display != 'none' && el.style.display != null) // Safari bug
		return {width: el.offsetWidth, height: el.offsetHeight};
	// All *Width and *Height properties give 0 on els with display none,
	// so enable the el temporarily
	var els = el.style;
	var originalVisibility = els.visibility;
	var originalPosition = els.position;
	var originalDisplay = els.display;
	els.visibility = 'hidden';
	els.position = 'absolute';
	els.display = 'block';
	var originalWidth = el.clientWidth;
	var originalHeight = el.clientHeight;
	els.display = originalDisplay;
	els.position = originalPosition;
	els.visibility = originalVisibility;
	return {width: originalWidth, height: originalHeight};    
}

/**
 * @description Returns an HTMLElement offset.
 * @method getOffset
 * @param {HTMLElement}	el	The DOM element to get offset of.
 * @return {Array} HTMLElement offset.
 */
function getOffset(el) {
	var valueT = 0, valueL = 0;
    do {
      valueT += el.offsetTop  || 0;
	  valueL += el.offsetLeft  || 0;
      el = el.offsetParent;
      if (el) {
        if (el.tagName.toLowerCase() == 'body')
			break;
        var pos = el.style.position;
        if (pos == 'relative' || pos == 'absolute')
			break;
      }
    } while (el);
	return {left: valueL, top: valueT};
}

/**
 * @description Determines whether an HTMLElement has the given className.
 * @method hasClass
 * @param {HTMLElement}	el		The DOM element to test.
 * @param {String}		className	The class name to search for.
 * @return {Boolean | Array} A boolean value or array of boolean values.
 */
function hasClass(el, className) {
	var elClassName = el.className;
	return (elClassName.length > 0 && (elClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elClassName)));
}

/**
 * @description Adds a class name to a given el.
 * @method addClass         
 * @param {HTMLElement}	el		The DOM element to add the class to.
 * @param {String}		className	The class name to add to the class attribute.
 * @return {void}
 */
function addClass(el, className) {
	if (!hasClass(el, className))
		el.className += (el.className ? ' ' : '') + className;
}

/**
 * @description Removes a class name from a given el.
 * @method removeClass         
 * @param {HTMLElement}	el		The DOM element or to remove the class from.
 * @param {String}		className	The class name to remove from the class attribute.
 * @return {void}
 */
function removeClass(el, className) {
	el.className = el.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').replace(/^\s+/, '').replace(/\s+$/, '');
}

/**
 * @description Replaces a class name fom a given el.
 * @method removeClass         
 * @param {HTMLElement}	el			The DOM elementement to remove the class from.
 * @param {String}		oldClassName	The class name to remove from the class attribute.
 * @param {String}		newClassName	The class name to add to the class attribute.
 * @return {void}
 */
function replaceClass(el, oldClassName, newClassName) {
	removeClass(el, oldClassName);
	addClass(el, newClassName);
}

/**
 * @description Adds a DOM event directly without the caching, cleanup, scope adj, etc.
 * @method addEventHandler
 * @param {HTMLElement}	el		The DOM element to bind the handler to.
 * @param {String}		name		The name of event handler.
 * @param {function} 		handler	The callback to invoke.
 * @return {void}
 */
function addEventHandler(el, name, handler) {
	el.addEventListener ? el.addEventListener(name, handler, false) : el.attachEvent('on' + name, handler);
}

/**
 * @description Removes a DOM event.
 * @method removeEventHandler
 * @param {HTMLElement}	el		The DOM element to bind the handler to.
 * @param {String}		name		The name of event handler.
 * @param {function} 		handler	The callback to invoke.
 * @return {void}
 */
function removeEventHandler(el, name, handler) {
	el.removeEventListener ? el.removeEventListener(name, handler, false) : el.detachEvent('on' + name, handler);
}