
Ext.ux.WindowBlind = function(el, win, config) {
    Ext.apply(this, config ? config : {} , {
        minButtonWidth: win.minButtonWidth,
        buttonAlign: win.buttonAlign
    });     
  
    this.buttons = [];  
    this.winEl = win.getEl();
    this.winBodyEl = win.body;
    
    var dh = Ext.DomHelper;
    this.el = Ext.get(el);
    if (!this.el && this.autoCreate) {
        var cls = ['ux-win-blind'];
        if (this.cls) cls.push(this.cls);
        cls.push('ux-win-blind-inactive');
        this.el = dh.append(
            this.winEl,
            {id: el, cls: cls.join(' ')}, 
            true
        );
    }
    
    var el = this.el;  
    
    // look for the body element and create if necessary
    this.body = el.child('/.ux-win-blind-body');
    if (!this.body) {
        this.body = el.createChild({cls: 'ux-win-blind-body'});
    }

    // if the config contained content update the content el
    if (this.text) {
        this.body.update(this.text);
    }

     // wrap the body and footer for special rendering
    this.bwrap = this.body.wrap({cls: 'ux-win-blind-bwrap'});
    
    // look for the footer element and create if necessary
    this.footer = el.child('/.ux-win-blind-ft');
    if (!this.footer) {
        this.footer = el.createChild({cls: 'ux-win-blind-ft'});
    }
    
    // move the footer to the bwrap
    this.bwrap.dom.appendChild(this.footer.dom);
 
    // create a shadow for a layer effect
    this.shadow = typeof this.shadow != 'undefined' ? this.shadow : true;
    if (this.shadow) {
        this.shadow = new Ext.Shadow({
            mode: typeof this.shadow == 'string' ? this.shadow : 'sides',
            offset: typeof this.shadowOffset == 'number' ? this.shadowOffset : 4
        }); 
    }
    
    // observable events
    this.addEvents({
        /**
         * @event beforeshow
         * Fires before this window is shown.
         * @param {WindowBlind} this
         */ 
        beforeshow: true,
        /**
         * @event show
         * Fires after the blind has been completely eased into view.
         * @param {WindowBlind} this
         */ 
        show: true,
        /**
         * @event beforehide
         * Fires before the blind el is eased out of view.
         * @param {WindowBlind} this
         */ 
        beforehide: true,
        /**
         * @event hide
         * Fires after the blind has been completely eased out of view.
         * @param {WindowBlind} this
         */ 
        hide: true
    });  

    // register for all events so that subtypes can plug into the lifecycle.
    this.on('beforeshow', this.onBeforeShow, this);  
    this.on('show', this.onShow, this);  
    this.on('beforehide', this.onBeforeHide, this);  
    this.on('hide', this.onHide, this);  
};

Ext.extend(Ext.ux.WindowBlind, Ext.util.Observable, {   
    /**
     * Default: autoCreate true is the default.
     */
    autoCreate: true,
    
    /**
     * Default: fxDuration (defaults to .35 sec).  Set this to -1 to remove animation fx.
     */ 
    fxDuration: .35,
    
    /**
     * Show Fx.  The function from Ext.Fx to use when showing this (defaults to Ext.Fx.slideIn).  Optional value
     * is Ext.Fx.fadeIn.  Motion effects are always based on top - center.
     */
    showFx: Ext.Fx.slideIn,
    
    /**
     * Hide Fx.  The function from Ext.Fx to use when hiding this (defaults to Ext.Fx.slideIn).  Optional values
     * include Ext.Fx.fadeOut, Ext.Fx.ghost, Ext.Fx.puff. Motion effects are always based on top - center.
     */
    hideFx: Ext.Fx.slideOut,
    
    /**
     * Show easing.  Easing method to use if using an easing animation (defaults to 'easeOut')
     */
    showEasing: 'easeOut',
    
    /**
     * Hide easing.  Easing method to use if using an easing animation (defaults to 'easeOut')
     */
    hideEasing: 'easeOut',
        
    /**
     * Adds and renders a button to the footer.
     * @param {String/Object} config A string becomes the button text, an object can be a Button config
     * object
     * @param {Function} handler The function called when the button is clicked
     * @param {Object} scope (optional) The scope of the handler function
     * @return {Ext.Button}
     */
    addButton : function(config, handler, scope) {
        if (!this.btnContainer) {
            var tbl = this.footer.createChild({cls:'x-panel-btns-ct', cn: {
                cls:"x-panel-btns x-panel-btns-"+this.buttonAlign,
                html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
            }}, null, true);

            this.btnContainer = tbl.getElementsByTagName('tr')[0];
        }
                
        var td = document.createElement("td");
        td.className = 'x-panel-btn-td';
        
        var btnConfig = this.getButtonConfig(config, handler, scope);
        btnConfig.renderTo = this.btnContainer.appendChild(td);
        var btn = new Ext.Button(btnConfig);

        this.footer.setHeight(
            this.btnContainer.parentNode.parentNode.offsetHeight 
            + this.footer.getPadding("tb") 
            + this.footer.getMargins("tb")
        );

        return this.registerButton(btn);
    }, 

    /**
     * Construct a button config.
     */
    getButtonConfig: function(config, handler, scope) {
        var btnConfig = {
            handler: handler || this.dismiss,
            scope: scope || this,
            hideParent: true
        };
        
        if (this.minButtonWidth) {
            btnConfig.minWidth = this.minButtonWidth
        }

        if (typeof config == "string") {
            btnConfig.text = config;
        } else {
            Ext.apply(btnConfig, config);
        }
    
        return btnConfig;
    },

    /**
     * Register a new button.
     */
    registerButton: function(btn) {
        if (btn.defaultButton) {
            this.setDefaultButton(btn);
        }

        this.buttons.push(btn); 
        return btn;
    },
    
    /**
     * Private
     * Align the element's top left position to the
     * top left of the window's body. 
     */
    beforeShow: function() {
        // mask the window's el
        this.winEl.mask();    

        // sync the height & width
        this.syncSize();

        // align el's top-center to the window's body top-center
        this.el.alignTo(this.winBodyEl, 't-t');
    },

    /**
     * Show this.  
     */
    show: function() {
        if (this.el.isVisible()) {
            return;
        }
    
        if (this.fireEvent("beforeshow", this) === false){
            return;
        }
    
        this.beforeShow();
        this.animate(this.showFx, {
            easing: this.showEasing,
            duration: this.fxDuration,
            callback: this.afterShow,
            scope: this           
        });
    },

    /**
     * Set the fucus and show the shadow.
     */
    afterShow: function() {
        this.focus();        
        if (this.shadow) {      
            this.shadow.show(this.el);
        }

        if (this.overflowBody) {
           this.body.setStyle('overflow', 'auto');
        }   

        this.el.removeClass('ux-win-blind-inactive');
        this.fireEvent('show', this);
    },    

    /**
     * Private
     * If the body needs to overflow set the overflow style to hidden.
     * Hides the scrollbar that would ease out beyond the window's 
     * containing element.
     */
    beforeHide: function() {
        if (this.overflowBody) {
            this.body.setStyle('overflow', 'hidden');
            delete this.overflowBody;
        }

        if (this.shadow) {
            this.shadow.hide();
        }

        // css override for overflow for divs within the body
        this.el.addClass('ux-win-blind-inactive'); 
    },

    /**
     * Hide this. 
     */
    hide: function() {
        if (!this.el.isVisible()) {
            return;
        }
        
        if (this.fireEvent("beforehide", this) === false) {
            return;
        }

        this.beforeHide();
        this.animate(this.hideFx, {
            easing: this.hideEasing,
            duration: this.fxDuration,
            callback: this.afterHide,
            scope: this                      
        });
    },

    /**
     * Private
     * Unmask the window's el.
     */
    afterHide: function() {
        this.winEl.unmask();    
        if (this.restoreHeight) {
            this.body.setHeight(this.restoreHeight);
            delete this.restoreHeight;
        }   
        this.fireEvent('hide', this);
    },

    /**
     * Sync the height and width of this.
     */
    syncSize: function() {
        this.syncWidth();
        this.syncHeight();
    },

    /**
     * Set el's width to be the window body's el less any padding or borders.
     */
    syncWidth: function() {
        this.el.setWidth(this.winBodyEl.getWidth(true) - 20);    
    },

    /**
     * Private
     * Adjust the body el height to ensure this blind only covers the body
     * of the window.   Manage overflow if this height exceeds the window's body.
     * It is important to ensure scrollbars are not visible until after show.
     */
    syncHeight: function() {
        var h = this.winBodyEl.getComputedHeight();
        if (this.el.getComputedHeight() > h) {
            this.overflowBody = true;
            this.restoreHeight = this.body.getHeight();
            this.body.setStyle('overflow', 'hidden');
            this.body.setHeight(h - this.bwrap.getPadding('tb') - this.footer.getComputedHeight());
        }
    },
    
    /**
     * Runs the animation.
     * @param {Function} fx The function that will be invoked by the animate method to either show or hide this.
     * @param {Object} options (optional) Object literal with any of the Fx config options.
     */
    animate: function(fx, options) {
        if (this.isMotionFx(fx)) {
            fx.call(this.el, 't', options);
        } else {
            fx.call(this.el, options);  
        }   
    },

    /**
     * Determines if the passed function is a motion effect that requires an anchor point.
     * @param {Function} fn The function that will be invoked by the animate method to either show or hide this.
     */
    isMotionFx: function(fn) {
        return fn === Ext.Fx.slideIn
            || fn === Ext.Fx.slideOut
            || fn === Ext.Fx.ghost;
    },

    /**
     * Sets the default button to be focused when the blind is displayed.
     * @param {Ext.Button} btn Sets the button
     */
    setDefaultButton: function(btn) {
        this.defaultBtn = btn;
    },

    /**
     * Private.  Focuses the specificed defaultButton.  If a default button
     * wasn't specified then the first button added to this blind will be focused
     */
    focus: function() {
        if (this.defaultBtn) {
            this.defaultBtn.focus.defer(10, this.defaultBtn);
        } else if (this.buttons){
            this.buttons[0].focus();
        }   
    },

    /**
     * Hides (if visible) and destroys this blind.  
     */
    dismiss: function() {
        if (!this.el.isVisible()) {
            this.destroy();
        } else {
            this.on('hide', this.destroy, this);
            this.hide();
        }
    },

    /**
     * Default implementation of the onBeforeShow method.  Subtypes
     * should override this to perform any 'beforeshow' logic.
     */
    onBeforeShow: Ext.emptyFn,
    
    /**
     * Default implementation of the onShow method.  Subtypes
     * should override this to perform any logic after the blind
     * el has been shown.
     */
    onShow: Ext.emptyFn,

    /**
     * Default implementation of the onBeforeHide method.  Subtypes
     * should override this to perform any logic prior to hiding
     * the blind el.
     */
    onBeforeHide: Ext.emptyFn,

    /**
     * Default implementation of the onBeforeHide method.  Subtypes
     * should override this to perform any logic after the blind el
     * has been hidden.
     */
    onHide: Ext.emptyFn,

    /**
     * Cleanup.  
     */
    destroy: function() {
        this.purgeListeners();
        if (this.shadow) {
            this.shadow.hide();
            delete this.shadow;
        }
    
        Ext.Element.uncache(
            this.body,
            this.footer,
            this.bwrap
        );
    
        if (this.buttons) {
           Ext.each(this.buttons, function(btn) {
              btn.destroy();
           });

           delete this.buttons;
        }     
        
        this.el.remove();
    }
});


Ext.ux.PanelBlind = function(el, win, config) {
    Ext.ux.PanelBlind.superclass.constructor.call(this, el, win, config);
    this.panel = new Ext.Panel(this.panelConfig);
};

Ext.extend(Ext.ux.PanelBlind, Ext.ux.WindowBlind, { 
    /**
     * Render the form to the body el prior to the first show.
     */
    onBeforeShow: function() {
        if (! this.panel.rendered) {
             this.panel.render(this.body);              
        }
    
        this.panel.hide();
        Ext.ux.PanelBlind.superclass.onBeforeShow.call(this);
    },
    
    /**
     * Override to destory the form.
     */
    destroy: function() {
        this.panel.destroy();
        Ext.ux.PanelBlind.superclass.destroy.call(this);
    }   
});

Ext.ux.PanelBlind = Ext.extend(Ext.Panel, {

    constructor: function(config) {
        config = Ext.apply({
            autoHeight: false,
            floating: true,
            shadowOffset: 6,
            cls: 'x-blind',
            bodyStyle: {
                'border-width': '0px'
            },
            buttonAlign: 'right',
            buttons: [{
                text: 'Annuler',
                handler: this.dismiss,
                scope: this
            }]
        }, config);
        Ext.ux.PanelBlind.superclass.constructor.call(this, config);
    },

    init: function(client) {
        this.client = client;
        if (!this.client.blinds) {
            this.client.blinds = new Ext.util.MixedCollection();
        }
        client.blinds.add(this);
        this.client.constructor.prototype.showBlind = this.clientShowBlind;
        this.client.constructor.prototype.dismissBlind = this.clientDismissBlind;
        this.client.on({
            destroy: this.destroy,
            resize: this.syncSizeWithClient,
            scope: this
        });
        

    },

    syncSizeWithClient: function() {
        this.setWidth(this.client.body.getSize(true).width - 120);
    },

    clientShowBlind: function(id) {
        var b = this.blinds.get(id);
        if (b) {
            b.show();
        }
    },

    clientDismissBlind: function(id) {
        var b = this.blinds.get(id);
        if (b) {
            b.dismiss();
        }
    },
    
    show: function() {

        if (!Ext.isIE) {
            var mask = this.client.getEl().mask();
        }

        if (!this.rendered) {
            this.render(this.client.getEl());
        }
        
        if (mask) {
       	    this.el.setZIndex(Number(mask.getStyle('z-index')) + 1);   
       	}     	

        this.el.disableShadow();
        this.syncSizeWithClient();
        this.el.alignTo(this.client.body, 't-t');
        this.el.slideIn('t', {
		    easing: 'easeOutStrong',
		    duration: 0.3,
            callback: function() {
                this.el.visible = true; // Ext bug. Flag not set causes enableShadow to fail.
                this.el.enableShadow(true);
                this.body.show();
            },
            scope: this
        });
    },

    dismiss: function() {
    
        this.body.hide();
        
        if (this.destroyOnDismiss) {
            this.destroy();
            delete this.client.blinds[this.id];
        } else {
            this.el.disableShadow();
            this.el.slideOut('t', {
                easing: 'easeInStrong',
                duration: 0.3,
                callback: function() {
                    if (!Ext.isIE) {
                        this.client.getEl().unmask();
                    }
                },
                scope: this
            });

        }
    }

});
