/**
 * Advanced form handling.
 *
 * The `.uiForm()` method add advanced form handling, like ajax or validate, to a form.
 *
 * Default handling:
 *
 *  + prevent submit if no action attribute givn
 *  + prevent submit if an element of the form invalid
 *
 * ### Examples
 * Apply .uiForm() to all forms and set an ajax handle for submit.
 *
 * JavaScript:
 *
 *     ep('form')
 *         .uiForm({
 *             ajax: {
 *                 success: function( successText ){
 *                     alert( successText );
 *                 },
 *                 error: function( errorText ){
 *                     alert( errorText );
 *                 }
 *             }
 *         });
 *
 *
 * @class jQuery.ui.uiForm
 *
 * @uses ep
 * @uses ep.ajax
 * @uses jQuery.fn.form
 * @uses jQuery.ui.widget
 * @since 6.11.0
 */

/**
 * Set the validity state of the form to `true` (valid) or `false` (invalid).
 *
 * @param {Boolean} [valid] A boolean indication whether to set the valid state of the form.
 *
 * @method setValid
 * @member jQuery.ui.uiForm
 *
 * @since 6.11.0
 */

/**
 * @cfg {Boolean} compareDiff A boolean indication wether to submit changed form only.
 */

/**
 * @cfg {Boolean} showSaveWarn A boolean indication wether to show a warning when leaving a page without saving.
 */

/**
 * @cfg {Object} ajax A map of ajax options to send form via ajax. If no url givn the action attribute will be used as url, same works for type/method.
 */

/**
 * See `jQuery.ui.uiForm` for details.
 *
 * @param {Object} [options] A map of additional options pass to the method.
 * @param {Boolean} compareDiff A boolean indication wether to submit changed form only.
 * @param {Boolean} showSaveWarn A boolean indication wether to show a warning when leaving a page without saving.
 * @param {Object} ajax A map of ajax options to send form via ajax. If no url givn the action attribute will be used as url, same works for type/method.
 *
 * @method uiForm
 * @member jQuery
 *
 * @since 6.11.0
 */

/*
 * @copyright		© Copyright 2006-2012, epages GmbH, All Rights Reserved.
 *
 * @module			ep.ui.form
 *
 * @revision		$Revision: 1.21 $
 */

define("ep/ui/form", [
    "jquery",
    "ep",
    "$dict!ep/dict",

    "jquery/ui/widget",
    "jquery/cookie",
    "jquery/fn/form",
    "ep/ajax"
], function ($, ep, epDict) {

    /*
     *
     * @dictionary		ep/dict
     *
     * @translation		{LoseChanges}
     *
     */

    var
    // jQuery window object
        win = $(window),

        tLoseChanges = epDict.translate("LoseChanges"),

        // Hash serializer
        serializeArray = function (hash) {
            var arr = [];

            $.each(hash, function (name, value) {
                arr.push({
                    name: name,
                    value: value
                });
            });

            return arr;
        };

    $.widget('ui.uiForm', {

        options: {
            showSaveWarn: false,
            tokenName: "SecToken"
        },

        _create: function () {
            var self = this,
                o = self.options,
                cookies = $.cookie();

            self.elem = ep(self.element)
                .on('change.uiForm', $.proxy(self, '_change'))
                .on('submit.uiForm', $.proxy(self, '_submit'))
                .on('validate.uiForm', $.proxy(self, '_validate'))
                .on('reset.uiForm', $.proxy(self, '_reset'))
                .on('mousedown', ':submit', $.proxy(self, '_submitButtonFix'));

            // Add security token
            if (cookies[o.tokenName] && self.elem.find("input[name='" + o.tokenName + "']").length === 0) {
                self.elem.append('<input type="hidden" name="' + o.tokenName + '" value="' + cookies[o.tokenName] + '" />');
            }

            self.timeStamp = $.now();

            //find all saveButtons and change label and class if page was reloaded after save
            self.setSaveButtonSaved();
        },

        // Validate all input elemenst of form
        _validate: function (event) {
            var self = this;

            if (!self.elem.attr('formnovalidate') && event.target === self.elem[0]) {
                var elems = self.elem
                    .formInput();

                elems.trigger('validate')
                    .filter(':invalid:first')
                    .focus()
                    .trigger('validate');

                self._change();
            }
        },

        // Public get valid state
        /**
         * Get a boolean indication wether the elements of the form are valid.
         *
         * @method isValid
         * @member jQuery.ui.uiForm
         *
         * @since 6.11.0
         */
        isValid: function () {
            return this.elem.attr('formnovalidate') ? true : !this.elem[0].formInvalid;
        },

        // Public set valid state by boolean value
        setValid: function (stat) {
            this.elem[0].formInvalid = !stat;
        },

        // Set valid state by checking input elements
        _setValid: function (elems) {
            this.setValid(!elems.filter(':invalid').length);
        },

        // Check changed state by input elements
        _isChanged: function (elems) {
            return !!elems
                .filter(':changed')
                .length;
        },

        _submitButtonFix: function (event) {
            var self = this,
                context = self._submitButtonFix;

            clearTimeout(context.timeout);

            // Reset
            if (event === true && context.input) {
                context.input.remove();
            }
            // Set to remember
            else if (event) {
                context.button = $(event.target);
            }
            // Fix button context by hidden input
            else if (!event && context.button) {
                context.input = $("<input type=\"hidden\">")
                    .attr({
                        name: context.button.attr("name"),
                        // EPG-36268
                        // If problems occure maybe this is the better solution
                        // value: context.button.val()
                        value: context.button.prop("value")
                    })
                    .appendTo(self.elem);

                // Remove the hidden button fix element with a short delay
                context.timeout = setTimeout(function () {
                    self._submitButtonFix(true);
                }, 300);
            }
        },

        // Handle submit check and AJAX submits
        _submitCheck: function (event) {
            var self = this,
                o = self.options,
                valid = self.isValid(),
                action = self.elem.attr('action'),
                ajax = self.options.ajax,
                isPrevented;

            // Prevent default if invalid, double submit or form without action
            if ((!action && !ajax) || !valid || (event.timeStamp - self.timeStamp) < 300) {
                event.preventDefault();
            }

            // Reset reference time for double call prevention
            self.timeStamp = event.timeStamp;

            // Detect prevented state to handle submit done / fail
            isPrevented = event.isDefaultPrevented();

            if (!isPrevented) {

                // Remove warning before page leave
                if (o.showSaveWarn) {
                    win.off('beforeunload.uiForm', self._beforeunload);
                }

                // Submit form via AJAX
                if (ajax) {
                    // Prevent default without handle submitFail
                    event.preventDefault();

                    // AJAX send handler
                    var send = function (settings) {
                        var
                        // Local for data
                            data = settings.data || [],

                            // AJAX default settings
                            defaults = {
                                // Use action attrigute as url
                                url: action || '',
                                // Use method attribute as type
                                type: self.elem.attr('method') || 'post',
                                // Add form input data
                                data: self.elem.serializeArray() || []
                            };

                        // Remove original data (use serialized)
                        if (settings.data) {
                            delete settings.data;
                        }

                        // Serialize for mergable data array
                        if (!$.isArray(data)) {
                            data = serializeArray(data);
                        }

                        // Build AJAX settings
                        $.extend(true, defaults, settings);

                        // Merge data arrays
                        $.merge(defaults.data, data);

                        // Return jqXHR promise
                        return ep.ajax(defaults)
                            .done(function () {
                                self.elem.trigger('submitDone');
                            })
                            .fail(function () {
                                self.elem.trigger('submitFail');
                            });
                    };

                    if ($.isFunction(ajax)) {
                        ajax(send);
                    } else {
                        send(ajax);
                    }
                }
            }

            // Fix submit buttons values
            self._submitButtonFix();

            // Trigger done / fail (if not AJAX)
            if (!ajax) {
                self.elem.trigger(isPrevented ? 'submitFail' : 'submitDone');
            }

            // Remove check handler
            self.elem.off('submit.uiForm', $.proxy(self, '_submitCheck'));
        },

        // Submit event pre controller
        _submit: function (event) {
            var self = this,
                o = self.options;

            // Control last event handle of submit call
            if (!event.uiFormTriggert) {

                // This way other *submit* event handlers can expect the validation to be done.
                self.elem.trigger('validate');

                // Actually set *formInvalid* attribute on *element*.
                self._setValid(self.elem.formInput());

                // Stop event
                event.stopImmediatePropagation();
                event.preventDefault();

                // Save autocomplete for IE
                if (window.external && typeof window.external.AutoCompleteSaveForm === 'function') {
                    window.external.AutoCompleteSaveForm(self.elem[0]);
                }

                // Add sumbit check as last event handle and trigger submit again
                self.elem
                    .on('submit.uiForm', $.proxy(self, '_submitCheck'))
                    .trigger({
                        type: 'submit',
                        uiFormTriggert: true
                    });
            }
        },

        // Reset form inputs
        _reset: function (event) {
            this.elem
                .formInput()
                .each(function () {
                    $(this).triggerHandler('reset');
                });
        },

        // Handle leave warning before leaving the page
        _change: function () {
            var self = this;

            //reset button state and text if form was changed
            var buttons = self.elem.
            formInput()
                .filter('.Saved');
            if (ep.config.saved) {
                buttons.each(function (i) {
                    var node = $(this),
                        originalText = node.data('originalText');
                    if (node.hasClass('Saved') && originalText) {
                        node
                            .addClass('SaveButton')
                            .removeClass('Saved')
                            .text(node.data('originalText'));
                    }
                });
            }

            if (self.options.showSaveWarn) {
                if (self._isChanged(self.elem.formInput())) {
                    win.on('beforeunload.uiForm', $.proxy(self._beforeunload, self));
                } else {
                    win.off('beforeunload.uiForm', $.proxy(self._beforeunload, self));
                }
            }
        },

        // Just the before page leave event callback
        _beforeunload: function () {
            return this.options.showSaveWarn ? tLoseChanges : undefined;
        },

        // Public set SaveButton class to Saved
        setSaveButtonSaved: function () {
            var self = this,
                buttons = self.elem.
            formInput()
                .filter('.SaveButton');

            if (ep.config.saved) {
                buttons.each(function (i) {
                    var node = $(this),
                        originalText = node.text();

                    node
                        .addClass('Saved')
                        .removeClass('SaveButton')
                        .data('originalText', originalText);

                    if (ep.config.saveButtonChangeMap && ep.config.saveButtonChangeMap[originalText]) {
                        node.text(ep.config.saveButtonChangeMap[originalText]);
                    }
                });
            }
        }

    });

    return ep;

});