/**
 * The Form panel presents a set of form fields and provides convenient ways to load and save data. Usually a form
 * panel just contains the set of fields you want to display, ordered inside the items configuration like this:
 *
 *     var form = Ext.create('Ext.form.Panel', {
 *         items: [
 *             {
 *                 xtype: 'textfield',
 *                 name: 'name',
 *                 label: 'Name'
 *             },
 *             {
 *                 xtype: 'emailfield',
 *                 name: 'email',
 *                 label: 'Email'
 *             },
 *             {
 *                 xtype: 'passwordfield',
 *                 name: 'password',
 *                 label: 'Password'
 *             }
 *         ]
 *     });
 *
 * Here we just created a simple form panel which could be used as a registration form to sign up to your service. We
 * added a plain {@link Ext.field.Text text field} for the user's Name, an {@link Ext.field.Email email field} and
 * finally a {@link Ext.field.Password password field}. In each case we provided a {@link Ext.field.Field#name name}
 * config on the field so that we can identify it later on when we load and save data on the form.
 *
 * <h2>Loading data</h2>
 *
 * Using the form we created above, we can load data into it in a few different ways, the easiest is to use
 * {@link #setValues}:
 *
 *     form.setValues({
 *         name: 'Ed',
 *         email: 'ed@sencha.com',
 *         password: 'secret'
 *     });
 *
 * It's also easy to load {@link Ext.data.Model Model} instances into a form - let's say we have a User model and want
 * to load a particular instance into our form:
 *
 *     Ext.define('MyApp.model.User', {
 *         fields: ['name', 'email', 'password']
 *     });
 *
 *     var ed = Ext.create('MyApp.model.User', {
 *         name: 'Ed',
 *         email: 'ed@sencha.com',
 *         password: 'secret'
 *     });
 *
 *     form.setRecord(ed);
 *
 * <h2>Retrieving form data</h2>
 *
 * Getting data out of the form panel is simple and is usually achieve vai the {@link #getValues} method:
 *
 *     var values = form.getValues();
 *
 *     //values now looks like this:
 *     {
 *         name: 'Ed',
 *         email: 'ed@sencha.com',
 *         password: 'secret'
 *     }
 *
 * It's also possible to listen to the change events on individual fields to get more timely notification of changes
 * that the user is making. Here we expand on the example above with the User model, updating the model as soon as
 * any of the fields are changed:
 *
 *     var form = Ext.create('Ext.form.Panel', {
 *         listeners: {
 *             '> field': {
 *                 change: function(field, newValue, oldValue) {
 *                     ed.set(field.getName(), newValue);
 *                 }
 *             }
 *         },
 *         items: //as before
 *     });
 *
 * The above used a new capability of Touch 2.0, which enables you to specify listeners on child components of any
 * container. In this case, we attached a listener to the {@link Ext.field.Text#change change} event of each form
 * field that is a direct child of the form panel. Our listener gets the name of the field that fired the change event,
 * and updates our {@link Ext.data.Model Model} instance with the new value. For example, changing the email field
 * in the form will update the Model's email field.
 *
 * <h2>Submitting forms</h2>
 *
 * There are a few ways to submit form data. In our example above we have a Model instance that we have updated, giving
 * us the option to use the Model's {@link Ext.data.Model#save save} method to persist the changes back to our server,
 * without using a traditional form submission. Alternatively, we can send a normal browser form submit using the
 * {@link #submit} method:
 *
 *     form.submit({
 *         url: 'url/to/submit/to',
 *         method: 'POST',
 *         success: function() {
 *             alert('form submitted successfully!');
 *         }
 *     });
 *
 * In this case we provided the url to submit the form to inside the submit call - alternatively you can just set the
 * {@link #url} configuration when you create the form. We can specify other parameters (see {@link #submit} for a
 * full list), including callback functions for success and failure, which are called depending on whether or not the
 * form submission was successful. These functions are usually used to take some action in your app after your data
 * has been saved to the server side.
 */
Ext.define('Ext.form.Panel', {
    alternateClassName: 'Ext.form.FormPanel',
    extend  : 'Ext.Panel',
    xtype   : 'formpanel',
    requires: ['Ext.XTemplate', 'Ext.field.Checkbox', 'Ext.Ajax'],

    /**
     * @event submit
     * Fires upon successful (Ajax-based) form submission
     * @param {Ext.form.Panel} this This FormPanel
     * @param {Object} result The result object as returned by the server
     */

    /**
     * @event beforesubmit
     * Fires immediately preceding any Form submit action.
     * Implementations may adjust submitted form values or options prior to execution.
     * A return value of <tt>false</tt> from this listener will abort the submission
     * attempt (regardless of standardSubmit configuration)
     * @param {Ext.form.Panel} this This FormPanel
     * @param {Object} values A hash collection of the qualified form values about to be submitted
     * @param {Object} options Submission options hash (only available when standardSubmit is false)
     */

    /**
     * @event exception
     * Fires when either the Ajax HTTP request reports a failure OR the server returns a success:false
     * response in the result payload.
     * @param {Ext.form.Panel} this This FormPanel
     * @param {Object} result Either a failed Ext.data.Connection request object or a failed (logical) server
     * response payload.
     */

    config: {
        // @inherit
        cls: Ext.baseCSSPrefix + 'form',

        /**
         * @cfg {Boolean} standardSubmit
         * Wether or not we want to perform a standard form submit.
         * @accessor
         */
        standardSubmit: false,

        /**
         * @cfg {String} url
         * The default Url for submit actions
         * @accessor
         */
        url: null,

        // @inherit
        elConfig: { tag: 'form' },

        /**
         * @cfg {Object} baseParams
         * Optional hash of params to be sent (when standardSubmit configuration is false) on every submit.
         * @accessor
         */
        baseParams : null,

        /**
         * @cfg {Ext.XTemplate/String/String[]} waitTpl
         * The defined waitMsg template.  Used for precise control over the masking agent used
         * to mask the FormPanel (or other Element) during form Ajax/submission actions. For more options, see
         * {@link #showMask} method.
         * @accessor
         */
        waitTpl: '<div class="{cls}">{message}&hellip;</div>',

        /**
         * @cfg {Object} submitOnAction
         * When this is set to true, the form will automatically submit itself whenever the 'action'
         * event fires on a field in this form. The action event usually fires whenever you press
         * go or enter inside a textfield.
         * @accessor
         */
        submitOnAction : true,

        /**
         * @cfg {Ext.dom.Element} maskTarget The target where the form mask will be shown.
         */
        maskTarget: null,

        /**
         * @cfg {Ext.data.Model} record The model instance of this form. Can by dynamically set at any time
         * @accessor
         */
        record: null,

        // @inherit
        layout: {
            type : 'vbox',
            align: 'stretch'
        },

        // @inherit
        scrollable: {
            scrollMethod: 'scrollposition'
        }
    },

    // @private
    initialize: function() {
        var me = this;

        me.on({
            action: 'onFieldAction',
            scope : me
        });

        me.element.on({
            submit: 'onSubmit',
            scope : this
        });

        me.callParent(arguments);
    },

    /**
     * Initializes the renderTpl.
     * @return {Ext.XTemplate} The renderTpl XTemplate instance.
     * @private
     */
    applyWaitTpl: function(waitTpl) {
        if (waitTpl) {
            if (Ext.isArray(waitTpl) || typeof waitTpl === "string") {
                waitTpl = Ext.create('Ext.XTemplate', waitTpl);
            }
        }
        return waitTpl;
    },

    /**
     * Loads matching fields from a model instance into this form
     * @param {Ext.data.Model} instance The model instance
     * @return {Ext.form.Panel} This form
     */
    setRecord: function(record) {
        var me = this;

        if (record && record.data) {
            me.setValues(record.data);
        }

        me._record = record;

        return this;
    },

    // @private
    onSubmit: function(e) {
        var me = this;
        if (!me.getStandardSubmit() || me.fireAction('submit', [me, me.getValues(true)], 'doSubmit') === false) {
            if (e) {
                e.stopEvent();
            }
        }
    },

    doSubmit: Ext.emptyFn,

    // @private
    onFieldAction: function(field) {
        if (this.getSubmitOnAction()) {
            field.blur();
            this.submit();
        }
    },

    /**
     * Performs a Ajax-based submission of form values (if standardSubmit is false) or otherwise
     * executes a standard HTML Form submit action.
     * @param {Object} options Unless otherwise noted, options may include the following:
     * <ul>
     * <li><b>url</b> : String
     * <div class="sub-desc">
     * The url for the action (defaults to the form's {@link #url url}.)
     * </div></li>
     *
     * <li><b>method</b> : String
     * <div class="sub-desc">
     * The form method to use (defaults to the form's method, or POST if not defined)
     * </div></li>
     *
     * <li><b>params</b> : String/Object
     * <div class="sub-desc">
     * The params to pass
     * (defaults to the FormPanel's baseParams, or none if not defined)
     * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
     * </div></li>
     *
     * <li><b>headers</b> : Object
     * <div class="sub-desc">
     * Request headers to set for the action
     * (defaults to the form's default headers)
     * </div></li>
     *
     * <li><b>autoAbort</b> : Boolean
     * <div class="sub-desc">
     * <tt>true</tt> to abort any pending Ajax request prior to submission (defaults to false)
     * Note: Has no effect when standardSubmit is enabled.
     * </div></li>
     *
     * <li><b>submitDisabled</b> : Boolean
     * <div class="sub-desc">
     * <tt>true</tt> to submit all fields regardless of disabled state (defaults to false)
     * Note: Has no effect when standardSubmit is enabled.
     * </div></li>
     *
     * <li><b>waitMsg</b> : String/Config
     * <div class="sub-desc">
     * If specified, the value is applied to the {@link #waitTpl} if defined, and rendered to the
     * {@link #maskTarget} prior to a Form submit action.
     * </div></li>
     *
     * <li><b>success</b>: function
     * <div class="sub-desc">
     * The callback that will be invoked after a successful response. A response is successful if
     * a response is received from the server and is a JSON object where the success property is set
     * to true, {"success": true}
     *
     *  The function is passed the following parameters:
     * <ul>
     * <li>form : Ext.form.Panel The form that requested the action</li>
     * <li>result : The result object returned by the server as a result of the submit request.</li>
     * </ul>
     * </div></li>
     *
     * <li><b>failure</b>: function
     * <div class="sub-desc">
     * The callback that will be invoked after a
     * failed transaction attempt. The function is passed the following parameters:
     * <ul>
     * <li>form : The Ext.form.Panel that requested the submit.</li>
     * <li>result : The failed response or result object returned by the server which performed the operation.</li>
     * </ul>
     * </div></li>
     *
     * <li><b>scope</b> : Object
     * <div class="sub-desc">
     * The scope in which to call the callback functions (The this reference for the callback functions).
     * </div></li>
     * </ul>
     *
     * @return {Ext.data.Connection} The request object
     */
    submit: function(options) {
        var me = this,
            form = me.element.dom || {},
            formValues;

        options = Ext.apply({
            url : me.getUrl() || form.action,
            submit: false,
            method : form.method || 'post',
            autoAbort : false,
            params : null,
            waitMsg : null,
            headers : null,
            success : null,
            failure : null
        }, options || {});

        formValues = me.getValues(me.getStandardSubmit() || !options.submitDisabled);

        if (me.getStandardSubmit()) {
            if (options.url && Ext.isEmpty(form.action)) {
                form.action = options.url;
            }

            form.method = (options.method || form.method).toLowerCase();

            if (me.fireEvent('beforesubmit', me, formValues, options) !== false) {
                form.submit();
            }
        }
        else {
            if (me.fireEvent('beforesubmit', me, formValues, options) !== false) {
                if (options.waitMsg) {
                    me.showMask(options.waitMsg);
                }

                return Ext.Ajax.request({
                    url: options.url,
                    method: options.method,
                    rawData: Ext.urlEncode(Ext.apply(
                        Ext.apply({}, me.getBaseParams() || {}),
                        options.params || {},
                        formValues
                    )),
                    autoAbort: options.autoAbort,
                    headers: Ext.apply(
                        {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
                        options.headers || {}),
                    scope    : me,
                    callback: function(callbackOptions, success, response) {
                        var me = this,
                            responseText = response.responseText,
                            failureFn;

                        me.hideMask();

                        failureFn = function() {
                            if (Ext.isFunction(options.failure)) {
                                options.failure.call(options.scope || me, me, response, responseText);
                            }
                            me.fireEvent('exception', me, response);
                        };

                        if (success) {
                            response = Ext.decode(responseText);
                            success = !!response.success;
                            if (success) {
                                if (Ext.isFunction(options.success)) {
                                    options.success.call(options.scope || me, me, response, responseText);
                                }
                                me.fireEvent('submit', me, response);
                            } else {
                                failureFn();
                            }
                        }
                        else {
                            failureFn();
                        }
                    }
                });
            }
        }
    },

    /**
     * Updates a model instance with the current values of this form
     * @param {Ext.data.Model} instance The model instance
     * @param {Boolean} enabled <tt>true</tt> to update the Model with values from enabled fields only
     * @return {Ext.form.Panel} This form
     */
    updateRecord: function(instance, enabled) {
        var fields, values, name;

        if (instance && (fields = instance.fields)) {
            values = this.getValues(enabled);
            for (name in values) {
                if (values.hasOwnProperty(name) && fields.containsKey(name)) {
                    instance.set(name, values[name]);
                }
            }
        }
        return this;
    },

    /**
     * Sets the values of form fields in bulk. Example usage:
     *
     *     myForm.setValues({
     *         name: 'Ed',
     *         crazy: true,
     *         username: 'edspencer'
     *     });
     *
     * If there groups of checkbox fields with the same name, pass their values in an array. For example:
     *
     *     myForm.setValues({
     *         name: 'Jacky',
     *         crazy: false,
     *         hobbies: [
     *             'reading',
     *             'cooking',
     *             'gaming'
     *         ]
     *     });
     *
     * @param {Object} values field name => value mapping object
     * @return {Ext.form.Panel} This form
     */
    setValues: function(values) {
        var fields = this.getFields(),
            name, field, value;

        values = values || {};

        for (name in values) {
            if (values.hasOwnProperty(name)) {
                field = fields[name];
                value = values[name];
                if (field) {
                    if (Ext.isArray(field)) {
                        field.forEach(function(f) {
                            if (f.isRadio) {
                                f.setGroupValue(value);
                            } else if (Ext.isArray(values[name])) {
                                f.setChecked((value.indexOf(f.getValue()) != -1));
                            } else {
                                f.setChecked((value == f.getValue()));
                            }
                        });
                    } else {
                        if (field.setChecked) {
                            field.setChecked(value);
                        } else {
                            field.setValue(value);
                        }
                    }
                }
            }
        }

        return this;
    },

    /**
     * Returns an object containing the value of each field in the form, keyed to the field's name.
     * For groups of checkbox fields with the same name, it will be arrays of values. For examples:

     <pre><code>
     {
         name: "Jacky Nguyen", // From a TextField
         favorites: [
             'pizza',
             'noodle',
             'cake'
         ]
     }
     </code></pre>

     * @param {Boolean} enabled <tt>true</tt> to return only enabled fields
     * @return {Object} Object mapping field name to its value
     */
    getValues: function(enabled) {
        var fields = this.getFields(),
            values = {},
            field, name, ln, i;

        for (name in fields) {
            if (fields.hasOwnProperty(name)) {
                if (Ext.isArray(fields[name])) {
                    values[name] = [];

                    ln = fields[name].length;

                    for (i = 0; i < ln; i++) {
                        field = fields[name][i];

                        if (!field.getChecked) {
                            values[name] = field.getValue();

                            //<debug>
                            throw new Error("Ext.form.Panel: [getValues] You have multiple fields with the same 'name' configuration of '" + name + "' in your form panel (#" + this.id + ").");
                            //</debug>

                            break;
                        }

                        if (!(enabled && field.getDisabled())) {
                            if (field.isRadio) {
                                values[name] = field.getGroupValue();
                            } else {
                                values[name].push(field.getValue());
                            }
                        }

                        
                    }
                } else {
                    field = fields[name];

                    if (!(enabled && field.getDisabled())) {
                        if (field.isCheckbox) {
                            values[name] = (field.getChecked()) ? field.getValue() : null;
                        } else {
                            values[name] = field.getValue();
                        }
                    }
                }
            }
        }

        return values;
    },

    /**
     * Resets all fields in the form back to their original values
     * @return {Ext.form.Panel} This form
     */
    reset: function() {
        this.getFieldsAsArray().forEach(function(field) {
            field.reset();
        });

        return this;
    },

    /**
     * A convenient method to enable all fields in this forms
     * @return {Ext.form.Panel} This form
     */
    enable: function() {
        this.getFieldsAsArray().forEach(function(field) {
            field.enable();
        });

        return this;
    },

    /**
     * A convenient method to disable all fields in this forms
     * @return {Ext.form.Panel} This form
     */
    disable: function() {
        this.getFieldsAsArray().forEach(function(field) {
            field.disable();
        });

        return this;
    },

    getFieldsAsArray: function() {
        var fields = [],
            getFieldsFrom = function(item) {
                if (item.isField) {
                    fields.push(item);
                }

                if (item.isContainer) {
                    item.getItems().each(getFieldsFrom);
                }
            };

        this.getItems().each(getFieldsFrom);

        return fields;
    },

    /**
     * @private
     * Returns all {@link Ext.Field field} instances inside this form
     * @param byName return only fields that match the given name, otherwise return all fields.
     * @return {Object/Array} All field instances, mapped by field name; or an array if byName is passed
     */
    getFields: function(byName) {
        var fields = {},
            itemName;

        var getFieldsFrom = function(item) {
            if (item.isField) {
                itemName = item.getName();

                if ((byName && itemName == byName) || typeof byName == 'undefined') {
                    if (fields.hasOwnProperty(itemName)) {
                        if (!Ext.isArray(fields[itemName])) {
                            fields[itemName] = [fields[itemName]];
                        }

                        fields[itemName].push(item);
                    } else {
                        fields[itemName] = item;
                    }
                }

            }

            if (item.isContainer) {
                item.items.each(getFieldsFrom);
            }
        };

        this.items.each(getFieldsFrom);

        return (byName) ? (fields[byName] || []) : fields;
    },

    getFieldsFromItem: Ext.emptyFn,

    /**
     * Shows a generic/custom mask over a designated Element.
     * @param {String/Object} cfg Either a string message or a configuration object supporting
     * the following options:
     *
     *     {
     *         message : 'Please Wait',
     *         transparent : false,
     *         target  : Ext.getBody(),  //optional target Element
     *         cls : 'form-mask',
     *         customImageUrl : 'trident.jpg'
     *     }
     *
     * This object is passed to the {@link #waitTpl} for use with a custom masking implementation.
     * @param {String/HTMLElement/Ext.Element} target The target Element instance or Element id to use
     * as the masking agent for the operation (defaults the container Element of the component)
     * @return {Ext.form.Panel} This form
     */
    showMask: function(cfg, target) {
        cfg = Ext.isString(cfg) ? {message : cfg} : cfg;
        var me = this,
            waitTpl = me.getWaitTpl();

        if (cfg && waitTpl) {
            target = Ext.get(target || cfg.target) || me.getEl();
            me.setMaskTarget(target);
            if (target) {
                target.mask(waitTpl.apply(cfg));
            }
        }
        return me;
    },

    /**
     * Hides a previously shown wait mask (See {@link #showMask})
     * @return {Ext.form.Panel} this
     */
    hideMask: function() {
        var me = this,
            maskTarget = me.getMaskTarget();
        
        if (maskTarget) {
            maskTarget.unmask();
            me.setMaskTarget(null);
        }
        return me;
    }
}, function() {
    //<deprecated product=touch since=2.0>
    this.override({
        /**
         * @deprecated Please use {@link #setRecord} instead
         */
        loadRecord: function(instance) {
            return this.setRecord.apply(this, arguments);
        },

        /**
         * @deprecated Please use {@link #setRecord} instead
         */
        loadModel: function() {
            return this.setRecord.apply(this, arguments);
        },

        constructor: function(config) {
            /**
             * @cfg {Ext.dom.Element} waitMsgTarget The target of any mask shown on this form.
             * @deprecated 2.0.0 Please use {@link #maskTarget} instead
             */
            if (config && config.hasOwnProperty('waitMsgTarget')) {
                config.maskTarget = config.waitMsgTarget;
                delete config.waitMsgTarget;
            }

            this.callParent([config]);
        }
    });
    //</deprecated>

    /**
     * (Shortcut to {@link #loadRecord} method) Loads matching fields from a model instance into this form
     * @param {Ext.data.Model} instance The model instance
     * @return {Ext.form.Panel} this
     */
    Ext.form.Panel.prototype.load = Ext.form.Panel.prototype.loadModel;
});