/**
 * Correção de diversos bugs dos componentes  
 * Ext.form.Checkbox e Ext.form.Radio reportados
 * na secao de bugs do Forum do Ext.
 * 
 * http://extjs.com/forum/showthread.php?t=44603
 *  
 */
Ext.override(Ext.form.Checkbox, {
	onRender: function(ct, position){
		Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
		if(this.inputValue !== undefined){
			this.el.dom.value = this.inputValue;
		}
		//this.el.addClass('x-hidden');
		this.innerWrap = this.el.wrap({
			//tabIndex: this.tabIndex,
			cls: this.baseCls+'-wrap-inner'
		});
		this.wrap = this.innerWrap.wrap({cls: this.baseCls+'-wrap'});
		if(this.boxLabel){
			this.labelEl = this.innerWrap.createChild({
				tag: 'label',
				htmlFor: this.el.id,
				cls: 'x-form-cb-label',
				html: this.boxLabel
			});
		}
		this.imageEl = this.innerWrap.createChild({
			tag: 'img',
			src: Ext.BLANK_IMAGE_URL,
			cls: this.baseCls
		}, this.el);
		if(this.checked){
			this.setValue(true);
		}else{
			this.checked = this.el.dom.checked;
		}
		this.originalValue = this.checked;
	},
	afterRender: function(){
		Ext.form.Checkbox.superclass.afterRender.call(this);
		//this.wrap[this.checked ? 'addClass' : 'removeClass'](this.checkedCls);
		this.imageEl[this.checked ? 'addClass' : 'removeClass'](this.checkedCls);
	},
	initCheckEvents: function(){
		//this.innerWrap.removeAllListeners();
		this.innerWrap.addClassOnOver(this.overCls);
		this.innerWrap.addClassOnClick(this.mouseDownCls);
		//this.innerWrap.on('click', this.onClick, this);
		this.el.on('click', this.onClick, this);
		this.imageEl.on('click', this.onClick, this);
		//this.innerWrap.on('keyup', this.onKeyUp, this);
	},
	onFocus: function(e) {
		Ext.form.Checkbox.superclass.onFocus.call(this, e);
		//this.el.addClass(this.focusCls);
		this.innerWrap.addClass(this.focusCls);
	},
	onBlur: function(e) {
		Ext.form.Checkbox.superclass.onBlur.call(this, e);
		//this.el.removeClass(this.focusCls);
		this.innerWrap.removeClass(this.focusCls);
	},
	onClick: function(e){
		if (Ext.isSafari) {
			this.el.focus();
		}
		if (!this.disabled && !this.readOnly) {
			this.toggleValue();
		}
		//e.stopEvent();
	},
	onEnable: Ext.form.Checkbox.superclass.onEnable,
	onDisable: Ext.form.Checkbox.superclass.onDisable,
	onKeyUp: undefined,
	setValue: function(v) {
		var checked = this.checked;
		this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
		if(this.rendered){
			this.el.dom.checked = this.checked;
			this.el.dom.defaultChecked = this.checked;
			//this.wrap[this.checked ? 'addClass' : 'removeClass'](this.checkedCls);
			this.imageEl[this.checked ? 'addClass' : 'removeClass'](this.checkedCls);
		}
		if(checked != this.checked){
			this.fireEvent("check", this, this.checked);
			if(this.handler){
				this.handler.call(this.scope || this, this, this.checked);
			}
		}
	}
});
Ext.override(Ext.form.Radio, {
	checkedCls: 'x-form-radio-checked'
});

/**
 * Adição do método serialize aos comonentes Store.
 * Esse método retorna um array de records codificados como
 * objetos JSON.
 * Recebe como parametro o valor modifiedOnly, se for true envia somente os registros alterados
 * se for false envia todos os registros. Default: true.
 * 
 * Obs: Até o momento só foi testado com JsonStore.
 */
Ext.override(Ext.data.Store, {
	serialize: function(modifiedOnly){
		var recordsRange;
		var retornar = [];

		if (modifiedOnly == null){
			modifiedOnly = true;
		}

		if (modifiedOnly){
			recordsRange = this.getModifiedRecords();
		} else {
			recordsRange = this.getRange();
		}

		for (var r=0; r<recordsRange.length; r++){
			retornar.push(Ext.util.JSON.encode(recordsRange[r].data));
		}

		return retornar;
	}
});

Ext.override(Ext.form.NumberField, {
       validateValue : function(value){
           if(!Ext.form.NumberField.superclass.validateValue.call(this, value)){
               return false;
           }
           if(value.length < 1){              
               return true;
           }
           value = String(value).replace(this.decimalSeparator, ".");
           if(isNaN(value)){
               this.markInvalid(String.format(this.nanText, String(value).replace(".", ",")));
               return false;
           }
           var num = this.parseValue(value);
           if(num < this.minValue){
               this.markInvalid(String.format(this.minText, String(this.minValue).replace(".", ",")));
               return false;
           }
           if(num > this.maxValue){
               this.markInvalid(String.format(this.maxText, String(this.maxValue).replace(".", ",")));
               return false;
           }
           return true;
       }
 });

Ext.override(Ext.form.BasicForm, {
    /**
     * Ext.form.BasicForm.clearDirty()
     * Restores the dirty status of all fields back to clean
     */
    clearDirty: function() {
        var i, it = this.items.items, l = it.length, c;
        for (i = 0; i < l; i++) {
            c = it[i];
            c.originalValue = String(c.getValue());
        }
    },
    /**
     * Ext.form.BasicForm.findField()
     * Sobrescrito método para buscar também campos radioGroup ou checkGroup
     * 
     * @param id
     * @returns {Object}
     */
    findField : function(id){
        var field = this.items.get(id);
        if(!field){
            this.items.each(function(f){
                if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
                    field = f;
                    return false;
                } else if (f.items) {
                    //do it again for each subitem
                    f.items.each(function(f2){
                        if(f2.isFormField && (f2.dataIndex == id || f2.id == id || f2.getName() == id)){
                            field = f2;
                            return false;
                        }
                    });
                }
            });
        }
        return field || null;
    }
});

/**
 * Ext.form.Field.setFieldLabel(text)
 * Update a form field label text
 * 
 * Ext.form.Field.setHideLabel(mod)
 * Hide or show field label
 * 
 * http://www.sencha.com/forum/showthread.php?16902-change-fieldLabel-of-Ext.form.Field&p=417493#post417493
 * 
 * @param {String} text
 */
Ext.override(Ext.form.Field, {
    setFieldLabel : function(text) {
        if (this.rendered) {
            this.el.up('.x-form-item', 10, true).child('.x-form-item-label').update(text);
        }
        this.fieldLabel = text;
    },
	
	setHideLabel : function(mod){
		mod = (mod)? false : true;
		if (this.rendered) {
			this.el.up('.x-form-item', 10, true).child('.x-form-item-label').setVisible(mod);
        }
	}
});

/**
 * Ext.form.HtmlEditor.onFirstFocus
 * Correção de bug que disparava um erro no Firebug ao dar foco pela primeira vez a um HtmlEditor
 * Ticket #12382
 * 
 * Fonte da correção: http://www.sencha.com/forum/showthread.php?55199-adding-html-editor-dynamically&p=263391#post263391
 */
Ext.override(Ext.form.HtmlEditor, {
    onFirstFocus : function(){
        this.activated = true;
        this.tb.items.each(function(item){
           item.enable();
        });
        if(Ext.isGecko){ 
            this.win.focus();
            var s = this.iframe.contentWindow.getSelection();
                if(!s.focusNode || s.focusNode.nodeType != 3){
                    var r = s.getRangeAt(0);
                    r.selectNodeContents(this.getEditorBody());
                    r.collapse(true);
                    this.deferFocus();
                }
            try{
                this.execCmd('useCSS', true);
                this.execCmd('styleWithCSS', false);
            }catch(e){}
        }
        this.fireEvent('activate', this);
    }  
});

/*
 * (Ext.DatePicker fix: INÍCIO)
 * 
 * Correções para o problema com a seleção da data em datepickers do ExtJs
 * (durante o primeiro mês do DST ("horário de verão") o DatePicker utiliza um
 * dia anterior ao selecionado)
 * 
 * FONTE: http://www.sencha.com/forum/showthread.php?62250-CLOSED-35-3.x-2.x-DatePicker-Picks-wrong-date
 * NOTA 1: Para o Windows XP, poderá ser necessário atualizar os TZ no registro
 *     http://support.microsoft.com/kb/914387
 * Nota 2: O bug foi corrigido no Ext 3.x. Quando o sistema for atualizado, os 
 *     overrides a seguir poderão ser removidos
 */
/**
 * Correção do DatePicker
 * 
 * http://www.sencha.com/forum/showthread.php?62250-CLOSED-35-3.x-2.x-DatePicker-Picks-wrong-date&p=302310#post302310
 */
Ext.override(Ext.DatePicker, {
    // PUBLIC -- to be documented
    // default value used to initialise each date in the DatePicker
    // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
    initHour: 12, // 24-hour format

    // private
    update : function(date, forceRefresh) {
        var vd = this.activeDate;
        this.activeDate = date;
        if (!forceRefresh && vd && this.el) {
            var t = date.getTime();
            if (vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()) {
                this.cells.removeClass("x-date-selected");
                this.cells.each(function(c) {
                    if (c.dom.firstChild.dateValue == t) {
                        c.addClass("x-date-selected");
                        setTimeout(function() {
                            try {
                                c.dom.firstChild.focus();
                            } catch(e) {
                            }
                        }, 50);
                        return false;
                    }
                });
                return;
            }
        }
        var days = date.getDaysInMonth(),
            firstOfMonth = date.getFirstDateOfMonth(),
            startingPos = firstOfMonth.getDay() - this.startDay;

        if (startingPos <= this.startDay) {
            startingPos += 7;
        }
        days += startingPos;

        var pm = date.add("mo", -1),
            prevStart = pm.getDaysInMonth() - startingPos,
            cells = this.cells.elements,
            textEls = this.textNodes,

            // convert everything to numbers so it's fast
            // var day = 86400000; // not in use throughout the update() method
            d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart, this.initHour)), // .clearTime();
            today = new Date().clearTime().getTime(),
            sel = date.clearTime(true).getTime(),
            min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY,
            max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY,
            ddMatch = this.disabledDatesRE,
            ddText = this.disabledDatesText,
            ddays = this.disabledDays ? this.disabledDays.join("") : false,
            ddaysText = this.disabledDaysText,
            format = this.format;

        if (this.showToday) {
            var td = new Date().clearTime();
            var disable = (td < min || td > max ||
                           (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||
                           (ddays && ddays.indexOf(td.getDay()) != -1));

            this.todayBtn.setDisabled(disable);
            this.todayKeyListener[disable ? 'disable' : 'enable']();
        }

        var setCellClass = function(cal, cell) {
            cell.title = "";
            var t = d.clearTime(true).getTime();
            cell.firstChild.dateValue = t;
            if (t == today) {
                cell.className += " x-date-today";
                cell.title = cal.todayText;
            }
            if (t == sel) {
                cell.className += " x-date-selected";
                setTimeout(function() {
                    try {
                        cell.firstChild.focus();
                    } catch(e) {
                    }
                }, 50);
            }
            // disabling
            if (t < min) {
                cell.className = " x-date-disabled";
                cell.title = cal.minText;
                return;
            }
            if (t > max) {
                cell.className = " x-date-disabled";
                cell.title = cal.maxText;
                return;
            }
            if (ddays) {
                if (ddays.indexOf(d.getDay()) != -1) {
                    cell.title = ddaysText;
                    cell.className = " x-date-disabled";
                }
            }
            if (ddMatch && format) {
                var fvalue = d.dateFormat(format);
                if (ddMatch.test(fvalue)) {
                    cell.title = ddText.replace("%0", fvalue);
                    cell.className = " x-date-disabled";
                }
            }
        };

        var i = 0;
        for (; i < startingPos; i++) {
            textEls[i].innerHTML = (++prevStart);
            d.setDate(d.getDate() + 1);
            cells[i].className = "x-date-prevday";
            setCellClass(this, cells[i]);
        }
        for (; i < days; i++) {
            var intDay = i - startingPos + 1;
            textEls[i].innerHTML = (intDay);
            d.setDate(d.getDate() + 1);
            cells[i].className = "x-date-active";
            setCellClass(this, cells[i]);
        }
        var extraDays = 0;
        for (; i < 42; i++) {
            textEls[i].innerHTML = (++extraDays);
            d.setDate(d.getDate() + 1);
            cells[i].className = "x-date-nextday";
            setCellClass(this, cells[i]);
        }

        this.mbtn.setText(this.monthNames[date.getMonth()] + " " + date.getFullYear());

        if (!this.internalRender) {
            var main = this.el.dom.firstChild;
            var w = main.offsetWidth;
            this.el.setWidth(w + this.el.getBorderWidth("lr"));
            Ext.fly(main).setWidth(w);
            this.internalRender = true;
            // opera does not respect the auto grow header center column
            // then, after it gets a width opera refuses to recalculate
            // without a second pass
            if (Ext.isOpera && !this.secondPass) {
                main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth + main.rows[0].cells[2].offsetWidth)) + "px";
                this.secondPass = true;
                this.update.defer(10, this, [date]);
            }
        }
    }
});

/**
 * Correção do DateField
 * 
 * http://www.sencha.com/forum/showthread.php?62250-CLOSED-35-3.x-2.x-DatePicker-Picks-wrong-date&p=302310#post302310
 */
Ext.override(Ext.form.DateField, {
    // PUBLIC -- to be documented
    // in the absence of a time value, a default value of 12 noon will be used
    // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
    initTime: '12', // 24 hour format
    
    // PUBLIC -- to be documented
    initTimeFormat: 'H',

    // PUBLIC -- to be documented
    // Should be explictly stated that this method should be overriden
    // for use in timezones which experience DST
    //
    // note: this turned out to be reaaaaaalllly difficult to customize to
    // fit every situation, so what i've put up here is a real naive implementation which 
    // works for the online examples (disclaimer: i've only tried it on one, but most of the configs are similar).
    // i suggest customizing it so it fits your particular situation. ask if you don't understand what on earth
    // i'm trying to achieve with this function.
    safeParse : function(value, format) {
        if (/[gGhH]/.test(format.replace(/(\\.)/g, ''))) {
            // if parse format contains hour information, no DST adjustment is necessary
            return Date.parseDate(value, format);
        } else {
            // set time to 12 noon, then clear the time
            var date = Date.parseDate(value + ' ' + this.initTime, format + ' ' + this.initTimeFormat);
            return !Ext.isEmpty(date) ? date.clearTime() : false;
        }
    },

    parseDate : function(value) {
        if(!value || Ext.isDate(value)){
            return value;
        }
    
        var v = this.safeParse(value, this.format),
            af = this.altFormats,
            afa = this.altFormatsArray;
        
        if (!v && af) {
            afa = afa || af.split("|");
        
            for (var i = 0, len = afa.length; i < len && !v; i++) {
                v = this.safeParse(value, afa[i]);
            }
        }
        return v;
    },
    
    /*
     * disabledDates fix:
     * http://www.sencha.com/forum/showthread.php?44781-2.2-FIXED-DateField-disabledDates-no-longer-working
     */
    initDisabledDays : function(){
        if(this.disabledDates){
            var dd = this.disabledDates;
            var re = "(?:";
            for(var i = 0; i < dd.length; i++){
                re += this.formatDate(dd[i]);
                if(i != dd.length-1) re += "|";
    }
            this.ddMatch = new RegExp(re + ")");
        }
    }
});

/**
 * Correção do método new Date().clearTime()
 * 
 * http://www.sencha.com/forum/showthread.php?62250-CLOSED-35-3.x-2.x-DatePicker-Picks-wrong-date&p=401259#post401259
 * 
 * @param clone
 * @returns
 */
Date.prototype.clearTime = function(clone) {    
    if (clone) {
        return this.clone().clearTime();
    }     

    // get current date before clearing time
    var d = this.getDate();
    
    // clear time
    this.setHours(0);
    this.setMinutes(0);
    this.setSeconds(0);
    this.setMilliseconds(0);

    if (this.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
        // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the ca
        // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
        // increment hour until cloned date == current date
        for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
        this.setDate(d);
        this.setHours(c.getHours());
    }     

    return this; 
};
/*
 * (Ext.DatePicker fix: FIM)
 */

/**
 * Fix Checkbox/Radio Groups ownerCt. That allows searching by XType.
 * 
 * http://www.sencha.com/forum/showthread.php?71261-FIXED-3.0RC2-RadioGroup-ownerCt-missing
 */
Ext.override(Ext.form.CheckboxGroup, {
    onRender : function(ct, position){
        if(!this.el){
            var panelCfg = {
                cls: this.groupCls,
                layout: 'column',
                border: false,
                renderTo: ct
            };
            var colCfg = {
                defaultType: this.defaultType,
                layout: 'form',
                border: false,
                defaults: {
                    hideLabel: true,
                    anchor: '100%'
                }
            };
            
            if(this.items[0].items){
                
                // The container has standard ColumnLayout configs, so pass them in directly
                
                Ext.apply(panelCfg, {
                    layoutConfig: {columns: this.items.length},
                    defaults: this.defaults,
                    items: this.items
                });
                for(var i=0, len=this.items.length; i<len; i++){
                    Ext.applyIf(this.items[i], colCfg);
                }
                
            }else{
                
                // The container has field item configs, so we have to generate the column
                // panels first then move the items into the columns as needed.
                
                var numCols, cols = [];
                
                if(typeof this.columns == 'string'){ // 'auto' so create a col per item
                    this.columns = this.items.length;
                }
                if(!Ext.isArray(this.columns)){
                    var cs = [];
                    for(var i=0; i<this.columns; i++){
                        cs.push((100/this.columns)*.01); // distribute by even %
                    }
                    this.columns = cs;
                }
                
                numCols = this.columns.length;
                
                // Generate the column configs with the correct width setting
                for(var i=0; i<numCols; i++){
                    var cc = Ext.apply({items:[]}, colCfg);
                    cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
                    if(this.defaults){
                        cc.defaults = Ext.apply(cc.defaults || {}, this.defaults)
                    }
                    cols.push(cc);
                };
                
                // Distribute the original items into the columns
                if(this.vertical){
                    var rows = Math.ceil(this.items.length / numCols), ri = 0;
                    for(var i=0, len=this.items.length; i<len; i++){
                        if(i>0 && i%rows==0){
                            ri++;
                        }
                        if(this.items[i].fieldLabel){
                            this.items[i].hideLabel = false;
                        }
                        cols[ri].items.push(this.items[i]);
                    };
                }else{
                    for(var i=0, len=this.items.length; i<len; i++){
                        var ci = i % numCols;
                        if(this.items[i].fieldLabel){
                            this.items[i].hideLabel = false;
                        }
                        cols[ci].items.push(this.items[i]);
                        
                    };
                }
                
                Ext.apply(panelCfg, {
                    layoutConfig: {columns: numCols},
                    items: cols
                });
            }
            
            this.panel = new Ext.Panel(panelCfg);
            this.panel.ownerCt = this;
            this.el = this.panel.getEl();
            
            if(this.forId && this.itemCls){
                var l = this.el.up(this.itemCls).child('label', true);
                if(l){
                    l.setAttribute('htmlFor', this.forId);
                }
            }
            
            var fields = this.panel.findBy(function(c){
                return c.isFormField;
            }, this);
            
            this.items = new Ext.util.MixedCollection();
            this.items.addAll(fields);
        }
        Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
    }
});

/*
 * As duas correções abaixo são para o uso de "colchetes" no atributo name de radio buttons
 *
 * There is a problem with the regular expression for attribute matching in DomQuery.
 * A possible workaround is to alter the regular expression a bit (by making the closing quote 
 * mandatory after an opening quote) and use quotes in the Ext.form.Radio name (and optionally value) matching.
 * 
 * "Bug in radiogroup when using brackets in name"
 * http://www.sencha.com/forum/showthread.php?44422-FIXED-518-3.x-2.x-Bug-in-radiogroup-when-using-brackets-in-name&p=210602#post210602
 */
Ext.DomQuery.matchers[2] = {
    re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?(["']?)(.*?)\4)?[\]\}])/,
    select: 'n = byAttribute(n, "{2}", "{5}", "{3}", "{1}");'
};
Ext.override(Ext.form.Radio, {
    getGroupValue : function(){
        var c = this.getParent().child('input[name="'+this.el.dom.name+'"]:checked', true);
        return c ? c.value : null;
    },
    toggleValue : function() {
        if(!this.checked){
            var els = this.getParent().select('input[name="'+this.el.dom.name+'"]');
            els.each(function(el){
                if(el.dom.id == this.id){
                    this.setValue(true);
                }else{
                    Ext.getCmp(el.dom.id).setValue(false);
                }
            }, this);
        }
    },
    setValue : function(v){
        if(typeof v=='boolean') {
            Ext.form.Radio.superclass.setValue.call(this, v);
        }else{
            var r = this.getParent().child('input[name="'+this.el.dom.name+'"][value="'+v+'"]', true);
            if(r && !r.checked){
                Ext.getCmp(r.id).toggleValue();
            };
        }
    }
});

/*
 * Alterações para permitir definir um texto diferenciado para o menu de ocultação das colunas.
 * Ela adiciona uma nova config ao ColumnModel: "menuText"
 */
Ext.override(Ext.grid.ColumnModel, {
    /**
     * Get the "menuText" config option
     * @param {Integer} col index
     * @returns {String}
     */
    getColumnMenuText: function(col){
        return this.config[col].menuText;
    }
});
Ext.override(Ext.grid.GridView, {
    /**
     * (Re)Creates the header menu to show/hide columns
     */
    beforeColMenuShow : function(){
        var cm = this.cm,  colCount = cm.getColumnCount();
        this.colMenu.removeAll();
        for(var i = 0; i < colCount; i++){
            if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
                this.colMenu.add(new Ext.menu.CheckItem({
                    id: "col-"+cm.getColumnId(i),
                    text: cm.getColumnMenuText(i) || cm.getColumnHeader(i),
                    checked: !cm.isHidden(i),
                    hideOnClick:false,
                    disabled: cm.config[i].hideable === false
                }));
            }
        }
    }
});

/*
 * Implementação de Range.prototype.createContextualFragment(), não presente no IE9
 * http://stackoverflow.com/questions/5375616/extjs4-ie9-object-doesnt-support-property-or-method-createcontextualfragme
 * 
 * TODO: provavelmente este trecho poderá ser removido futuramente
 */
if (typeof Range !== "undefined" && typeof Range.prototype.createContextualFragment == "undefined") {
    Range.prototype.createContextualFragment = function(html) {
        var doc = this.startContainer.ownerDocument;
        var container = doc.createElement("div");
        container.innerHTML = html;
        var frag = doc.createDocumentFragment(), n;
        while ( (n = container.firstChild) ) {
            frag.appendChild(n);
        }
        return frag;
    };
}
