Ext.namespace('Ext.ux.J2EEAuth');
Ext.apply(Ext.ux.J2EEAuth, {
  loginActionConfig: {
    text: 'Login',
    tooltip: {
      title: 'Login',
      text: 'Log in using the provided credentials.'
    }
  },

  validationErrorTitle: 'Validation error',
  validationErrorText: 'Marked fields are required.',
  blankText: 'This field is required.',

  windowTitle: 'Login',
  formAuthenticationURL: 'j_security_check',
  usernameFieldLabel: 'Username',
  passwordFieldLabel: 'Password',
  usernameFieldName: 'j_username',
  passwordFieldName: 'j_password',

  protectedResourceConfig: {
    url: 'authTest.jsp'
  },

  triggerAuth: function(config) {
    var requestConfig = Ext.apply({}, config, Ext.ux.J2EEAuth.protectedResourceConfig);
    Ext.Ajax.request(requestConfig);
  },

	successCallback: false
});

Ext.ux.J2EEAuth.LoginWindow = function() {

  var maskedAjaxId;
  var loginForm;

  /***************************************************
   * Handlers
   */

  function loginHandler() {
    if (!loginForm.form.isValid()) {
      Ext.Msg.show({
        title: Ext.ux.J2EEAuth.validationErrorTitle,
        msg: Ext.ux.J2EEAuth.validationErrorText,
        icon: Ext.MessageBox.ERROR,
        buttons: Ext.MessageBox.OK
      });
      return;
    }

    loginForm.form.submit({
      url: Ext.ux.J2EEAuth.formAuthenticationURL,
      success: function() {alert("success");},
      failure: function() {alert("failure");},
      params: {
        _auth_: true,
        maskedAjaxId: maskedAjaxId
      }
    });
    this.hide();
  }

  /***************************************************
   * Actions
   */

  var loginAction = new Ext.Action(Ext.apply({ },
    { handler: loginHandler, scope: this },
    Ext.ux.J2EEAuth.loginActionConfig));
  
  var cancelButton = new Ext.Button({
  	id	: 'cancel',
  	name: 'cancel',
  	text: Ext.ux.J2EEAuth.cancelButtonText,
  	handler: function(button, event) {
  		authInProgress = false;
  		loginWindow.close();
  		loginWindow = undefined;
  	}
  });

  /***************************************************
   * GUI
   */

  var usernameField = new Ext.form.TextField({
    fieldLabel: Ext.ux.J2EEAuth.usernameFieldLabel,
    name: Ext.ux.J2EEAuth.usernameFieldName,
    anchor: '90%',
    value: '',
    allowBlank: false,
    blankText: Ext.ux.J2EEAuth.blankText,
    selectOnFocus: true,
    regex: /\S/i
  });

  var passwordField = new Ext.form.TextField({
    fieldLabel: Ext.ux.J2EEAuth.passwordFieldLabel,
    inputType: 'password',
    name: Ext.ux.J2EEAuth.passwordFieldName,
    anchor: '90%',
    value: '',
    allowBlank: false,
    blankText: Ext.ux.J2EEAuth.blankText,
    selectOnFocus: true
  });

  loginForm = new Ext.form.FormPanel({
    baseCls: 'x-plain',
    labelAlign: 'top',
    labelWidth: 55,
    defaultType: 'textfield',

    items: [usernameField, passwordField]
  });

  /***************************************************
   * Public methods
   */

  this.show = function(id) {
    maskedAjaxId = id;
    Ext.ux.J2EEAuth.LoginWindow.superclass.show.call(this);

    passwordField.reset();
    usernameField.focus();

    try {
      loginForm.form.isValid();
    } catch(e3) {
      // Internet Explorer bug, as usual
    }
  };

  /***************************************************
   * Constructor
   */

  Ext.ux.J2EEAuth.LoginWindow.superclass.constructor.call(this, {
    layout: 'fit',
    width: 348,
    height: 200,
    //closeAction: 'hide',
    closable: false,
    plain: true,
    title: Ext.ux.J2EEAuth.windowTitle,
    bodyStyle: 'padding: 8px;',
    modal: true,
    items: loginForm,

    buttons: [cancelButton, loginAction]
  });

  /***************************************************
   * Hooks.
   */

  usernameField.on('specialkey', function(f, e) {
    if (e.getKey() == e.ENTER) {
      passwordField.focus();
    }
  }, this);

  passwordField.on('specialkey', function(f, e) {
    if (e.getKey() == e.ENTER) {
      loginHandler.call(this);
    }
  }, this);
};
Ext.extend(Ext.ux.J2EEAuth.LoginWindow, Ext.Window);

/**
 * Override default behavior in Ext.Ajax.
 */
(function() {
  authInProgress = false;
  loginWindow = undefined;
  ajaxRequest = Ext.Ajax.request;
  pendingRequests = [];
  var savedOptions = new Ext.util.MixedCollection(true, function(o) {
    return o.maskedAjaxId;
  });

  /**
   * Masked request method, to be called instead of the one from Ext.Ajax
   */
  function maskedRequest(o) {
    /* Check that we are not trying to authenticate using the LoginWindow request */
    var authAttempt;
    try {
      authAttempt = o.scope.options.params._auth_;
    } catch(e) {
      authAttempt = false;
    }

    /* Any external request while authenticating are queued to be sent afterwards */
    if (authInProgress === true && !authAttempt) {
      pendingRequests.push(o);
      return;
    }

    /* If this request is considered "original", save it back for later.
     * All requests are marked so that we can distinguish the one we need to
     * resend in later stages.
     */
    if (!authAttempt) {
      o.maskedAjaxId = Ext.id();
      var newObj = Ext.apply({}, o);
      savedOptions.add(newObj);
    } else {
      o.maskedAjaxId = o.scope.options.params.maskedAjaxId;
    }

    /**
     * Callback for the masked requests.
     */
    function callback(options, success, response) {
      var authRequest = false;
		
      /* If it was a successful request, check the "authentication required" flag */
      if (success === true) {
        try {
          var result = Ext.decode(response.responseText);
          authRequest = (result.authRequest === 'true');
        } catch(e1) {
          authRequest = false;
        }
      } else if (response.status === 403) {
        /**
         * At this point, the server found an unauthorized attempt to use the system and at the same time
         * the POSTed data was large enough to prevent the server to cache it. The server's authentication
         * mechanism didn't start so we need to trigger it. This is because at this point the Tomcat servers (and others???)
         * have lost the callback URL (remember it was not cached). If we tried to log in directly and we succeeded,
         * we would be redirected back to the very same login URL and this is an error condition in Tomcat.
         * The trick is to save back the original POST, then issue a normal access to a well-known protected resource.
         * This will allow the server to cache something and will prevent the error to be thrown. Of course, the forced
         * access' result is to be discarded. Once succeeded, the saved-back original POST can be sent again.
         *
         * The current "options" object is altered by the masking procedure, so we need to recover the original one and
         * to push it onto the pending requests to be processed later.
         */
        options = savedOptions.removeKey(options.maskedAjaxId);
        pendingRequests.unshift(options);

        /* Request protected resource in order to trigger the authentication mechanism. */
        Ext.ux.J2EEAuth.triggerAuth();
        return;
      }

      /* If we got an authentication request, raise the login window. */
      if (authRequest) {
        authInProgress = true;

        /* Since the requests will come asynchronously, we pass them the
         * generated id so that we can distinguish them.
         */
        if (!loginWindow) {
          loginWindow = new Ext.ux.J2EEAuth.LoginWindow();
        }
        loginWindow.show(options.maskedAjaxId);
      } else {
        var successCallback = (authInProgress ? Ext.ux.J2EEAuth.successCallback : false);
        authInProgress = false;

        /* Restore the original options object. */
        options = savedOptions.removeKey(options.maskedAjaxId);
        if (!response.argument) {
        	response.argument = options.argument;
        }

        /* Chain the original handlers. */
        if (success === null || response === null) {
          Ext.callback(options.callback, options.scope, [options, null, null]);
        }
        if (success) {
          Ext.callback(options.success, options.scope, [response, options]);
          Ext.callback(options.callback, options.scope, [options, true, response]);
        } else {
          Ext.callback(options.failure, options.scope, [response, options]);
          Ext.callback(options.callback, options.scope, [options, false, response]);
        }

        /* Clear the cached pending requests and relaunch them. */
        var oldPendingRequests = pendingRequests;
        pendingRequests = [];
        for (var i=0; i<oldPendingRequests.length; i++) {
          var opts = oldPendingRequests[i];
          try {
            maskedRequest.call(opts.scope || this, opts);
          } catch(e2) {
            // Log this
          }
          oldPendingRequests[i] = undefined;
        }

				if (successCallback) {
					successCallback.fn.call(successCallback.scope);
				}
      }
    }

    /* Intercept the original callback handler. */
    o.callback = callback;
    delete o.success;
    delete o.failure;
    return ajaxRequest.call(Ext.Ajax, o);
  }

  Ext.Ajax.request = maskedRequest;
}());
