/*
 *  Copyright 2017 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

// ------------------------------
// Here are ExtJS bug fixes
// ------------------------------
(function()
{
    Ext.override(Ext.layout.ContextItem, {
        // Fix CMS-5908 http://www.sencha.com/forum/showthread.php?291412-Error-after-upgrade-to-ExtJS-4.2.3
        init: function (full, options) {
            var me = this;
            
            var protection = false;
            if (me.ownerLayout && !target.ownerLayout.isItemBoxParent)
            {
                target.ownerLayout.isItemBoxParent = function() { return false; };
                protection = true;
            }
            
            var returnValue = this.callParent(arguments);
            
            if (protection)
            {
                delete target.ownerLayout.isItemBoxParent;
            }
            
            return returnValue;
        }
    });
    
    Ext.override(Ext.menu.Menu, {
        initComponent: function()
        {
            this.callParent(arguments);
            
            this.on('resize', this._onResize, this);
        },
        
        // Fix for CMS-5997 http://www.sencha.com/forum/showthread.php?297558-ExtJs-4.2.3-Adding-items-to-an-opened-menu-on-a-floating-parent&p=1086597#post1086597
        _onResize: function(menu, width, height, oldWidth, oldHeight, eOpts)
        {
            if (this.isVisible() /*&& oldHeight != null && oldWidth != null */&& this.floatParent && !this._doingShowBy)
            {
                this._doingShowBy = true; // let's avoid infinite showBy -> needResize -> showBy -> ...
                this.showBy(this.ownerCmp, this.ownerCmp.menuAlign);
                this._doingShowBy = false;
            }
        }
    });
   
    Ext.override(Ext.view.DropZone, {
        // Fix for CMS-6262 https://www.sencha.com/forum/showthread.php?301552-ExtJS-4.2.3-Drag-n-drop-in-a-grid-and-invalid-zone.&p=1101961#post1101961
        containsRecordAtOffset: function(records, record, offset) 
        {
            if (!record) {
                return false;
            }
            
            var view = this.view,
                recordIndex = view.indexOf(record),
                nodeBefore = view.getNode(recordIndex + offset, true),
                recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
    
            var containsRecordAtOffset = recordBefore && Ext.Array.contains(records, recordBefore);
            if (!containsRecordAtOffset)
            {
                return false;
            }
            else if (record.store.getGroupField() != null && Ext.Array.findBy(this.view.features, function(item) { return item.ftype == "grouping" }) != null)
            {
                // using groups, we need to ignore items from different groups 
                var groups = [];
                for (var i = 0; i < records.length; i++)
                {
                    groups.push(records[i].get(record.store.getGroupField()));
                }
                
                var targetGroup = record.get(record.store.getGroupField());
                
                return Ext.Array.contains(groups, targetGroup);
            }
            else
            {
                return true;            
            }
        }
    });
    
    // Fix for CMS-6366 https://www.sencha.com/forum/showthread.php?304867-D-n-D-over-an-IFrame-issue
    if (Ext.ux && Ext.ux.IFrame)
    {
        Ext.override(Ext.dd.DragDropManager, {
            /**
             * @private
             * @member Ext.dd.DragDropManager
             * @method _onAll
             * @ametys
             * @since Ametys Runtime 4.1
             * Execute action on all that need to be protected
             * @param {Function} action The action to do
             * @param {Ext.Component} action.component The component to act on
             */
            _onAll: function(action)
            {
                Ext.ComponentManager.each(function(key, component) {
                    if (component.isXType('uxiframe') || component.isXType('richtextfield'))
                    {
                        action(component);
                    }
                });
            },
            
            handleMouseDown: function(e, oDD)
            {
                this.callParent(arguments);
                
                // Find all iframes to "protect them"
                this._onAll(function (component) {
                    component = component.bodyEl ? /* richtext or code, need to apply on underlying element, if not a UI issue will appear */ component.bodyEl : /* iframe */component;
                    
                    component.addCls("iframe-protected")
                    component.mask(); 
                });
            },
            
            handleMouseUp: function(e) 
            {
                this.callParent(arguments);
                
                // Find all iframes to "unprotect them"
                this._onAll(function (component) {
                    component = component.bodyEl ? /* richtext or code, need to apply on underlying element, if not a UI issue will appear */ component.bodyEl : /* iframe */component;
                    
                    component.removeCls("iframe-protected")
                    component.unmask(); 
                });
            }
        }); 
        
        Ext.override(Ext.ux.IFrame, {
            // Fix for RUNTIME-3116
            // Loading url while iframe is not rendered yet
            load: function(src)
            {
                var me = this;
                if (me.rendered)
                {
                    me.callParent(arguments);
                }
                else
                {
                    // We will load when the rendering will be done
                    var args = Array.from(arguments);
                    me.on({'render': { fn: function() { me.load.call(me, args); }, scope: me, single: true }});
                }
            }
        });
    }
    
    Ext.override(Ext.data.Model, {
        privates: {
            statics: {
                // Fix for https://issues.ametys.org/browse/CMS-6363
                // Actually, this enables to specify a convert or calculate function for an id field in a Ext.data.Model (which does not work, is it a bug ?)
                // See https://www.sencha.com/forum/showthread.php?292044-Ext.data.Field.convert%28%29-not-called-for-idField-if-only-calculated
                initFields: function (data, cls, proto) {
                    var me = this,
                        idField;
    
                    me.callParent(arguments);
    
                    idField = proto.idField;
                    idField.defaultValue = (idField.convert) ? undefined : null; // defaultValue must be undefined instead of null if a convert function is specified
                }
            }
        },
        
        inheritableStatics: {
            // Fix for https://issues.ametys.org/browse/CMS-8330
            // When updating model and reconfiguring columns, the new columns sometimes did not have their value set (cache on fieldExtractors not cleared)
            replaceFields: function(newFields, removeFields) {
                var me = this;
                me.callParent(arguments);
                if (me.fieldExtractors)
                {
                    delete me.fieldExtractors[me.getProxy().getReader().$className];
                }
            }
        }
    });
    
    Ext.override(Ext.form.field.Base, {
        // Fix for https://issues.ametys.org/browse/RUNTIME-1858
        // See https://www.sencha.com/forum/showthread.php?311209-Autocomplete-with-Chrome&p=1136279#post1136279
        getSubTplMarkup: function(fieldData)
        {
            var value = this.callParent(arguments);
            if (Ext.isChrome && fieldData.$comp && fieldData.$comp.inputType == 'password')
            {
                value = value.replace('autocomplete="off"', 'autocomplete="new-password"');
            }
            return value;
        }
    });

    Ext.override(Ext.form.field.Date, {
        // Fix for https://issues.ametys.org/browse/RUNTIME-2658
        formatText: null
    });
    Ext.override(Ext.form.field.Time, {
        // Fix for https://issues.ametys.org/browse/RUNTIME-2658
        formatText: null
    });

    Ext.override(Ext.form.field.ComboBox, {
        // Fix for https://issues.ametys.org/browse/CMS-5934
        // https://www.sencha.com/forum/showthread.php?339854-ExtJS-6-2-Filtering-a-combox-tagfield-and-backspace&p=1179339#post1179339
        
        // Also fix for https://issues.ametys.org/browse/CMS-8760 [Widget] Typing a comma in the select-referencetable-content widget
        // as we still want to search the entire user input if it contains the delimiter
        
        /**
         * @private
         * @member Ext.form.field.ComboBox
         * @property {String} _lastRawValue The last raw input value
         * @since Ametys Runtime 4.0
         * @ametys
         */
        _lastRawValue: null,
        
        doRawQuery: function() {
            var me = this,
                rawValue = me.inputEl.dom.value;
    
            // Use final bit after comma as query value if multiselecting (Ametys edit: no !!)
//            if (me.multiSelect) {
//                rawValue = rawValue.split(me.delimiter).pop();
//            }

            // Here is the fix
            if (Ext.isString(me._lastRawValue) && Ext.isString(rawValue) && rawValue.length < me.minChars && me.minChars <= me._lastRawValue.length)
            {
                // last value is longer than current, so the user removed some characters in the query (by pressing BACKSPACE for instance...)
                // and current value is shorter than the threshold (me.minChars)
                // and last value is equal or greater than the threshold
                // So force to query in order to always have the same results with the same inputs
                me.doQuery("", true, true);
            }
            else
            {
                me.doQuery(rawValue, false, true);
            }
            
            me._lastRawValue = rawValue;
        }
    });
    
    // Fix for RUNTIME-2575 https://www.sencha.com/forum/showthread.php?454691-In-Ext-data-Store-the-remove-method-is-not-ok-with-doc
    Ext.override(Ext.data.Store, {
        remove: function(records, isMove, silent) {
            if (Ext.isNumber(records))
            {
                records = [records];
            }
            return this.callParent(arguments);
        }
    });

    // Fix for CTREE-19 https://www.sencha.com/forum/showthread.php?366515-Untranslated-labels-in-ExtJS-6-5-1&p=1210058#post1210058
    Ext.override(Ext.tree.plugin.TreeViewDragDrop, {
        dragText: "{{i18n PLUGINS_CORE_UI_TREEDRAGNDROP_LABEL}}"
    });
    // Fix for RUNTIME-1640 https://www.sencha.com/forum/showthread.php?366515-Untranslated-labels-in-ExtJS-6-5-1&p=1210058#post1210058
    Ext.override(Ext.panel.Panel, {
        collapseToolText: "{{i18n PLUGINS_CORE_UI_PANEL_COLLAPSE}}", 
        expandToolText: "{{i18n PLUGINS_CORE_UI_PANEL_EXPAND}}"
    });
    // Fix for  CMS-8635
    Ext.override(Ext.tree.Panel, {
        ensureVisible: function()
        {
            if (this.getView().getNodeContainer())
            {
                this.callParent(arguments);
            }
            else
            {
                this.getLogger().warn("Avoid a UI crash by discarding Ext.tree.Panel#ensureVisible on a semi rendered tree view");
            }
        }
    });

    // Fix for CMS-8958
    Ext.override(Ext.view.BoundList, {
        onEndUpdate: function() {
            var me = this;
    
            if (me.updateSuspendCounter) {
                --me.updateSuspendCounter;
                
                Ext.resumeLayouts(true); // The fix is putting this line inside the if instead of after
            }
            else
            {
                this.getLogger().warn("resuming an unsuspended layout");
            }
            
            if (me.refreshSizePending) {
                me.refreshSize(true);
                me.refreshSizePending = false;
            }
        }
    });
    
    // Fix for FRONTEDIT-100 (already fixed in extjs 6.5.3)
    Ext.override(Ext.util.Positionable, {
        getAlignToRegion: function(alignToEl, posSpec, offset, minHeight) {
                var me = this,
                    inside,
                    newRegion;
        
                alignToEl = Ext.fly(alignToEl.el || alignToEl);
        
                if (!alignToEl || !alignToEl.dom) {
                    //<debug>
                    Ext.raise({
                        sourceClass: 'Ext.util.Positionable',
                        sourceMethod: 'getAlignToXY',
                        msg: 'Attempted to align an element that doesn\'t exist'
                    });
                    //</debug>
                }
        
                posSpec = me.convertPositionSpec(posSpec);
        
                // If position spec ended with a "?" or "!", then constraining is necessary
                if (posSpec.constrain) {
                    // Constrain to the correct enclosing object:
                    // If the assertive form was used (like "tl-bl!"), constrain to the alignToEl.
                    if (posSpec.constrain === '!') {
                        inside = alignToEl;
                    }
                    else {
                        // Otherwise, attempt to use the constrainTo property.
                        // Otherwise, if we are a Component, there will be a container property.
                        // Otherwise, use this Positionable's element's parent node.
                        inside = me.constrainTo || me.container || me.el.parent();
                    }
                    
                    inside = Ext.fly(inside.el || inside).getConstrainRegion();
                }

                // Back from extjs 6.5.3
                if (alignToEl === Ext.getBody()) {
                    bodyScroll = alignToEl.getScroll();
        
                    offset = [bodyScroll.left, bodyScroll.top];
                }
                
                newRegion = me.getRegion().alignTo({
                    target: alignToEl.getRegion(),
                    inside: inside,
                    minHeight: minHeight,
                    offset: offset,
                    align: posSpec,
                    axisLock: true
                });
                
                return newRegion;
            }
    });
    Ext.override(Ext.window.Window, {
        getAlignToRegion: function(alignToEl, posSpec, offset, minHeight) {
                var me = this,
                    inside,
                    newRegion;
        
                alignToEl = Ext.fly(alignToEl.el || alignToEl);
        
                if (!alignToEl || !alignToEl.dom) {
                    //<debug>
                    Ext.raise({
                        sourceClass: 'Ext.util.Positionable',
                        sourceMethod: 'getAlignToXY',
                        msg: 'Attempted to align an element that doesn\'t exist'
                    });
                    //</debug>
                }
        
                posSpec = me.convertPositionSpec(posSpec);
        
                // If position spec ended with a "?" or "!", then constraining is necessary
                if (posSpec.constrain) {
                    // Constrain to the correct enclosing object:
                    // If the assertive form was used (like "tl-bl!"), constrain to the alignToEl.
                    if (posSpec.constrain === '!') {
                        inside = alignToEl;
                    }
                    else {
                        // Otherwise, attempt to use the constrainTo property.
                        // Otherwise, if we are a Component, there will be a container property.
                        // Otherwise, use this Positionable's element's parent node.
                        inside = me.constrainTo || me.container || me.el.parent();
                    }
                    
                    inside = Ext.fly(inside.el || inside).getConstrainRegion();
                }

                // Back from extjs 6.5.3
                if (alignToEl === Ext.getBody()) {
                    bodyScroll = alignToEl.getScroll();
        
                    offset = [bodyScroll.left, bodyScroll.top];
                }
                
                newRegion = me.getRegion().alignTo({
                    target: alignToEl.getRegion(),
                    inside: inside,
                    minHeight: minHeight,
                    offset: offset,
                    align: posSpec,
                    axisLock: true
                });
                
                return newRegion;
            }
    });
    
        // Fix for RUNTIME-3119
    Ext.override(Ext.Component, {
        mask: function()
        {
            if (this.rendered)
            {
                this.callParent(arguments);
            }
            else
            {
                this._shouldBeMasked = Ext.Array.from(arguments);
            }
        },
        unmask: function()
        {
            if (this.rendered)
            {
                this.callParent(arguments);
            }
            else
            {
                this._shouldBeMasked = null;
            }
        },
        afterRender: function()
        {
            this.callParent(arguments);
            if (this._shouldBeMasked)
            {
                this.mask.call(this, this._shouldBeMasked);
            }
        },
        destroy: function()
        {
            this._shouldBeMasked = null;
            this.callParent(arguments);
        }
    });

    /**
     * @member Ext.app.ViewController
     * @method afterRender
     * After render
     */
    
    /**
     * @member Ext.util.Floating
     * @event tofront
     * When bring to front
     */
    
    /**
     * @member Ext.data.proxy.Server
     * @event beginprocessresponse
     * When starting to process answer
     * @param {Object} response The response
     * @param {Object} operation The running operation
     */
    /**
     * @member Ext.data.proxy.Server
     * @event endprocessresponse
     * When starting to process answer
     * @param {Object} response The response
     * @param {Object} operation The running operation
     */
    /**
     * @member Ext.panel.Table
     * @event viewcreated
     * When a table view was created
     * @param {Ext.panel.Table} panel The view owner
     * @param {Ext.view.Table} view The view
     */
    /**
     * @member Ext.panel.Panel
     * @event beginfloat
     * When a collasped panel is starting to float
     * @param {Ext.panel.Panel} panel The panel
     */
    /**
     * @member Ext.panel.Panel
     * @event endfloat
     * When a collasped panel is stopping to float
     * @param {Ext.panel.Panel} panel The panel
     */
})();