Ext.namespace('Ext.ux.form');

Ext.ux.form.CheckboxCombo = Ext.extend(Ext.form.TriggerField, {
    /**
     * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
     * the dropdown list (defaults to undefined, with no header element)
     */

    // private
    defaultAutoCreate: {tag: "input", type: "text", size: "24", autocomplete: "off"},
    
    /**
     * @cfg {String} listClass The CSS class to add to the predefined 'x-checkboxcombo-list' class
     * applied the dropdown list element (defaults to '').
     */
    listClass: '',
    
    /**
     * @cfg {String} listEmptyText The empty text to display in the data view if no items are found.
     * (defaults to '')
     */
    listEmptyText: '',
    
    /**
     * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always
     * get the class 'x-form-trigger' and triggerClass will be appended if specified
     * (defaults to 'x-form-arrow-trigger' which displays a downward arrow icon).
     */
    triggerClass: 'x-form-arrow-trigger',
    
    /**
     * @cfg {Boolean/String} shadow true or "sides" for the default effect, "frame" for
     * 4-way shadow, and "drop" for bottom-right
     */
    shadow: 'sides',
    
    /**
     * @cfg {String/Array} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details
     * on supported anchor positions and offsets. To specify x/y offsets as well, this value
     * may be specified as an Array of {@link Ext.Element#alignTo} method arguments.

     * 
    [ 'tl-bl?', [6,0] ]
    (defaults to 'tl-bl?')
     */
    listAlign: 'tl-bl?',
    
    /**
     * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown
     * (defaults to 300)
     */
    maxHeight: 300,
    
    /**
     * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its
     * distance to the viewport edges (defaults to 90)
     */
    minHeight: 90,
    
    /**
     * @cfg {Boolean} selectOnFocus true to select any existing text in the field immediately on focus.
     * Only applies when {@link Ext.form.TriggerField#editable editable} = true (defaults to
     * false).
     */
    selectOnFocus: false,
    
    /**
     * @cfg {Boolean} false to prevent the user from typing text directly into the field, the field will only respond to a click on the trigger to set the value. (defaults to false).
     */
    editable: false,
    
    /**
     * @cfg {String} loadingText The text to display in the dropdown list while data is loading.  Only applies
     * when {@link #mode} = 'remote' (defaults to 'Loading...')
     */
    loadingText: 'Loading...',
    
    /**
     * @cfg {String} mode Acceptable values are 'remote' (Default) or 'local'
     */
    mode: 'remote',
    
    /**
     * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will
     * be ignored if {@link #listWidth} has a higher value)
     */
    minListWidth: 70,
    
    /**
     * @cfg {Boolean} updateOnChange true to update the value as soon as the user checks or unchecks the checkboxes
     * (defaults to true)
     */
    updateOnChange: true,
    
    /**
     * @cfg {Boolean} displayLabelsInInput true to display the labels of the selected values in the input
     *                                     false to always display the emptyText in the input
     * (defaults to true)
     */
    displayLabelsInInput: true,
    
    /**
     * @cfg {String} textDisplayedAsValue Text displayed in the value input if displayLabelsInInput is set to true
     * (defaults to this.emptyText)
     */
    textDisplayedAsValue: this.emptyText,
    
    
    /**
     * @cfg {Boolean} lazyInit true to not initialize the list for this combo until the field is focused
     * (defaults to true)
     */
    lazyInit: true,

    
    /**
     * @cfg {Boolean} submitValue False to clear the name attribute on the field so that it is not submitted during a form post.
     * If a hiddenName is specified, setting this to true will cause both the hidden field and the element to be submitted.
     * Defaults to undefined.
     */
    submitValue: undefined,

    
    // private
    initComponent: function() {
        Ext.ux.form.CheckboxCombo.superclass.initComponent.call(this);
        this.addEvents(
            'expand',
            'collapse'
        );

        //auto-configure store from local array data
        if (this.store) {
            this.store = Ext.StoreMgr.lookup(this.store);
            if (this.store.autoCreated){
                this.displayField = this.valueField = 'field1';
                if (!this.store.expandData){
                    this.displayField = 'field2';
                }
                this.mode = 'local';
            }
        }

        this.selectedIndex = -1;
    },

    // private
    onRender: function(ct, position){
        if (this.hiddenName && !Ext.isDefined(this.submitValue)){
            this.submitValue = false;
        }
        Ext.ux.form.CheckboxCombo.superclass.onRender.call(this, ct, position);
        if (this.hiddenName){
            this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id: (this.hiddenId||this.hiddenName)}, 'before', true);
        }
        if (Ext.isGecko){
            this.el.dom.setAttribute('autocomplete', 'off');
        }
        if (!this.lazyInit){
            this.initList();
        } else {
            this.on('focus', this.initList, this, {single: true});
        }
    },

    // private
    initValue: function() {
        Ext.ux.form.CheckboxCombo.superclass.initValue.call(this);
        if (this.hiddenField){
            this.hiddenField.value = Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, '');
        }
    },

    // private
    initList: function() {
        if (!this.list){
            var cls = 'x-checkboxcombo-list',
                listParent = Ext.getDom(this.getListParent() || Ext.getBody()),
                zindex = parseInt(Ext.fly(listParent).getStyle('z-index') ,10);

            if (this.ownerCt && !zindex){
                this.findParentBy(function(ct){
                    zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
                    return !!zindex;
                });
            }

            this.list = new Ext.Layer({
                parentEl: listParent,
                shadow: this.shadow,
                cls: [cls, this.listClass, 'x-combo-list'].join(' '),
                constrain:false,
                zindex: (zindex || 12000) + 5
            });

            var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
            this.list.setWidth(lw);
            this.list.swallowEvent('mousewheel');
            this.assetHeight = 0;
            if (this.syncFont !== false){
                this.list.setStyle('font-size', this.el.getStyle('font-size'));
            }

            this.innerList = this.list.createChild({cls:cls + '-inner x-combo-list-inner'});
            this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
            this.mon(this.innerList, 'mouseover', this.onListOver, this, {delegate: '.x-form-item'});
            this.mon(this.innerList, 'mousemove', this.onListMove, this, {delegate: '.x-form-item'});
            this.mon(this.innerList, 'click', this.onListClick, this, {delegate: '.x-form-item'});
            
            this.cbgroup = new Ext.form.CheckboxGroup({
                renderTo: this.innerList,
                columns: 1,
                autoHeight: true,
                autoScroll: true,
                border: false,
                items: [{}]
            });
            
            this.bindStore(this.store, true);
            this.restrictHeight();
        }
    },

    getListParent: function() {
        return document.body;
    },

    
    /**
     * Returns the store associated with this combo.
     * @return {Ext.data.Store} The store
     */
    getStore: function() {
        return this.store;
    },

    // private
    bindStore: function(store, initial){
        if (this.store && !initial){
            if (this.store !== store && this.store.autoDestroy){
                this.store.destroy();
            } else {
                this.store.un('beforeload', this.onBeforeLoad, this);
                this.store.un('load', this.onLoad, this);
                this.store.un('exception', this.collapse, this);
            }
            if (!store){
                this.store = null;
            }
        }
        if (store){
            if (!initial) {
                this.lastQuery = null;
            }

            this.store = Ext.StoreMgr.lookup(store);
            this.store.on({
                scope: this,
                beforeload: this.onBeforeLoad,
                load: this.onLoad,
                exception: this.collapse
            });
            
            this.removeCheckboxes();
            this.addCheckboxes();
        }
    },

    // private
    initEvents: function() {
        Ext.ux.form.CheckboxCombo.superclass.initEvents.call(this);

        this.keyNav = new Ext.KeyNav(this.el, {
            'up': function(e){
                this.inKeyMode = true;
                this.selectPrev();
            },
            'down': function(e){
                if (!this.isExpanded()){
                    this.onTriggerClick();
                } else {
                    this.inKeyMode = true;
                    this.selectNext();
                }
            },
            'enter': function(e) {
                this.onListEnter();
            },
            'esc': function(e){
                this.collapse();
            },
            'tab': function(e){
                this.collapse();
                return true;
            },
            scope: this,
            doRelay: function(e, h, hname){
                if (hname == 'down' || this.scope.isExpanded()){
                    // this MUST be called before ComboBox#fireKey()
                    var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
                    if (!Ext.isIE && Ext.EventManager.useKeydown){
                        // call Combo#fireKey() for browsers which use keydown event (except IE)
                        this.scope.fireKey(e);
                    }
                    return relay;
                }
                return true;
            },

            forceKeyDown: true,
            defaultEventAction: 'stopEvent'
        });
        if (!this.enableKeyEvents){
            this.mon(this.el, 'keyup', this.onKeyUp, this);
        }
    },

    // private
    onDestroy: function() {
        this.bindStore(null);
        Ext.destroy(
            this.resizer,
            this.cbgroup,
            this.list
        );
        Ext.destroyMembers(this, 'hiddenField');
        Ext.ux.form.CheckboxCombo.superclass.onDestroy.call(this);
    },

    // private
    fireKey: function(e){
        if (!this.isExpanded()) {
            Ext.ux.form.CheckboxCombo.superclass.fireKey.call(this, e);
        }
    },

    // private
    onResize: function(w, h){
        Ext.ux.form.CheckboxCombo.superclass.onResize.apply(this, arguments);
        if (this.isVisible() && this.list){
            this.doResize(w);
        } else {
            this.bufferSize = w;
        }
    },

    doResize: function(w){
        if (!Ext.isDefined(this.listWidth)){
            var lw = Math.max(w, this.minListWidth);
            this.list.setWidth(lw);
            this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
        }
    },

    // private
    onEnable: function() {
        Ext.ux.form.CheckboxCombo.superclass.onEnable.apply(this, arguments);
        if (this.hiddenField){
            this.hiddenField.disabled = false;
        }
    },

    // private
    onDisable: function() {
        Ext.ux.form.CheckboxCombo.superclass.onDisable.apply(this, arguments);
        if (this.hiddenField){
            this.hiddenField.disabled = true;
        }
    },

    // private
    onBeforeLoad: function() {
        if (!this.hasFocus){
            return;
        }
        
        // Setup a temp var so we can recheck on load
        this.checkboxValues = this.cbgroup.getValue();
        
        this.removeCheckboxes();
    },

    // private
    onLoad: function() {
        if (!this.hasFocus){
            return;
        }
        
        // If there were old checkbox values, make sure they are still checked
        if (this.checkboxValues) {    
            Ext.each(this.checkboxValues, function(v) {
                if (this.valueField){
                    var r = this.findRecord(this.valueField, v.inputValue);
                    if (r) {
                        r.checked = true;
                    }
                }
            }, this);
        }
        
        if (this.store.getCount() > 0 || this.listEmptyText){
            this.addCheckboxes();
            this.expand();
            this.restrictHeight();
        } else {
            this.collapse();
        }
    },

    // inherit docs
    getName: function() {
        var hf = this.hiddenField;
        return hf && hf.name ? hf.name : this.hiddenName || Ext.ux.form.CheckboxCombo.superclass.getName.call(this);
    },
    
    // private
    assertValue: function() {
        var checkboxValues = this.cbgroup.getValue();
        if (checkboxValues) {
            var vals = [];
            Ext.each(checkboxValues, function(cb) {
                vals.push(cb.inputValue);
            });
            this.setValue(vals);
        } else {
            this.clearValue();
        }
    },
    
    /**
     * Sets all checkboxes as checked/unchecked, according to the checked parameter
     * @param {bool} checked 
     * @return {Ext.form.Field} this
     */
    toggleAll: function(checked) {
      var vals = [];
      
      Ext.each(this.store.data.items, function(rec) {
        this.store.getById(rec.id).checked = checked;
        if(checked) {
          vals.push(rec.id);
        }
      });
      
      if(this.cbgroup != undefined) {
        this.removeCheckboxes();
        this.addCheckboxes();
        this.assertValue();
      } else {
        this.setValue(vals);
      }
    },
    
    /**
     * Sets all checkboxes as checked
     * @return {Ext.form.Field} this
     */
    selectAll: function() {
      return this.toggleAll(true);
    },
    
    /**
     * 
     * @return {Ext.form.Field} this
     */
    selectNone: function() {
      return this.toggleAll(false);
    },
    
    /**
     * Sets the specified value into the field. 
     * If this.displayLabelsInInput is set to false, the displayed value is the emptyText.
     * Otherwise, if this.displayLabelsInInput is set to true :
     * If the value finds a match, the corresponding record text
     * will be displayed in the field.  If the value does not match the data value of an existing item,
     * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
     * Otherwise the field will be blank (although the value will still be set).
     * @param {String} value The value to match
     * @return {Ext.form.Field} this
     */
    setValue: function(vals) {
        if (typeof vals == 'string') {
            vals = vals.split(',');
        }
        
        var text = [];

        if(!this.displayLabelsInInput && vals.length > 0) {
          text.push(this.textDisplayedAsValue);
        } else {
          Ext.each(vals, function(v) {
              if (this.valueField){
                  var r = this.findRecord(this.valueField, v);
                  if (r) {
                      text.push(r.data[this.displayField]);
                      r.checked = true;
                  }
              }
          }, this);
        }
        
        if (this.hiddenField) {
            this.hiddenField.value = Ext.value(vals.join(','), '');
        }
        
        this.lastSelectionText = text.join(', ');
        Ext.ux.form.CheckboxCombo.superclass.setValue.call(this, text.join(', '));
        this.value = vals.join(',');
        return this;
    },  
    
    /**
     * Returns the currently selected field value or empty string if no value is set.
     * @return {String} value The selected value
     */
    getValue: function() {
        if (this.valueField){
            return Ext.isDefined(this.value) ? this.value : '';
        } else {
            return Ext.ux.form.CheckboxCombo.superclass.getValue.call(this);
        }
    },
    
    /**
     * Clears any text/value currently set in the field
     */
    clearValue: function() {
        if (this.hiddenField){
            this.hiddenField.value = '';
        }
        this.setRawValue('');
        this.lastSelectionText = '';
        this.applyEmptyText();
        this.value = '';
    },

    // private
    findRecord: function(prop, value){
        var record;
        if (this.store.getCount() > 0) {
            record = this.store.getAt(this.store.findExact(prop, value));
            return (record ? record : false);
        }
    },
    
    // private
    onListMove: function(e, t) {
        this.inKeyMode = false;
    },
    
    // private
    onListOver: function(e, t) {
        var target = e.getTarget('div.x-form-item');
        if (target) {
            target = Ext.get(target);
            target.radioClass('x-combo-selected');
        }
    },
    
    // private
    onListClick: function(e, t) {
        if (Ext.get(e.getTarget()).dom.tagName != 'INPUT' && Ext.get(e.getTarget()).dom.tagName != 'LABEL') {
            var target = e.getTarget('div.x-form-item');
            if (target) {
                target = Ext.get(target);
                var cb = target.child('input');
                cb = Ext.getCmp(cb.id);
                cb.setValue(cb.getValue() ? false : true);

            }
        }
        if (this.updateOnChange) {
            this.assertValue();
        }
    },
    
    // private
    onListEnter: function(e, t) {
        var target = Ext.DomQuery.selectNode('.x-combo-selected', this.list.dom);
        if (target) {
            target = Ext.get(target);
            var cb = target.child('input');
            cb = Ext.getCmp(cb.id);
            cb.setValue(cb.getValue() ? false : true);
        }
    },

    // private
    restrictHeight: function() {
        this.innerList.dom.style.height = '';
        var inner = this.innerList.dom,
            pad = this.list.getFrameWidth('tb') + this.assetHeight,
            h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
            ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
            hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
            space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;

        h = Math.min(h, space, this.maxHeight);

        this.innerList.setHeight(h);
        this.list.beginUpdate();
        this.list.setHeight(h+pad);
        this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
        this.list.endUpdate();
    },

    
    /**
     * Returns true if the dropdown list is expanded, else false.
     */
    isExpanded: function() {
        return this.list && this.list.isVisible();
    },
    
    // private
    selectNext: function() {
        var ct = this.store.getCount();
        if (ct > 0) {
            var el = Ext.DomQuery.selectNode('.x-combo-selected', this.list.dom);
            if (!el) {
                this.innerList.child('.x-form-item').radioClass('x-combo-selected');
            } else {
                Ext.get(el).next().radioClass('x-combo-selected');
            }
        }
    },

    // private
    selectPrev: function() {
        var ct = this.store.getCount();
        if (ct > 0) {
            var el = Ext.DomQuery.selectNode('.x-combo-selected', this.list.dom);
            if (!el) {
                this.innerList.child('.x-form-item').radioClass('x-combo-selected');
            } else {
                Ext.get(el).prev().radioClass('x-combo-selected');
            }
        }
    },

    // private
    validateBlur: function() {
        return !this.list || !this.list.isVisible();
    },

    // private
    beforeBlur: function() {
        this.assertValue();
    },

    // private
    postBlur: function() {
        Ext.ux.form.CheckboxCombo.superclass.postBlur.call(this);
        this.collapse();
        this.inKeyMode = false;
    },
    
    /**
     * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
     */
    collapse: function() {
        if (!this.isExpanded()){
            return;
        }
        this.list.hide();
        Ext.getDoc().un('mousewheel', this.collapseIf, this);
        Ext.getDoc().un('mousedown', this.collapseIf, this);
        this.beforeBlur();
        this.fireEvent('collapse', this);
    },

    // private
    collapseIf: function(e){
        if (!e.within(this.wrap) && !e.within(this.list)){
            this.collapse();
        }
    },
    
    /**
     * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
     */
    expand: function() {
        if (this.isExpanded() || !this.hasFocus){
            return;
        }
        if (this.bufferSize){
            this.doResize(this.bufferSize);
            delete this.bufferSize;
        }
        this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
        this.list.show();
        if (Ext.isGecko2){
            this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
        }
        this.mon(Ext.getDoc(), {
            scope: this,
            mousewheel: this.collapseIf,
            mousedown: this.collapseIf
        });
        this.fireEvent('expand', this);
    },

    removeCheckboxes: function() {
        this.cbgroup.destroy();
        
        /*this.cbgroup.items.each(function(cb) {
            cb.destroy();
        }, this);*/
    },
    
    addCheckboxes: function() {
        this.cbgroup = new Ext.form.CheckboxGroup({
            renderTo: this.innerList,
            columns: 1,
            autoHeight: true,
            border: false,
            items: [{}]
        });
        
        this.cbgroup.items.items[0].destroy();
        
        var col = this.cbgroup.panel.items.get(0);
        
        // Add new checkboxes from store
        Ext.each(this.store.data.items, function(rec) {
            var checkbox = new Ext.form.Checkbox({name: rec.data.id + '-checkbox', boxLabel: rec.data[this.displayField], inputValue: rec.data[this.valueField], checked: (rec.checked ? rec.checked : false)});
            this.cbgroup.items.add(checkbox);
            col.add(checkbox);
        }, this);
        
        this.cbgroup.panel.doLayout();
    },
    
    /**
     * @method onTriggerClick
     * @hide
     */
    // private
    // Implements the default empty TriggerField.onTriggerClick function
    onTriggerClick: function() {
        if (this.readOnly || this.disabled){
            return;
        }
        if (this.isExpanded()){
            this.collapse();
            this.el.focus();
        } else {
            this.onFocus({});
            if (this.triggerAction == 'all') {
                this.store.reload();
            }
            this.expand();
            this.el.focus();
        }
    },
    
    // A renderer for displaying the values in a grid
    gridRenderer: function(value) {
        if (typeof value == 'string') {
            value = value.split(',');
        }
        
        var text = [];
    
        Ext.each(value, function(v) {
            if (this.valueField){
                var r = this.findRecord(this.valueField, v);
                if (r) {
                    text.push(r.data[this.displayField]);
                }
            }
        }, this);
        
        return text.join(', ');
    }
});
Ext.reg('checkboxcombo', Ext.ux.form.CheckboxCombo);
