/*
 *  Copyright 2013 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.
 */

/**
 * This class will handle the drag and drop process of tabs between zones
 * @private
 */
Ext.define("Ametys.ui.tool.layout.ZonedTabsToolsLayout.ZonedTabsDD", 
    {
        extend: "Ext.dd.DDProxy",
        
        /**
         * @property {String} visibleZoneCls The CSS classname for the rendering of a drop zone
         * @readonly
         * @private
         */
        visibleZoneCls : 'a-tool-layer-zoned-dragndrop-marker',
        
        /**
         * @property {String} effectiveZoneCls The CSS classname for the effective location of a drop zone
         * @readonly
         * @private
         */
        effectiveZoneCls: 'a-tool-layer-zoned-dragndrop-effective',
        
        statics: 
        {
            /**
             * @readonly
             * @private
             * @property {Number} __FLOATINGZONES_RATIO The size to display for slided in lateral zones (ratio of central zone).
             */
            _FLOATINGZONES_RATIO: 0.3,
            /**
             * @readonly
             * @private
             * @property {Number} ___FLOATINGZONES_DROPRATIO The size to detect drop for slided in lateral zones (ratio of central zone).
             */
            _FLOATINGZONES_DROPRATIO: 0.25,
            /**
             * @readonly
             * @private
             * @property {Number} __HEADER_HEIGHT The size in pixels of a horizontal header of centrals zones to avoid overlap
             */
            _HEADER_HEIGHT: 40
        },
    
        /**
         * @property {String} _originalGroup The drag'n'drop group name used at creation time
         * @private
         */
    
        /**
         * @property {HTMLElement[]} _ddzones The array of html element used to display drop targets and other elements for the drop targets themselves.
         * @private
         */
        _ddzones: [],
        /**
         * @property {Ext.dd.DDTarget[]} __ddobjects The drop targets associated to #_ddzones (for destroy purposes).
         * @private
         */
        _ddobjects: [],
        
        /**
         * @property {Ext.dom.Element} el The dom element to drag
         * @private
         */
        /**
         * @property {Array} originalXY The x,y coordinates of the #el before the drag has started. Used to cancel the drag.
         * @private
         */     
         
        /**
         * @cfg {String} toolId The identifier of the Ametys.ui.tool.ToolPanel associated to the drag process 
         */      
        
        /**
         * @cfg {Ametys.ui.tool.ToolsLayout} toolsLayout (required) The current tools layout 
         */
    
        constructor: function(id, sGroup, config)
        {
            this._originalGroup = sGroup;
            this.toolsLayout = config.toolsLayout;
            
            this.callParent(arguments);
        },
        
        b4StartDrag : function(x, y) 
        {
            this.callParent(arguments);
            
            // Cache the drag element
            if (!this.el) 
            {
                this.el = Ext.get(this.getEl());
            }
         
            //Cache the original XY Coordinates of the element, we'll use this later.
            this.originalXY = this.el.getXY();
            
            // Which panels are visible? where are they?
            var visible = {}; // {String} location <-> {Boolean} isVisible
            var position = {}; // {String} location <-> {Boolean} getPosition (x,y position)
            var size = {}; // {String} location <-> {Boolean} getSize (width,height size)
            var dropOffset = {}; // {String} location <-> {Number[2]} the drop zone position relatively to the zone
            var dropSize = {}; // {String} location <-> {Object} the width and height of the drop zone inside the zone

            this.toolsLayout._slideInZone();
            
            for (var loc in this.toolsLayout._panelHierarchy)
            {
                var tabPanel = this.toolsLayout._panelHierarchy[loc];
            
                visible[loc] = tabPanel.isVisible();
                
                var p = tabPanel;
                if (!visible[loc])
                {
                    p = tabPanel.ownerCt || tabPanel.floatingOwnerCt;
                    while (!p.isVisible())
                    {
                        p = p.ownerCt;
                    }
                }
                
                position[loc] =  p.getPosition();
                size[loc] = p.getSize();
                
                if (visible[loc])
                {
                    // Add spliter to zone
                    var associatedSplitter = null;
                    if (loc == 'l' || loc == 't' || loc == 'cl')
                    {
                        associatedSplitter = tabPanel.nextSibling();
                    }
                    else if (loc == 'r' || loc == 'b')
                    {
                        associatedSplitter = tabPanel.previousSibling();
                    }
                    
                    if (associatedSplitter && associatedSplitter.isVisible()) // spliter can be invisible for a visible zone with "cl" if "cr" is not visible
                    {
                        spliterPosition = associatedSplitter.getPosition();
                        spliterSize = associatedSplitter.getSize();
                        
                        size[loc] = { 
                            width: Math.max(position[loc][0] + size[loc].width, spliterPosition[0] + spliterSize.width) - Math.min(position[loc][0], spliterPosition[0]),
                            height: Math.max(position[loc][1] + size[loc].height, spliterPosition[1] + spliterSize.height) - Math.min(position[loc][1], spliterPosition[1])
                        };
                        
                        position[loc] = [ 
                            Math.min(position[loc][0], spliterPosition[0]), 
                            Math.min(position[loc][1], spliterPosition[1]) 
                        ];
                    }
                }

                dropOffset[loc] = [0, 0];
                dropSize[loc] = {width: size[loc].width, height: size[loc].height};
            }
            
            // Handle top zone
            // When top zone is visible, nothing to do
            // But when top zone is invisible
            if (!visible['t'])
            {
                // look and drop width are set
                size['t'].height = Math.round(size['cl'].height * this.self._FLOATINGZONES_RATIO);
                dropSize['t'].height = 0; // we be set to max possible value later
                
                // drop position is negativized
                dropOffset['t'][1] -= dropSize['t'].height;
                
                // no impact on middle dropzones height
            }
            
            // Handle bottom zone
            // When bottom zone is visible, nothing to do
            // But when bottom zone is invisible
            if (!visible['b'])
            {
                var futureSize = Math.round(size['cl'].height * this.self._FLOATINGZONES_RATIO);
                var futureDropSize = Math.round(size['cl'].height * this.self._FLOATINGZONES_DROPRATIO);
                
                // drop vposition is set for a bottom align
                position['b'][1] += size['b'].height - futureSize;
                dropOffset['b'][1] += futureSize - futureDropSize;
                
                // look and drop height are set
                size['b'].height = futureSize;
                dropSize['b'].height = futureDropSize;

                // vmiddle dropzones height are reduced
                for (var loc in {"cl": null, "cr": null, "l": null, "r": null})
                {
                    dropSize[loc].height -= dropSize['b'].height;
                }
            }       
            
            // Handle left/right zone
            // When visible nothing to do (invisible bottom part overlap is already handled)
            // But when invisible
            for (var loc in {"l": null, "r": null})
            {
                if (!visible[loc])
                {
                    // on the right, right align the drop zone
                    if (loc == "r") 
                    {
                        position[loc][0] += Math.round(size[loc].width - size['c' + loc].width * this.self._FLOATINGZONES_RATIO);
                        dropOffset[loc][0] = Math.round(size['c' + loc].width * this.self._FLOATINGZONES_RATIO - size['c' + loc].width * this.self._FLOATINGZONES_DROPRATIO);
                    }
                    
                    // look and drop width are set
                    size[loc].width = Math.round(size['c' + loc].width * this.self._FLOATINGZONES_RATIO);
                    dropSize[loc].width = Math.round(size['c' + loc].width * this.self._FLOATINGZONES_DROPRATIO);
                    
                    // RUNTIME-1344 
                    dropSize[loc].height -= this.self._HEADER_HEIGHT;
                    dropOffset[loc][1] += this.self._HEADER_HEIGHT;
                }
            }       
            
            var zones = {"cl": null, "cr": null, "l": null, "r": null, "t": null, "b": null};
            
            // Handle central zones
            var flexCL = this.toolsLayout._panelHierarchy['cl'].flex || this.toolsLayout._panelHierarchy['cr'].flex || 1;
            var flexCR = this.toolsLayout._panelHierarchy['cr'].flex ||this.toolsLayout._panelHierarchy['cl'].flex || 1;
            
            if (visible['cl'] && !visible['cr'])
            {
                if (!visible['l'])
                {
                    // space for left zone
                    // RUNTIME-1344 dropSize['cl'].width -= dropSize['l'].width;
                    // RUNTIME-1344 dropOffset['cl'][0] += dropSize['l'].width;
                }
                
                if (!visible['r'])
                {
                    // space for right zone
                    // RUNTIME-1344 dropSize['cl'].width -= dropSize['r'].width;
                }
                
                // space for cr zone
                var centralWidth = dropSize['cl'].width; 
                dropSize['cl'].width = Math.round(0.66 * centralWidth);
                
                size['cr'].width = Math.round(size['cl'].width * flexCR / (flexCL + flexCR));
                position['cr'][0] += size['cl'].width - size['cr'].width;
                dropSize['cr'].width = centralWidth - dropSize['cl'].width;
                dropOffset['cr'][0] = size['cr'].width - dropSize['cr'].width;
                if (!visible['r'])
                {
                    // RUNTIME-1344 dropOffset['cr'][0] -= dropSize['r'].width;
                }
            }
            else if (!visible['cl'] && visible['cr'])
            {
                if (!visible['r'])
                {
                    // space for right zone
                    // RUNTIME-1344 dropSize['cr'].width -= dropSize['r'].width;
                }
                
                if (!visible['l'])
                {
                    // space for left zone
                    // RUNTIME-1344 dropSize['cr'].width -= dropSize['l'].width;
                    // RUNTIME-1344 dropOffset['cr'][0] += dropSize['l'].width;
                }
                
                // space for cl zone
                var centralWidth = dropSize['cr'].width; 
                dropSize['cr'].width = Math.round(0.66 * centralWidth);
                dropOffset['cr'][0] += centralWidth - dropSize['cr'].width;
                
                size['cl'].width = Math.round(size['cr'].width * flexCL / (flexCL + flexCR));
                dropSize['cl'].width = centralWidth - dropSize['cr'].width;
                if (!visible['l'])
                {
                    // RUNTIME-1344 dropOffset['cl'][0] += dropSize['l'].width;
                }
            }
            else if (visible['cl'] && visible['cr'])
            {
                if (!visible['l'])
                {
                    // RUNTIME-1344 dropOffset['cl'][0] += dropSize['l'].width;
                    // RUNTIME-1344 dropSize['cl'].width -= dropSize['l'].width;
                }
                
                if (!visible['r'])
                {
                    // RUNTIME-1344 dropSize['cr'].width -= dropSize['r'].width;
                }
            }
            else if (!visible['cl'] && !visible['cr'])
            {
                if (!visible['l'])
                {
                    // RUNTIME-1344 dropOffset['cl'][0] += dropSize['l'].width;
                    // RUNTIME-1344 dropSize['cl'].width -= dropSize['l'].width;
                }
                if (!visible['r'])
                {
                    // RUNTIME-1344 dropSize['cl'].width -= dropSize['r'].width;
                }
                delete zones.cr;
            }
            
            // Now, due to borders in the theme and due to splitters, we may have a few pixels between zones
            // Let's remove it
            
            // Vertically middle zones must stick to top and bottom zones
            for (var loc in {"l": null, "r": null, "cl": null, "cr": null})
            {
                if (loc == 'cl' || loc == 'cr' || visible[loc]) // left and right zones, when hidden are starting after the central header
                {
                    var topDiff = (position[loc][1] + dropOffset[loc][1]) - (position['t'][1] + dropOffset['t'][1] + dropSize['t'].height);
                    dropSize[loc].height += topDiff;
                    dropOffset[loc][1] -= topDiff;
                }
                
                var bottomDiff = (position['b'][1] + dropOffset['b'][1]) - (position[loc][1] + dropOffset[loc][1] + dropSize[loc].height);
                dropSize[loc].height += bottomDiff;
            }
            
            // Horizontally middle zones must stick to left and right zones : issue with splitters when lateral zones are visible
            if (visible['l'])
            {
                var leftDiff = (position['cl'][0] + dropOffset['cl'][0]) - (position['l'][0] + dropOffset['l'][0] + dropSize['l'].width);
                dropOffset['cl'][0] -= leftDiff;
                dropSize['cl'].width += leftDiff;
            }
            if (visible['r'])
            {
                if (zones.cr !== undefined)
                {
                    var rightDiff = (position['r'][0] + dropOffset['r'][0]) - (position['cr'][0] + dropOffset['cr'][0] + dropSize['cr'].width);
                    dropSize['cr'].width += rightDiff;
                }
                else
                {
                    var rightDiff = (position['r'][0] + dropOffset['r'][0]) - (position['cl'][0] + dropOffset['cl'][0] + dropSize['cl'].width);
                    dropSize['cl'].width += rightDiff;
                }
            }

            var toStickTo = Ext.getBody().getRegion();
            // Top zone must stick to the top
            var topDiff = position['t'][1] + dropOffset['t'][1] - toStickTo.top;
            dropOffset['t'][1] -= topDiff;
            dropSize['t'].height += topDiff;
            // Bottom zone must stick to the bottom
            var bottomDiff = toStickTo.bottom - (position['b'][1] + dropOffset['b'][1] + dropSize['b'].height);
            dropSize['b'].height += bottomDiff;
            // Left zone must stick to the left
            var leftDiff = position['l'][0] + dropOffset['l'][0] - toStickTo.left;
            dropOffset['l'][0] -= leftDiff;
            dropSize['l'].width += leftDiff;
            // If left zone is not visible, cl zone must stick to the left too
            if (!visible['l'])
            {
                var leftDiff = position['cl'][0] + dropOffset['cl'][0] - toStickTo.left;
                dropOffset['cl'][0] -= leftDiff;
                dropSize['cl'].width += leftDiff;
            }
            // Right zone must stick to the right
            var rightDiff = toStickTo.right - (position['r'][0] + dropOffset['r'][0] + dropSize['r'].width);
            dropSize['r'].width += rightDiff;
            // If right zone is not visible, cr zone must stick to the right too
            if (!visible['r'])
            {
                var rightDiff = toStickTo.right - (position['cr'][0] + dropOffset['cr'][0] + dropSize['cr'].width);
                dropSize['cr'].width += rightDiff;
            }
            
            // Draw now
            for (var loc in zones)
            {
                var div = document.createElement("div");
                div.id = Ext.id();
                div.className = this.visibleZoneCls;
                div.style.top = position[loc][1] + "px";
                div.style.left = position[loc][0] + "px";
                div.style.width = size[loc].width + "px";
                div.style.height = size[loc].height + "px";
                document.body.appendChild(div);
                this._ddzones.push(div);

                var idiv = document.createElement("div");
                idiv.location = loc;
                idiv.displaydiv = div.id;
                idiv.className = this.effectiveZoneCls;
                idiv.style.top = (position[loc][1] + dropOffset[loc][1]) + "px";
                idiv.style.left = (position[loc][0] + dropOffset[loc][0]) + "px";
                idiv.style.width = dropSize[loc].width + "px";
                idiv.style.height = dropSize[loc].height + "px";
                document.body.appendChild(idiv);
                this._ddzones.push(idiv);
                
                this._ddobjects.push(Ext.create("Ext.dd.DDTarget", idiv, this._originalGroup));         
            }
        },
        
        onDragDrop: function(evtObj, targetElId) 
        {
            // Wrap the drop target element with Ext.Element
            var dropEl = Ext.get(targetElId);
         
            // Perform the node move only if the drag element's 
            // parent is not the same as the drop target
            if (this.el.dom.parentNode.id != targetElId) 
            {
                // Move the element
                var uiToolId = this.config.toolId;
                
                
                var t = Ext.get(targetElId);
                var newlocation = t.dom.location;
                
                // Defering the moves because the tab button have to be released before it is destroyed
                Ext.defer(this.toolsLayout.moveTool, 1, this.toolsLayout, [Ext.getCmp(uiToolId), newlocation]);

                // Remove the drag invitation
                this.onDragOut(evtObj, targetElId);
            }
            else {
                // This was an invalid drop, initiate a repair
                this.onInvalidDrop();
            }
        },
        
        onDragEnter : function(evtObj, targetElId) 
        {
            // Colorize the drag target
            Ext.get(Ext.get(targetElId).dom.displaydiv).addCls('dragover');
        },
        
        onInvalidDrop : function() 
        {
            // Set a flag to invoke the animated repair
            this.invalidDrop = true;
        },
        
        b4EndDrag: function(e) 
        {
            if (this.invalidDrop !== true) 
            {
                this.callParent(arguments);
            }
            
            for (var i = 0; i < this._ddzones.length; i++)
            {
                Ext.removeNode(this._ddzones[i]);
            }
            this._ddzones = [];
            
            for (var i = 0; i < this._ddobjects.length; i++)
            {
                Ext.destroy(this._ddobjects[i]);
            }
            this._ddobjects = [];
        },
        
        endDrag : function(e) 
        {
            // Invoke the animation if the invalidDrop flag is set to true
            if (this.invalidDrop === true) {
                // Remove the drop invitation
                this.el.removeCls('dropOK');
                
                // Create the animation configuration object
                var animCfgObj = {
                    easing   : 'easeIn',
                    //duration : 1,
                    scope    : this,
                    callback : function() {
                        // Remove the position attribute
                        Ext.fly(this.getDragEl()).hide();
                    }
                };
         
                // Apply the repair animation
                Ext.fly(this.getDragEl()).setXY(this.originalXY, animCfgObj);
                delete this.invalidDrop;
            }
        },
        
        onDragOut: function(evtObj, targetElId) 
        {
            Ext.get(Ext.get(targetElId).dom.displaydiv).removeCls('dragover');
        }
    }
);