/**
 * The `formpanel` is an extension over `fieldpanel` and, in addition to rendering the
 * proper `form` tag
 *
 * presents a set of form fields and provides convenient ways to load and
 * save data. This component uses an HTML `form` element to contain its fields, unlike
 * {@link Ext.field.Panel fieldpanel}. As a `form`, this component provides a `submit`
 * method that can be used to post field data to a server.
 *
 * Populating a `formpanel` is the same as with {@link Ext.field.Panel fieldpanel}. The
 * same method are available for setting data in the form, gather field values and managing
 * field errors.
 *
 * ## Submitting Forms
 *
 * Using the {@link Ext.field.Panel fieldpanel} class data can be saved to the server using
 * the {@link Ext.data.Model#method!save save method} of a record. With `formpanel`,
 * however, you can use its submit method.
 *
 *      var panel = Ext.create({
 *          xtype: 'formpanel',
 *          fullscreen: true,
 *
 *          controller: 'mycontroller',
 *
 *          buttons: {
 *              submit: 'onSubmit'
 *          },
 *
 *          items: [{
 *              xtype: 'textfield',
 *              name: 'name',
 *              label: 'Name'
 *          }, {
 *              xtype: 'emailfield',
 *              name: 'email',
 *              label: 'Email'
 *          }, {
 *              xtype: 'passwordfield',
 *              name: 'password',
 *              label: 'Password'
 *          }]
 *      });
 *
 *      Ext.define('MyController', {
 *          extend: 'Ext.app.ViewController',
 *          alias: 'controller.mycontroller',
 *
 *          onSubmit: function () {
 *              var form = this.getView();
 *
 *              form.submit({
 *                  url: 'url/to/submit/to',
 *                  success: function () {
 *                      Ext.Msg.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} config when you create the form. We can
 * specify other parameters (see {@link #method!submit} for details), including callback
 * functions for success and failure. These functions are used to take some action in your
 * app after your data has been saved to the server side.
 *
 * When doing a form submit, each field's value is serialized using the serializer for the
 * {@link Ext.data.Model} used when `modelValidation` is enabled, or using the serializer
 * specified in the form field's {@link Ext.field.Field#dataType} config.
 */
Ext.define('Ext.form.Panel', {
    extend: 'Ext.field.Panel',
    xtype: 'formpanel',
    isFormPanel: true,
 
    mixins: [
        'Ext.mixin.FieldDefaults'
    ],
 
    alternateClassName: 'Ext.form.FormPanel',
 
    classCls: Ext.baseCSSPrefix + 'formpanel',
 
    element: {
        reference: 'element',
        tag: 'form',
        novalidate: 'novalidate'
    },
 
    /**
     * @event submit
     * @preventable
     * 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.
     * @param {Ext.event.Event} e The event object.
     */
 
    /**
     * @event beforesubmit
     * @preventable
     * Fires immediately preceding any Form submit action.
     * Implementations may adjust submitted form values or options prior to execution.
     * A return value of `false` 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 to be sent to
     * the server.
     * @param {Object} options Additional options (this is only available when
     * `standardSubmit` is `false`).
     * @param {Ext.event.Event} e The event object if the form was submitted via a HTML5
     * form submit event.
     */
 
    config: {
        /**
         * @cfg {Boolean} enableSubmissionForm
         * The submission form is generated but never added to the dom. It is a submittable
         * version of your form panel, allowing for fields that are not simple textfields
         * to be properly submitted to servers. It will also send values that are easier
         * to parse with server side code.
         *
         * If this is false we will attempt to subject the raw form inside the form panel.
         */
        enableSubmissionForm: true,
 
        /**
         * @cfg {String} enctype
         * The enctype attribute for the form, specifies how the form should be encoded
         * when submitting.
         */
        enctype: null,
 
        /**
         * @cfg {String} method
         * The method which this form will be submitted. `post` or `get`.
         */
        method: 'post',
 
        /**
         * @cfg {Boolean} multipartDetection
         * If this is enabled the form will automatically detect the need to use
         * 'multipart/form-data' during submission.
         */
        multipartDetection: true,
 
        /**
         * @cfg {Boolean} standardSubmit
         * Whether or not we want to perform a standard form submit.
         */
        standardSubmit: false,
 
        /**
         * @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.
         */
        submitOnAction: false,
 
        /**
         * @cfg {Boolean} trackResetOnLoad
         * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}
         * data instead of when the form was first created.
         */
        trackResetOnLoad: false
 
        /**
         * @cfg {Object} fieldDefaults
         * The properties in this object are used as default config values for field instance.
         */
 
        /**
         * @cfg {Boolean} jsonSubmit
         * If set to true, the field values are sent as JSON in the request body.
         * All of the field values, plus any additional params configured via
         * {@link #baseParams} and/or the `options` to {@link #submit},
         * will be included in the values POSTed in the body of the request.
         */
    },
 
    getTemplate: function() {
        var template = this.callParent();
 
        // Added a submit input for standard form submission. This cannot have "display: none;"
        // or it will not work
        template.push({
            tag: 'input',
            type: 'submit',
            cls: Ext.baseCSSPrefix + 'hidden-submit'
        });
 
        return template;
    },
 
    /**
     * @private
     */
    initialize: function() {
        this.callParent();
        this.element.on('submit', 'onSubmit', this);
    },
 
    applyEnctype: function(newValue) {
        var form = this.element.dom || null;
 
        if (form) {
            if (newValue) {
                form.setAttribute('enctype', newValue);
            }
            else {
                form.setAttribute('enctype');
            }
        }
    },
 
    /**
     * @private
     */
    onSubmit: function(event) {
        var me = this;
 
        if (event && !me.getStandardSubmit()) {
            event.stopEvent();
        }
        else {
            this.submit(null, event);
        }
    },
 
    updateSubmitOnAction: function(value) {
        this[value ? 'on' : 'un']({
            action: 'onFieldAction',
            scope: this
        });
    },
 
    /**
     * @private
     */
    onFieldAction: function(field) {
        if (this.getSubmitOnAction()) {
            field.blur();
            this.submit();
        }
    },
 
    /**
     * Performs a Ajax-based submission of form values (if {@link #standardSubmit} is false)
     * or otherwise executes a standard HTML Form submit action.
     *
     * **Notes**
     *
     *  1. Only the first parameter is implemented. Put all other parameters inside the first
     *  parameter:
     *
     *     submit({params: "" ,headers: "" etc.})
     *
     *  2. Submit example:
     *
     *     myForm.submit({
     *       url: 'PostMyData/To',
     *       method: 'Post',
     *       success: function () { Ext.Msg.alert("success"); },
     *       failure: function () { Ext.Msg.alert("error"); }
     *     });
     *
     *  3. Parameters and values only submit for a POST and not for a GET.
     *
     * @param {Object} options 
     * The configuration when submitting this form.
     *
     * The following are the configurations when submitting via Ajax only:
     *
     * @param {String} options.url 
     * The url for the action (defaults to the form's {@link #url}).
     *
     * @param {String} options.method 
     * The form method to use (defaults to the form's {@link #method}, or POST if not defined).
     *
     * @param {Object} options.headers 
     * Request headers to set for the action.
     *
     * @param {Boolean} [options.autoAbort=false] 
     * `true` to abort any pending Ajax request prior to submission.
     * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
     *
     * @param {Number} options.timeout 
     * The number is seconds the loading will timeout in.
     *
     * The following are the configurations when loading via Ajax or Direct:
     *
     * @param {String/Object} options.params
     * The params to pass when submitting this form (defaults to this forms {@link #baseParams}).
     * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
     *
     * @param {Boolean} [options.submitDisabled=false] 
     * `true` to submit all fields regardless of disabled state.
     * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
     *
     * @param {String/Object} [options.waitMsg]
     * If specified, the value which is passed to the loading {@link #masked mask}. See
     * {@link #masked} for more information.
     *
     * @param {Function} options.success 
     * 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 and can be used for submitting via
     * Ajax or Direct:
     *
     * @param {Ext.form.Panel} options.success.form 
     * The {@link Ext.form.Panel} that requested the action.
     *
     * @param {Object/Ext.direct.Event} options.success.result
     * The result object returned by the server as a result of the submit request. If the
     * submit is sent using Ext Direct, this will return the {@link Ext.direct.Event}
     * instance, otherwise will return an Object.
     *
     * @param {Object} options.success.data 
     * The parsed data returned by the server.
     *
     * @param {Function} options.failure 
     * The callback that will be invoked after a failed transaction attempt.
     *
     * The function is passed the following parameters and can be used for submitting via
     * Ajax or Direct:
     *
     * @param {Ext.form.Panel} options.failure.form 
     * The {@link Ext.form.Panel} that requested the submit.
     *
     * @param {Ext.form.Panel} options.failure.result 
     * The failed response or result object returned by the server which performed the
     * operation.
     *
     * @param {Object} options.scope 
     * The scope in which to call the callback functions (The `this` reference for the
     * callback functions).
     *
     * @param e
     *
     * @return {Ext.data.Connection} The request object if the {@link #standardSubmit}
     * config is false. If `standardSubmit` is `true`, then the return value is undefined.
     */
    submit: function(options, e) {
        var me = this,
            formValues, form;
 
        options = options || {};
 
        formValues = me.getSubmitValues({
            enabled: me.getStandardSubmit() || !options.submitDisabled
        });
        form = me.element.dom || {};
 
        if (this.getEnableSubmissionForm()) {
            form = this.createSubmissionForm(form, formValues);
        }
 
        options = Ext.apply({
            url: me.getUrl() || form.action,
            submit: false,
            form: form,
            method: me.getMethod() || form.method || 'post',
            autoAbort: false,
            params: null,
            waitMsg: null,
            headers: null,
            success: null,
            failure: null
        }, options || {});
 
        return me.fireAction('beforesubmit',
                             [me, formValues, options, e],
                             'doBeforeSubmit',
                             null,
                             null,
                             'after'
        );
    },
 
    privates: {
        /**
         * @private
         */
        applyExtraParams: function(options) {
            var form = options.form,
                params = Ext.merge(this.getBaseParams() || {}, options.params),
                name, input;
 
            for (name in params) {
                input = document.createElement('input');
                input.setAttribute('type', 'text');
                input.setAttribute('name', name);
                input.setAttribute('value', params[name]);
                form.appendChild(input);
            }
        },
 
        /**
         * @private
         */
        beforeAjaxSubmit: function(form, options, successFn, failureFn) {
            var me = this,
                url = options.url || me.getUrl(),
                request = Ext.merge({}, {
                    url: url,
                    timeout: me.getTimeout() * 1000,
                    form: form,
                    scope: me
                }, options),
                formValues = {},
                jsonSubmit = me.jsonSubmit,
                paramsKey = 'params',
                contentType = 'x-www-form-urlencoded; charset=UTF-8',
                original, placeholder, formData;
 
            delete request.success;
            delete request.failure;
 
            if (jsonSubmit) {
                paramsKey = 'jsonData';
                contentType = 'json';
                formValues = me.getSubmitValues({
                    enabled: me.getStandardSubmit() || !options.submitDisabled
                });
                delete request.params;
                delete request.form;
            }
 
            request[paramsKey] = Ext.merge({}, me.getBaseParams(), options.params, formValues);
            request.header = Ext.apply({
                'Content-Type': 'application/' + contentType
            }, options.headers || {});
 
            request.callback = function(callbackOptions, success, response) {
                var responseText = response.responseText,
                    responseXML = response.responseXML,
                    statusResult = Ext.data.request.Ajax.parseStatus(response.status, response);
 
                if (form.$fileswap) {
                    Ext.each(form.$fileswap, function(item) {
                        original = item.original;
                        placeholder = item.placeholder;
 
                        placeholder.parentNode.insertBefore(original, placeholder.nextSibling);
                        placeholder.parentNode.removeChild(placeholder);
                    });
 
                    form.$fileswap = null;
                    delete form.$fileswap;
                }
 
                me.setMasked(false);
 
                if (response.success === false) {
                    success = false;
                }
 
                if (success) {
                    if (statusResult && responseText && responseText.length === 0) {
                        success = true;
                    }
                    else {
                        if (!Ext.isEmpty(response.responseBytes)) {
                            success = statusResult.success;
                        }
                        else {
                            if (
                                Ext.isString(responseText) &&
                                response.request.options.responseType === 'text'
                            ) {
                                response.success = true;
                            }
                            else if (Ext.isString(responseText)) {
                                try {
                                    response = Ext.decode(responseText);
                                }
                                catch (e) {
                                    response.success = false;
                                    response.error = e;
                                    response.message = e.message;
                                }
                            }
                            else if (Ext.isSimpleObject(responseText)) {
                                response = responseText;
                                Ext.applyIf(response, { success: true });
                            }
 
                            if (!Ext.isEmpty(responseXML)) {
                                response.success = true;
                            }
 
                            success = !!response.success;
                        }
                    }
 
                    if (success) {
                        successFn(response, responseText);
                    }
                    else {
                        failureFn(response, responseText);
                    }
                }
                else {
                    failureFn(response, responseText);
                }
            };
 
            if (Ext.feature.has.XHR2 && request.xhr2) {
                delete request.form;
 
                formData = request.data = new FormData(form);
 
                if (request.params) {
                    Ext.iterate(request.params, function(name, value) {
                        if (Ext.isArray(value)) {
                            Ext.each(value, function(v) {
                                formData.append(name, v);
                            });
                        }
                        else {
                            formData.append(name, value);
                        }
                    });
 
                    delete request.params;
                }
            }
 
            return Ext.Ajax.request(request);
        },
 
        /**
         * @private
         */
        beforeDirectSubmit: function(api, form, options, successFn, failureFn) {
            var me = this,
                submit;
 
            me.applyExtraParams(options);
 
            api = Ext.direct.Manager.resolveApi(api, me);
            me.setApi(api);
 
            submit = api.submit;
 
            if (!submit) {
                Ext.raise('Cannot find Ext Direct API method for submit action');
            }
 
            return submit(form, function(data, response, success) {
                me.setMasked(false);
 
                if (success) {
                    if (data.success) {
                        successFn(response, data);
                    }
                    else {
                        failureFn(response, data);
                    }
                }
                else {
                    failureFn(response, data);
                }
            }, me);
        },
 
        /**
         * @private
         */
        beforeStandardSubmit: function(form, options) {
            var field, fields, ln, body, i;
 
            if (options.url && Ext.isEmpty(form.action)) {
                form.action = options.url;
            }
 
            // Spinner fields must have their components enabled *before*
            // submitting or else the value will not be posted.
            fields = this.query('spinnerfield');
            ln = fields.length;
            body = document.body;
 
            for (= 0; i < ln; i++) {
                field = fields[i];
 
                if (!field.getDisabled()) {
                    field.setDisabled(false);
                }
            }
 
            body.appendChild(form);
 
            form.method = (options.method || form.method).toLowerCase();
            form.submit();
 
            // If the form is targeting a different DOM such as an iframe, then this
            // resource will remain in the current body since it does not reload upon
            // submit so we need to explicitly remove it.
            body.removeChild(form);
        },
 
        /**
         * @private
         */
        createSubmissionForm: function(form, values) {
            var fields = this.getFields(),
                name, input, field, fileTrigger, inputDom;
 
            if (form.nodeType === 1) {
                form = form.cloneNode(false);
 
                for (name in values) {
                    input = document.createElement('input');
                    input.setAttribute('type', 'text');
                    input.setAttribute('name', name);
                    input.setAttribute('value', values[name]);
                    form.appendChild(input);
                }
            }
 
            for (name in fields) {
                if (fields.hasOwnProperty(name)) {
                    field = fields[name];
 
                    if (field.isFile) {
                        // The <input type="file"> of a FileField is its "file" trigger button.
                        fileTrigger = field.getTriggers().file;
                        inputDom = fileTrigger && fileTrigger.getComponent().buttonElement.dom;
 
                        if (inputDom) {
                            if (!form.$fileswap) {
                                form.$fileswap = [];
                            }
 
                            input = inputDom.cloneNode(true);
                            inputDom.parentNode.insertBefore(input, inputDom.nextSibling);
                            form.appendChild(inputDom);
                            form.$fileswap.push({ original: inputDom, placeholder: input });
                        }
                    }
                    else if (field.isPassword) {
                        if (field.getInputType() !== 'password') {
                            field.setRevealed(false);
                        }
                    }
                }
            }
 
            return form;
        },
 
        /**
         * @private
         */
        doBeforeSubmit: function(me, formValues, options) {
            var form = options.form || {},
                multipartDetected = false,
                api, scope, ret, successFn, failureFn, waitMsg;
 
            if (this.getMultipartDetection() === true) {
                this.getFields(false).forEach(function(field) {
                    if (field.isFile === true) {
                        multipartDetected = true;
 
                        return false;
                    }
                });
 
                if (multipartDetected) {
                    form.setAttribute('enctype', 'multipart/form-data');
                }
            }
 
            if (options.enctype) {
                form.setAttribute('enctype', options.enctype);
            }
 
            if (me.getStandardSubmit()) {
                ret = me.beforeStandardSubmit(form, options);
            }
            else {
                api = me.getApi();
                scope = options.scope || me;
 
                failureFn = function(response, responseText) {
                    if (Ext.isFunction(options.failure)) {
                        options.failure.call(scope, me, response, responseText);
                    }
 
                    me.fireEvent('exception', me, response);
                };
 
                successFn = function(response, responseText) {
                    if (Ext.isFunction(options.success)) {
                        options.success.call(options.scope || me, me, response, responseText);
                    }
 
                    me.fireEvent('submit', me, response);
                };
 
                waitMsg = options.waitMsg;
 
                if (options.waitMsg) {
                    if (typeof waitMsg === 'string') {
                        waitMsg = {
                            xtype: 'loadmask',
                            message: waitMsg
                        };
                    }
 
                    me.setMasked(waitMsg);
                }
 
                if (api) {
                    ret = me.beforeDirectSubmit(api, form, options, successFn, failureFn);
                }
                else {
                    ret = me.beforeAjaxSubmit(form, options, successFn, failureFn);
                }
            }
 
            return ret;
        }
    }
});