Ext.namespace('Ext.ux');

/**
 * Licensed under GNU LESSER GENERAL PUBLIC LICENSE Version 3
 *
 * @author Thorsten Suckow-Homberg <ts@siteartwork.de>
 * @url http://www.siteartwork.de/wizardcomponent
 */

/**
 * @class Ext.ux.Wiz
 * @extends Ext.Window
 *
 * A specific {@link Ext.Window} that models a wizard component.
 * A wizard is basically a dialog that guides a user through various steps
 * where he has to fill out form-data.
 * A {@link Ext.ux.Wiz}-component consists typically of a {@link Ext.ux.Wiz.Header}
 * and window-buttons ({@link Ext.Button}) which are linked to the {@link Ext.ux.Wiz.Card}s
 * which themself represent the forms the user has to fill out.
 *
 * In order to switch between the cards in the wizard, you need the {@link Ext.ux.layout.CardLayout},
 * which will check if an active-item can be hidden, before the requested new item will be set to
 * 'active', i.e. shown. This is needed since the wizard may not allow a card to be hidden, if
 * the input entered by the user was not valid. You can get this custom layout at
 * {@link http://www.siteartwork.de/cardlayout}.
 *
 * Note:
 * When data has been collected and teh "onFinish" listener triggers an AJAX-request,
 * you should call the "switchDialogState" method so that the the dialog shows a loadmask.
 * Once the requests finishes, call "switchDialogState" again, specially before any call
 * to the "close" method of this component, otherwise the "closable" property of this
 * instance might prevent a "close" operation for this dialog.
 *
 *
 * @constructor
 * @param {Object} config The config object
 */
Ext.ux.Wiz = Ext.extend(Ext.Window, {

    /**
     * @cfg {Object} An object containing the messages for the {@link Ext.LoadMask}
     * covering the card-panel on request, whereas the property identifies the
     * msg-text to show, and the value is the message text itself. Defaults to
     <pre><code>
{
    default : _('ID_SAVING')
}
     </code></pre>
     *
     * Depending on the contexts the loadMask has to be shown in (using the method
     * showLoadMask of this class), the object can be configure to hold
     * various messages.
<pre><code>
this.loadMaskConfig = {
    default    : _('ID_SAVING'),
    validating : 'Please wait, validating input...',
};
// loadMask will be shown, displaying the message 'Please wait, validating input...'
this.showLoadMask(true, 'validating');
     </code></pre>
     */
    loadMaskConfig : {
        'default' : _('ID_SAVING')
    },

    /**
     * @cfg {Number} height The height of the dialog. Defaults to "400".
     */
    height : 400,

    /**
     * @cfg {Number} width The width of the dialog. Defaults to "540".
     */
    width : 540,

    /**
     * @cfg {Boolean} closable Wether the dialog is closable. Defaults to "true".
     * This property will be changed by the "switchDialogState"-method, which will
     * enable/disable controls based on the passed argument. Thus, this config property
     * serves two purposes: Tell the init config to render a "close"-tool, and create a
     * "beforeclose"-listener which will either return true or false, indicating if the
     * dialog may be closed.
     */
    closable : true,

    /**
     * @cfg {Boolean} resizable Wether the dialog is resizable. Defaults to "false".
     */
    resizable : false,

    /**
     * @cfg {Boolean} resizable Wether the dialog is modal. Defaults to "true".
     */
    modal : true,

    /**
     * @cfg {Array} cards A numeric array with the configured {@link Ext.ux.Wiz.Card}s.
     * The index of the cards in the array represent the order in which they get displayed
     * in the wizard (i.e. card at index 0 gets displayed in the first step, card at index 1 gets
     * displayed in the second step and so on).
     */
    cards : null,

    /**
     * @cfg {String} previousButtonText The text to render the previous-button with.
     * Defaults to "&lt; Back" (< Back)
     */
    previousButtonText : _('ID_PREVIUS'),

    /**
     * @cfg {String} nextButtonText The text to render the next-button with.
     * Defaults to "Next &gt;" (Next >)
     */
    nextButtonText : _('ID_NEXT'),

    /**
     * @cfg {String} cancelButtonText The text to render the cancel-button with.
     * Defaults to "Cancel"
     */
    cancelButtonText : _('ID_CANCEL'),

    /**
     * @cfg {String} finishButtonText The text to render the next-button with when the last
     * step of the wizard is reached. Defaults to "Finish"
     */
    finishButtonText : _('ID_FINISH'),

    /**
     * @cfg {Object} headerConfig A config-object to use with {@link Ext.ux.Wiz.Header}.
     * If not present, it defaults to an empty object.
     */
    headerConfig : {},

    /**
     * @cfg {Object} cardPanelConfig A config-object to use with {@link Ext.Panel}, which
     * represents the card-panel in this dialog.
     * If not present, it defaults to an empty object
     */
    cardPanelConfig : {},

    /**
     * @param {Ext.Button} The window-button for paging to the previous card.
     * @private
     */
    previousButton : null,

    /**
     * @param {Ext.Button} The window-button for paging to the next card. When the
     * last card is reached, the event fired by and the text rendered to this button
     * will change.
     * @private
     */
    nextButton : null,

    /**
     * @param {Ext.Button} The window-button for canceling the wizard. The event
     * fired by this button will usually close the dialog.
     * @private
     */
    cancelButton : null,

    /**
     * @param {Ex.Panel} The card-panel that holds the various wizard cards
     * ({@link Ext.ux.Wiz.Card}). The card-panel itself uses the custom
     * {@link Ext.ux.layout.CardLayout}, which needs to be accessible by this class.
     * You can get it at {@link http://www.siteartwork.de/cardlayout}.
     * @private
     */
    cardPanel : null,

    /**
     * @param {Number} currentCard The current {@link Ext.ux.Wiz.Card} displayed.
     * Defaults to -1.
     * @private
     */
    currentCard : -1,

    /**
     * @param {Ext.ux.Wiz.Header} The header-panel of the wizard.
     * @private
     */
    headPanel : null,

    /**
     * @param {Number} cardCount Helper for storing the number of cards used
     * by this wizard. Defaults to 0 (inherits "cards.length" later on).
     * @private
     */
    cardCount : 0,

    /**
     * Inits this component with the specified config-properties and automatically
     * creates its components.
     */
    initComponent : function()
    {
        this.initButtons();
        this.initPanels();

        var title = this.title || this.headerConfig.title;
        title     = title || "";

        Ext.apply(this, {
            title     : title,
            layout    : 'border',
            cardCount : this.cards.length,
            buttons   : [
                this.previousButton,
                this.nextButton,
                this.cancelButton
            ],
            items : [
                this.headPanel,
                this.cardPanel
            ]
        });

        this.addEvents(
            /**
             * @event cancel
             * Fires after the cancel-button has been clicked.
             * @param {Ext.ux.Wiz} this
             */
            'cancel',
            /**
             * @event finish
             * Fires after the last card was reached in the wizard and the
             * next/finish-button has been clicked.
             * @param {Ext.ux.Wiz} this
             * @param {Object} data The collected data of the cards, whereas
             * the index is the id of the card and the specific values
             * are objects with key/value pairs in the form formElementName : value
             */
            'finish'
        );

        Ext.ux.Wiz.superclass.initComponent.call(this);
    },

// -------- helper
    /**
     * Returns the form-data of all cards in this wizard. The first index is the
     * id of the card in this wizard,
     * and the values are objects containing key/value pairs in the form of
     * fieldName : fieldValue.
     *
     * @return {Array}
     */
    getWizardData : function()
    {
        var formValues = {};
        var cards = this.cards;
        for (var i = 0, len = cards.length; i < len; i++) {
            if (cards[i].form) {
                formValues[cards[i].id] = cards[i].form.getValues(false);
            } else {
                formValues[cards[i].id] = {};
            }
        }

        return formValues;
    },

    /**
     * Switches the state of this wizard between disabled/enabled.
     * A disabled dialog will have a {@link Ext.LoadMask} covering the card-panel
     * to prevent user input, and the buttons will be rendered disabled/enabled.
     * If the dialog is closable, the close-tool will be masked, too, and the dialog will not
     * be closable by clicking the "close" tool.
     *
     * @param {Boolean} enabled "false" to prevent user input and mask the elements,
     * otherwise true.
     * @param {String} type The type of msg for the {@Ext.LoadMask} covering
     * the cardPanel, as defined in the cfg property "loadMaskConfig"
     */
    switchDialogState : function(enabled, type)
    {
        this.showLoadMask(!enabled, type);

        this.previousButton.setDisabled(!enabled);
        this.nextButton.setDisabled(!enabled);
        this.cancelButton.setDisabled(!enabled);

        var ct = this.tools['close'];

        if (ct) {
            switch (enabled) {
                case true:
                    this.tools['close'].unmask();
                break;

                default:
                    this.tools['close'].mask();
                break;
            }
        }

        this.closable = enabled;
    },

    /**
     * Shows the load mask for this wizard. By default, the cardPanel's body
     * will be masked.
     *
     * @param {Boolean} show true to show the load mask, otherwise false.
     * @param {String} type The type of message for the {@Ext.LoadMask} covering
     * the cardPanel, as defined in the cfg property "loadMaskConfig"
     */
    showLoadMask : function(show, type)
    {
        if (!type) {
            type = 'default';
        }

        if (show) {
            if (this.loadMask == null) {
                this.loadMask = new Ext.LoadMask(this.body);
            }
            this.loadMask.msg = this.loadMaskConfig[type];
            this.loadMask.show();
        } else {
            if (this.loadMask) {
                this.loadMask.hide();
            }
        }
    },


    /**
     * Inits the listener for the various {@link Ext.ux.Wiz.Card}s used
     * by this component.
     */
    initEvents : function()
    {
        Ext.ux.Wiz.superclass.initEvents.call(this);

        this.on('beforeclose', this.onBeforeClose, this);
    },

    /**
     * Creates the head- and the card-panel.
     * Be sure to have the custom {@link Ext.ux.layout.CardLayout} available
     * in order to make the card-panel work as expected by this component
     * ({@link http://www.siteartwork.de/cardlayout}).
     */
    initPanels : function()
    {
        var cards           = this.cards;
        var cardPanelConfig = this.cardPanelConfig;

        Ext.apply(this.headerConfig, {
            steps : cards.length
        });

        this.headPanel = new Ext.ux.Wiz.Header(this.headerConfig);

        Ext.apply(cardPanelConfig, {
            layout : new Ext.ux.layout.CardLayout(),
            items  : cards
        });

        Ext.applyIf(cardPanelConfig, {
            region     : 'center',
            border     : false,
            activeItem : 0
        });

        var cards = this.cards;

        for (var i = 0, len = cards.length; i < len; i++) {
            cards[i].on('show', this.onCardShow, this);
            cards[i].on('hide', this.onCardHide, this);
            cards[i].on('clientvalidation', this.onClientValidation, this);
        }

        this.cardPanel = new Ext.Panel(cardPanelConfig);
    },

    /**
     * Creates the instances for the the window buttons.
     */
    initButtons : function()
    {
        this.previousButton = new Ext.Button({
            text     : this.previousButtonText,
            disabled : true,
            minWidth : 75,
            handler  : this.onPreviousClick,
            scope    : this
        });

        this.nextButton = new Ext.Button({
            text     : this.nextButtonText,
            minWidth : 75,
            handler  : this.onNextClick,
            scope    : this
        });

        this.cancelButton = new Ext.Button({
            text     : this.cancelButtonText,
            handler  : this.onCancelClick,
            scope    : this,
            minWidth : 75
        });
    },

// -------- listeners

    /**
     * Listener for the beforeclose event.
     * This listener will return true or false based on the "closable"
     * property by this component. This property will be changed by the "switchDialogState"
     * method, indicating if there is currently any process running that should prevent
     * this dialog from being closed.
     *
     * @param {Ext.Panel} panel The panel being closed
     *
     * @return {Boolean}
     */
    onBeforeClose : function(panel)
    {
        return this.closable;
    },

    /**
     * By default, the card firing this event monitors user input in a frequent
     * interval and fires the 'clientvalidation'-event along with it. This listener
     * will enable/disable the next/finish-button in accordance with it, based upon
     * the parameter isValid. isValid" will be set by the form validation and depends
     * on the validators you are using for the different input-elemnts in your form.
     * If the card does not contain any forms, this listener will never be called by the
     * card itself.
     *
     * @param {Ext.ux.Wiz.Card} The card that triggered the event.
     * @param {Boolean} isValid "true", if the user input was valid, otherwise
     * "false"
     */
    onClientValidation : function(card, isValid)
    {
        if (!isValid) {
            this.nextButton.setDisabled(true);
        } else {
            this.nextButton.setDisabled(false);
        }
    },

    /**
     * This will render the "next" button as disabled since the bindHandler's delay
     * of the next card to show might be lagging on slower systems
     *
     */
    onCardHide : function(card)
    {
        if (this.cardPanel.layout.activeItem.id === card.id) {
            this.nextButton.setDisabled(true);
        }
    },


    /**
     * Listener for the "show" event of the card that gets shown in the card-panel.
     * Renders the next/previous buttons based on the position of the card in the wizard
     * and updates the head-panel accordingly.
     *
     * @param {Ext.ux.Wiz.Card} The card being shown.
     */
    onCardShow : function(card)
    {
        var parent = card.ownerCt;

        var items = parent.items;

        for (var i = 0, len = items.length; i < len; i++) {
            if (items.get(i).id == card.id) {
                break;
            }
        }

        this.currentCard = i;
        this.headPanel.updateStep(i, card.title);

        if (i == len-1) {
            this.nextButton.setText(this.finishButtonText);
        } else {
            this.nextButton.setText(this.nextButtonText);
        }

        if (card.isValid()) {
            this.nextButton.setDisabled(false);
        }

        if (i == 0) {
            this.previousButton.setDisabled(true);
        } else {
            this.previousButton.setDisabled(false);
        }

    },


    /**
     * Fires the 'cancel'-event. Closes this dialog if the return value of the
     * listeners does not equal to "false".
     */
    onCancelClick : function()
    {
        if (this.fireEvent('cancel', this) !== false) {
            this.close();
        }
    },

    /**
     * Fires the 'finish'-event. Closes this dialog if the return value of the
     * listeners does not equal to "false".
     */
    onFinish : function()
    {
        if (this.fireEvent('finish', this, this.getWizardData()) !== false) {
            this.close();
        }
    },

    /**
     * Listener for the previous-button.
     * Switches to the previous displayed {@link Ext.ux.Wiz.Card}.
     */
    onPreviousClick : function()
    {
        if (this.currentCard > 0) {
            this.cardPanel.getLayout().setActiveItem(this.currentCard - 1);
        }
    },

    /**
     * Listener for the next-button. Switches to the next {@link Ext.ux.Wiz.Card}
     * if the 'beforehide'-method of it did not return false. The functionality
     * for this is implemented in {@link Ext.ux.layout.CardLayout}, which is needed
     * as the layout for the card-panel of this component.
     */
    onNextClick : function()
    {
        this.nextButton.setDisabled(true);
        if (this.currentCard == this.cardCount-1) {
            this.onFinish();
        } else {
            this.cardPanel.getLayout().setActiveItem(this.currentCard+1);
        }
    }
});