(function($) {
    var delegate = $.ajax;

    var SETTINGS = {
        loginFormURL: undefined,
        loginPageURL: undefined,
        decorator: 'simple',
        responseTimeout: 15000,
        i18n: {
            loginFailureText: 'Login failed',
            dialogTitle: 'Login',
            loginButtonLabel: 'Login',
            cancelButtonLabel: 'Cancel'
        }
    };

    var DEFERRED;

    function showLoginDialog(successCallback, errorCallback) {
        if (!DEFERRED) {
            DEFERRED = $.Deferred();
            DEFERRED.always(function() {
                DEFERRED = null;
            });
            (function(deferred) {
                var control, loginSuccess;
                $.fn.lightBoxAjax({
                    title: SETTINGS.i18n.dialogTitle,
                    url: SETTINGS.loginFormURL,
                    dialogOptions: {
                        buttons: [{
                            type: 'primary',
                            text: SETTINGS.i18n.loginButtonLabel,
                            click: function() {
                                control && control.submit();
                            }
                        }, {
                            text: SETTINGS.i18n.cancelButtonLabel,
                            click: function() {
                                $(this).lightBoxAjax("close");
                            }
                        }],
                        close: function() {
                            control && control.doCleanUp && control.doCleanUp();
                            if (!loginSuccess) {
                                deferred.reject();
                            }
                        }
                    },
                    ajaxOptions: {
                        noIntercept: true,
                        success: function() {
                            // AJAX LightBox Content loaded ..
                            var dialogBox = this;
                            control = initLoginForm(dialogBox, function () {
                                loginSuccess = true;
                                dialogBox.lightBoxAjax("close");
                                deferred.resolve();
                            });
                        }
                    }
                });
            })(DEFERRED);
        }
        DEFERRED.then(successCallback, errorCallback);
    }

    function initLoginForm(dialogBox, successCallback) {
        var control;
        var form = dialogBox.find('form');
        if (!form.length) {
            $.error('Error: No form found in page fetched from login form URL: ' + SETTINGS.loginFormURL);
        }
        var url = form[0].action; // Returns entire URL for IE8+/Chrome/FF/...
        var regex = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/i;
        var parts = regex.exec(url);
        //console.log(url, parts);
        if (!window.location.origin) { // IE < 10 hack ...
            window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
        }
        var formOrigin = parts ? parts[0] : window.location.origin;
        if (formOrigin == window.location.origin) {
            control = initAjaxFormSubmission(form, dialogBox, successCallback);
        } else {
            // Same origin policy problem -> AJAX login not possible!
            // ------------------------------------------------------
            if (location.hostname != parts[2]) {
                $.error('Error: Cannot login on other host: Host: ' + location.hostname + ', Login Host: ' + parts[2]);
            }
            control = initIFrameFormSubmission(form, url, formOrigin, dialogBox, successCallback);
        }
        form.find('input[type="text"]:first').focus();
        // Use key events [ENTER] to perform primary action (-> submit form) ..
        form.on('keyup.ajaxlogin', function(e) {
            if (e.keyCode == 13 /*ENTER*/) {
                dialogBox.lightBoxAjax('primaryAction');
            }
        });
        return control;
    }

    function initAjaxFormSubmission(form, dialogBox, successCallback) {
        var submissionActive;
        return form.on('submit', function (e) {
            e.preventDefault();
            e.stopImmediatePropagation();
            if (!submissionActive) {
                submissionActive = true;
                var globalErrorContainer = form.find('.form-group .alert-danger');
                globalErrorContainer.empty().hide();
                var data = form.serializeArray();
                var url = form.attr('action');
                var method = form.attr('method') || 'POST';
                // Block UI ..
                dialogBox.lightBoxAjax('blockUI');
                var fields = form.find(':input:enabled').prop('disabled', true);
                // Try to login ...
                delegate({
                    url: url,
                    type: method,
                    data: data,
//                    timeout: SETTINGS.responseTimeout,
                    success: successCallback,
                    error: function () {
                        globalErrorContainer.text(SETTINGS.i18n.loginFailureText).show();
                        // Unblock UI ..
                        fields.prop('disabled', false);
                        dialogBox.lightBoxAjax('unblockUI');
                        form.find('input[type="text"]:first').focus();
                    },
                    complete: function () {
                        submissionActive = false;
                    }
                });
            }
        });
    }

    function initIFrameFormSubmission(form, url, formOrigin, dialogBox, successCallback) {
        var submissionActive;
        // Create an iFrame for form submission ..
        var iFrameName = '_ajax_login_frame_';
        var iFrame = $('<iframe width="1" height="1"/>').attr('name', iFrameName).appendTo($(document.body));
        // Make sure frame submission target is the iFrame ..
        form.attr('target', iFrameName);
        // Use minimal decoration ..
        if (SETTINGS.decorator) {
            form.attr('action', url + (url.indexOf('?') != -1 ? '&' : '?') + "decorator=" + encodeURIComponent(SETTINGS.decorator));
        }
        var fields;
        var globalErrorContainer;
        var timeout;
        var fieldSet;
        // Mark this form as iFrameLogin-form ..
        $('<input type="hidden" name="iFrameLogin" value="true"/>').appendTo(form);
        // Handle form submission ..
        form.on('submit', function(e) {
            if (submissionActive) {
                e.preventDefault();
                return;
            }
            submissionActive = true;
            globalErrorContainer = form.find('.form-group .alert-danger');
            globalErrorContainer.empty().hide();
            // Copy data to submit ..
            var data = form.serializeArray();
            // Block UI ..
            dialogBox.lightBoxAjax('blockUI');
            fields = form.find(':input:enabled').prop('disabled', true);
            // Create hidden fields for data since disabled fields are not submitted!!
            fieldSet = $('<fieldset>');
            $.each(data, function() {
                $('<input type="hidden"/>').attr('name', this.name).val(this.value).appendTo(fieldSet);
            });
            fieldSet.appendTo(form);
            // Register message listener ..
            $(window).on('message', messageHandler);
            // Limit waiting time for response ..
            timeout = setTimeout(function() {
                fail();
            }, SETTINGS.responseTimeout);
        });

        function fail() {
            done();
            // Remove temporary fields ..
            fieldSet.remove();
            // Show error text ..
            globalErrorContainer.text(SETTINGS.i18n.loginFailureText).show();
            // Mark submission a inactive ..
            submissionActive = false;
            // Unblock UI ..
            dialogBox.lightBoxAjax('unblockUI');
            fields.prop('disabled', false);
            form.find('input[type="text"]:first').focus();
        }

        function messageHandler(e) {
            var messageEvent = e.originalEvent;
            if (messageEvent.origin == formOrigin) {
                //console.log('Matches form origin: ' + formOrigin);
                e.preventDefault();
                e.stopImmediatePropagation();
                if (messageEvent.data == 'success') {
                    done();
                    successCallback();
                } else {
                    fail();
                }
            }
        }

        function done() {
            // Clear timeout ..
            clearTimeout(timeout);
            timeout = null;
            // Unregister message listener ..
            $(window).off('message', messageHandler);
        }

        return {
            submit: function() {
                form.submit();
            },
            doCleanUp: function() {
                iFrame.remove();
            }
        };
    }

    function wrapAJAXObject(jqXHR, deferred) {
        //jqXHR.done(function( data, textStatus, jqXHR ) {}); -> also mapped to "success"
        //jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {}); -> also mapped to "error"
        //jqXHR.always(function( data|jqXHR, textStatus, jqXHR|errorThrown ) { }); -> also mapped to "done"
        //jqXHR.then(function( data, textStatus, jqXHR ) {}, function( jqXHR, textStatus, errorThrown ) {});
        return $.extend(jqXHR, {
            success: function(success) {
                deferred.done(success);
                return this;
            },
            error: function(error) {
                deferred.fail(error);
                return this;
            },
            done: function(done) {
                deferred.always(done);
                return this;
            },
            fail: function(error) {
                deferred.fail(error);
                return this;
            },
            always: function(done) {
                deferred.always(done);
                return this;
            },
            then: function(success, error) {
                deferred.then(success, error);
                return this;
            },
            pipe: function(success, error) {
                deferred.then(success, error);
                return this;
            }
        });
    }

    function patchAjax() {
        $.ajax = function (url, options) {
            var context = this;
            var originalRequestArguments = Array.prototype.slice.call(arguments, 0);
            if (typeof url === "object") {
                options = url;
                url = undefined;
            }
            var xOptions = jQuery.ajaxSetup({}, options || {});
            if (!xOptions.noIntercept) {
                // The request may be intercepted :)
                url && (xOptions.url = url);
                if (xOptions.async) {
                    // Async requests ..
                    var deferred = $.Deferred();
                    xOptions.error && deferred.fail(xOptions.error);
                    xOptions.success && deferred.done(xOptions.success);
                    xOptions.complete && deferred.always(xOptions.complete);
                    xOptions.complete = null;
                    xOptions.success = function () {
                        deferred.resolve.apply(deferred, arguments);
                    };
                    xOptions.error = function (jqXHR, textStatus) {
                        if (jqXHR.status == '401') {
                            // User is not logged in ..
                            var originalErrorArguments = Array.prototype.slice.call(arguments, 0);
                            showLoginDialog(function () {
                                // User is now logged in ..
                                // -> Attach error/success/complete/done/fail/always handlers from wrapped AJAXObject ..
                                delegate.apply(context, originalRequestArguments).then(
                                    function () {
                                        deferred.resolve.apply(deferred, arguments);
                                    },
                                    function () {
                                        deferred.reject.apply(deferred, arguments);
                                    }
                                );
                            }, function () {
                                // User cancelled login dialog ..
                                // -> Notify error/complete/fail/always handlers from wrapped AJAXObject ..
                                deferred.reject.apply(deferred, originalErrorArguments);
                            })
                        } else {
                            // Not a 401 error ..
                            // -> Notify error/complete/fail/always handlers from wrapped AJAXObject ..
                            deferred.reject.apply(deferred, arguments);
                        }
                    };
                    // Delegate ...
                    return wrapAJAXObject(delegate.apply(this, [xOptions]), deferred);
                } else if (SETTINGS.loginPageURL) {
                    // Synchronous request ..
                    return delegate.apply(this, [xOptions]).error(function(jqXHR) {
                        if (jqXHR.status == '401') {
                            // Not logged in -> Redirect to login page ..
                            window.location = SETTINGS.loginPageURL.replace(/\{url}/, encodeURIComponent(window.location));
                        }
                    });
                }
            }
            // Simply forward to original $.ajax ..
            return delegate.apply(this, originalRequestArguments);
        }
    }

    window.AjaxLogin = {
        activate: function(settings) {
            $.extend(true, SETTINGS, settings);
            if (!SETTINGS.loginFormURL) {
                SETTINGS.loginFormURL = Application.contextPath() + '/ajaxLogin';
            }
            patchAjax();
        }
    }
})(jQuery);