/*

 TODO List:
 - ajaxify
 - animation
 - keypresses on optgroups
 - events
  
 */
 
XERO.widget.DropDownList = Class.create();
XERO.widget.DropDownList.prototype = {
    /* events */
    onSelected: null,
    
    /* constructor */
    initialize: function(id, dictionary) {
        this.id = id;
        this._isOpen = false;
        this.list = null;
        this._cachedData = {};
        this.selectedItem = { index: -1, element: null, key: null, value: null };
        this.hasContainerResized = false;
        this.controls = {
            controlContainer: null,
            input: null,                    // the textbox
            key: null,
            toggle: null,                   // the toggle anchor
            container: null,                // the list container
            list: null
        };        

        // Load options
        this.options = Object.extend({
            sortedDictionary: (dictionary ? dictionary.sorted : false) || false,
            maxListHeight: 400,
            listWidth: 180,
            onSelected: null
        }, arguments[2] || {});

        // select events
        this.onSelected = this.options.onSelected;

        // get the dictionary        
        dictionary = dictionary || window[id + "_data"];
        if(!dictionary) return;
        
        // Add dictionary to static dictionary cache
        if(!XERO.widget.DropDownList._data[id]) {           
            XERO.widget.DropDownList._data[id] = dictionary;
        }
        
        // Find control elements
        this.controls.controlContainer = $(id + "_control");
        this.controls.input = $(id + "_value");
        this.controls.key = $(id);// + "_key"
        this.controls.toggle = $(id + "_toggle");
        this.controls.container = $(id + "_container");
        
        this.controls.list = document.createElement("ul");
        this.controls.list.className = "dropDownList";
        this.controls.list.id = id + "_ddl";
        this.controls.container.appendChild(this.controls.list);
        this.controls.list = $(this.controls.list);
        
        /* Listen for events */
        // Keyboard events
        this.controls.input.observe('keypress', this._onKeyPress.bindAsEventListener(this)); 
        // Click events       
        this.controls.input.observe('click', this.toggle.bindAsEventListener(this)); 
        this.controls.toggle.observe('click', 
            function(e){
                if(!this._isOpen){
                    this._show();
                    this.controls.input.focus();
                } else {
                    this._hide();
                }
                Event.stop(e);
            }.bind(this)
        ); 
        this.controls.controlContainer.observe('click', function(e){ Event.stop(e); });
        this.controls.list.observe('mousedown', this.selectItem.bindAsEventListener(this) );        
        $(document.getElementsByTagName('body')[0]).observe('click', this._hide.bind(this));
        // Hover
        this.controls.list.observe('mouseover', 
            function(e){ var li = Event.element(e); if(li && li.className!='optgroup') li.addClassName('hover'); }
        );
        this.controls.list.observe('mouseout', 
            function(e){ var li = Event.element(e); if(li && li.className!='optgroup') li.removeClassName('hover'); }
        );
        // Focus/Blur
        this.controls.input.observe('focus', 
            function(e){ 
                var li = Event.element(e); 
                if(li) li.addClassName('active');
                Event.stop(e);
            }.bindAsEventListener(this)
        );
        this.controls.input.observe('blur', 
            function(e){ 
                var li = Event.element(e); 
                if(li) li.removeClassName('active'); 
            }.bindAsEventListener(this)
        );
        
        if(dictionary.selected){
            this._storeSelectedItem(dictionary.selected.index, dictionary.selected.key, dictionary.selected.value, null);
        }

        return this;
    },
    
    _renderDictionary : function(id, dictionary) {                
        while (this.controls.list.hasChildNodes()){
            this.controls.list.removeChild(this.controls.list.firstChild);
        }

        var keys = Object.keys(dictionary).without("inherits", "sorted", "selected", "clone");
        var keysLength = keys.length;
        var itemsAdded = 0;
        var indexOfSelectedItem = -1;
        
        // Build up list items
        var html = new Array();
        
        for(var k=0, key; key = keys[k]; k++){
            var dic = dictionary[key];
            if(dic){
                if(dic.title){
                    html.push( this._renderDictionaryGroupTitle(dic.title, itemsAdded++) );
                }
                for(var i=0, datum; datum = dic.data[i]; i++, itemsAdded++){
                    datum.index = itemsAdded;
                    html.push( this._renderDictionaryItem(datum) );
                    if(itemsAdded == this.selectedItem.index) {
                        indexOfSelectedItem = itemsAdded;
                    }
                }
            }
        }
        
        // Insert list items into list
        setTimeout(
            function(){
                this.controls.list.innerHTML = html.join('');
                if(indexOfSelectedItem != -1) {
                    this._storeSelectedItem(indexOfSelectedItem, this.selectedItem.key, this.controls.list.childNodes[indexOfSelectedItem].innerHTML, this.controls.list.childNodes[indexOfSelectedItem])
                }
                this._resizeListContainer();
            }.bind(this)
        ,0);
        
        return this.controls.list;
    },

    _renderDictionaryGroupTitle: function(title, index){
        /* Split out to increase speed. src:http://dev.opera.com/articles/view/efficient-javascript/?page=2 */
        var li = '';
        li += '<li class="optgroup" index="';
        li += index;
        li += '">';
        li += title;
        li += '</li>';
      
        return li; 
    },
    
    _renderDictionaryItem: function(datum){
        var k = datum.key;
        var v = datum.value;
        var i = datum.index;
        
        var selected = (this.selectedItem.key == k);
        
        var classes = "";
        if(selected) classes = "selected";
        
        /* Split out to increase speed. src:http://dev.opera.com/articles/view/efficient-javascript/?page=2 */
        var li = '';
        li += '<li class="';
        li += classes;
        li += '" key="';
        li += k;
        li += '" index="';
        li += i;
        li += '">';
        li += v;
        li += '</li>';
      
        return li;       
    },
    
    _resizeListContainer: function(){
        if(!this.hasContainerResized){
            if(this.controls.container.scrollHeight > this.options.maxListHeight){
                this.controls.container.style.height = this.options.maxListHeight+'px';
                this.controls.container.style.overflow = 'auto';
            } else {
                this.controls.container.style.height = 'auto';
                this.controls.container.style.overflow = 'visible';
            }
            this.controls.container.style.width = this.options.listWidth+'px';
            this.hasContainerResized = true;
        }
    },
    
    _render : function(e, maxItems, startIndex){   
        if(!this._cachedData.data){
            var dictionary = XERO.widget.DropDownList._data[this.id];
            this._cachedData.data = this._renderDictionary(this.id, dictionary, maxItems);    
        }
    },
    
    /* toggle the display this list */
    toggle : function(e){
        if(this._isOpen) {
            this._hide();
        } else {
            this._show();
        }
        Event.stop(e);
    },
    
    /* hide this list */
    _hide : function(e){
        if(this._isOpen){
            this.controls.container.style.display='none';
            this._isOpen = false;
            // stop tracking this list as being open
            XERO.widget.DropDownList._currentlyOpenList = null;
        }
    },
    
    /* Show this list.
     * This will also cause a re-render of the list if required
     */
    _show : function(e){
        /* hide an already open lists */
        if(XERO.widget.DropDownList._currentlyOpenList != null){
            XERO.widget.DropDownList._currentlyOpenList._hide();
        }
        /* open this list */
        if(!this._isOpen){
            this._isOpen = true;
            this._render(e);
            this._scrollItemIntoView(this.selectedItem.element);
            // track this list as being open
            XERO.widget.DropDownList._currentlyOpenList = this;
            this.controls.container.style.display='block';                       
        }
    },
    
    /* Set a list item as being selected.
     * It is passed an event (e) and an index.
     * If the event is passed then the LI is gathered from that, otherwise the index is 
     * used to grab from the childNodes list of the UL.
     */
    selectItem : function(e, i){
        var li;
        if(e){
            li = Event.element(e);
            i = li.getAttribute("index") * 1;
            li = this.controls.list.childNodes[i];
        } else {
            if(i >= this.controls.list.childNodes.length) i = this.controls.list.childNodes.length - 1;
            if(i < 0) i = 0;            
            if(this.controls.list.hasChildNodes) li = this.controls.list.childNodes[i];
            var delta = (i - this.selectedItem.index);
            if(delta < 0){
                delta = -1;
            } else if(delta > 0){
                delta = 1;
            } else {
                delta = 0;
            }
            if(delta != 0){
                while(li && li.className == 'optgroup' && i > 0 && i < this.controls.list.childNodes.length) {
                    i += delta;
                    li = this.controls.list.childNodes[i];
                }
            }
            if(this.selectedItem.element == li) return;
        }
        if(li){
            if(li.attributes['key']){
                var key = li.getAttribute("key");
                
                var prevSelected = this.selectedItem.element;

                // store selected values       
                this._storeSelectedItem(i, key, li.innerHTML, li);      

                $(this.selectedItem.element).addClassName('selected');
                
                // unselect the current item
                if(prevSelected){
                    if(prevSelected){
                        $(prevSelected).removeClassName('selected');
                    }
                }
                
                // hide if it was a click
                if(e) { 
                    this._hide(e);
                    Event.stop(e);
                }
                
                this._scrollItemIntoView(this.selectedItem.element);                                
                
                if(this.onSelected != null && typeof(this.onSelected) == "function"){
                    this.onSelected();
                }
            }
        }
    },
    
    /* Store the data as a selected item */
    _storeSelectedItem : function(index, key, value, li){
        this.selectedItem.index = index;
        this.selectedItem.element = li;
        this.selectedItem.key = key;
        this.selectedItem.value = value;
        
        this.controls.input.value = value;
        this.controls.key.value = key;
    },
    
    /* Scroll a LI item into view within the container */
    _scrollItemIntoView: function(li){
        if(li){
            var y = li.offsetTop;
            var t = (this.options.maxListHeight / 2);
            if(y > t){
                y = y - t;
                this._scrollToPosition(y);
            } else {
                this._scrollToPosition(0);
            }
        }
    },
    
    _scrollToPosition : function(y){
        this.controls.container.scrollTop = y;
    },
    
    _onKeyPress: function(e){
        if(!this._isOpen) {
		    switch(e.keyCode) 
		    {
    		    case Event.KEY_HOME:
			    case Event.KEY_END:
				case Event.KEY_PAGEUP:
				case Event.KEY_PAGEDOWN:
			    case Event.KEY_RETURN:
			    case Event.KEY_LEFT:
			    case Event.KEY_RIGHT:
			    case Event.KEY_UP:
			    case Event.KEY_DOWN:
			        this._show(e);
			        break;
			    case Event.KEY_TAB:
			    case Event.KEY_ESC:
			    default:
				    return;
		    }
		}
		
		if(this._isOpen){
		    switch(e.keyCode) 
		    {
			    case Event.KEY_TAB:
				    this._hide();
				    return;
			    case Event.KEY_RETURN:
				    this._hide();
				    break;
			    case Event.KEY_ESC:
				    this._hide();
				    break;
			    case Event.KEY_LEFT:
			    case Event.KEY_RIGHT:
				    return;
			    case Event.KEY_UP:
			        this.selectItem(null, this.selectedItem.index - 1);
			        return;
			    case Event.KEY_DOWN:
			        this.selectItem(null, this.selectedItem.index + 1);
			        return;
			    case Event.KEY_PAGEUP:
			        this.selectItem(null, this.selectedItem.index - 20);
			        return;
				case Event.KEY_PAGEDOWN:
				    this.selectItem(null, this.selectedItem.index + 20);
			        return;
			    case Event.KEY_HOME:
				    this.selectItem(null, 0);
				    return;
			    case Event.KEY_END:
				    this.selectItem(null, this.controls.list.childNodes.length - 1);
				    return;
		    }
		}
		Event.stop(e);
    }   
}

XERO.widget.DropDownList._data = new Hash();
XERO.widget.DropDownList._currentlyOpenList = null;
