//  Copyright 2009 Google Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.


//  PopupManager is a library to facilitate integration with OpenID
//  identity providers (OP)s that support a pop-up authentication interface.
//  To create a popup window, you first construct a popupOpener customized
//  for your site and a particular identity provider, E.g.:
//
//  var googleOpener = popupManager.createOpener(openidParams);
//
//  where 'openidParams' are customized for Google in this instance.
//  (typically you just change the openidpoint, the version number
//  (the openid.ns parameter) and the extensions based on what
//  the OP supports.
//  OpenID libraries can often discover these properties
//  automatically from the location of an XRD document.
//
//  Then, you can either directly call
//  googleOpener.popup(width, height), where 'width' and 'height' are your choices
//  for popup size, or you can display a button 'Sign in with Google' and set the
//..'onclick' handler of the button to googleOpener.popup()

var popupManager = {};

// Library constants

popupManager.constants = {
  'darkCover' : 'popupManager_darkCover_div',
  'darkCoverStyle' : ['position:absolute;',
                      'top:0px;',
                      'left:0px;',
                      'padding-right:0px;',
                      'padding-bottom:0px;',
                      'background-color:#000000;',
                      'opacity:0.5;', //standard-compliant browsers
                      '-moz-opacity:0.5;',           // old Mozilla 
                      'filter:alpha(opacity=0.5);',  // IE
                      'z-index:10000;',
                      'width:100%;',
                      'height:100%;'
                      ].join(''),
  'openidSpec' : {
     'identifier_select' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select',
     'namespace2' : 'http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0'
  } };

// Computes the size of the window contents. Returns a pair of
// coordinates [width, height] which can be [0, 0] if it was not possible
// to compute the values.
popupManager.getWindowInnerSize = function() {
  var width = 0;
  var height = 0;
  var elem = null;
  if ('innerWidth' in window) {
    // For non-IE
    width = window.innerWidth;
    height = window.innerHeight;
  } else {
    // For IE,
    if (('BackCompat' === window.document.compatMode)
        && ('body' in window.document)) {
        elem = window.document.body;
    } else if ('documentElement' in window.document) {
      elem = window.document.documentElement;
    }
    if (elem !== null) {
      width = elem.offsetWidth;
      height = elem.offsetHeight;
    }
  }
  return [width, height];
};

// Computes the coordinates of the parent window.
// Gets the coordinates of the parent frame
popupManager.getParentCoords = function() {
  var width = 0;
  var height = 0;
  if ('screenLeft' in window) {
    // IE-compatible variants
    width = window.screenLeft;
    height = window.screenTop;
  } else if ('screenX' in window) {
    // Firefox-compatible
    width = window.screenX;
    height = window.screenY;
  }
  return [width, height];
};

// Computes the coordinates of the new window, so as to center it
// over the parent frame
popupManager.getCenteredCoords = function(width, height) {
   var parentSize = this.getWindowInnerSize();
   var parentPos = this.getParentCoords();
   var xPos = parentPos[0] +
       Math.max(0, Math.floor((parentSize[0] - width) / 2));
   var yPos = parentPos[1] +
       Math.max(0, Math.floor((parentSize[1] - height) / 2));
   return [xPos, yPos];
};

//  A utility class, implements an onOpenHandler that darkens the screen
//  by overlaying it with a semi-transparent black layer. To use, ensure that
//  no screen element has a z-index at or above 10000.
//  This layer will be suppressed automatically after the screen closes.
//
//  Note: If you want to perform other operations before opening the popup, but
//  also would like the screen to darken, you can define a custom handler
//  as such:
//  var myOnOpenHandler = function(inputs) {
//    .. do something
//    popupManager.darkenScreen();
//    .. something else
//  };
//  Then you pass myOnOpenHandler as input to the opener, as in:
//  var openidParams = {};
//  openidParams.onOpenHandler = myOnOpenHandler;
//  ... other customizations
//  var myOpener = popupManager.createOpener(openidParams);
popupManager.darkenScreen = function() {
  var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']);
  if (!darkCover) {
    darkCover = window.document.createElement('div');
    darkCover['id'] = window.popupManager.constants['darkCover'];
    darkCover.setAttribute('style', window.popupManager.constants['darkCoverStyle']);
    window.document.body.appendChild(darkCover);
  }
  darkCover.style.visibility = 'visible';
};

//  Returns a an object that can open a popup window customized for an OP & RP.
//  to use you call var opener = popupManager.cretePopupOpener(openidParams);
//  and then you can assign the 'onclick' handler of a button to
//  opener.popup(width, height), where width and height are the values of the popup size;
//
//  To use it, you would typically have code such as:
//  var myLoginCheckFunction = ...  some AJAXy call or page refresh operation
//  that will cause the user to see the logged-in experience in the current page.
//  var openidParams = { realm : 'openid.realm', returnToUrl : 'openid.return_to',
//  opEndpoint : 'openid.op_endpoint', onCloseHandler : myLoginCheckFunction,
//  shouldEncodeUrls : 'true' (default) or 'false', extensions : myOpenIDExtensions };
//
//  Here extensions include any OpenID extensions that you support. For instance,
//  if you support Attribute Exchange v.1.0, you can say:
//  (Example for attribute exchange request for email and name,
//  assuming that shouldEncodeUrls = 'true':)
//  var myOpenIDExtensions = {
//      'openid.ax.ns' : 'http://openid.net/srv/ax/1.0',
//      'openid.ax.type.email' : 'http://axschema.org/contact/email',
//      'openid.ax.type.name1' : 'http://axschema.org/namePerson/first',
//      'openid.ax.type.name2' : 'http://axschema.org/namePerson/last',
//      'openid.ax.required' : 'email,name1,name2' };
//  Note that the 'ui' namespace is reserved by this library for the OpenID
//  UI extension, and that the mode 'popup' is automatically applied.
//  If you wish to make use of the 'language' feature of the OpenID UI extension
//  simply add the following entry (example assumes the language requested
//  is Swiss French:
//  var my OpenIDExtensions = {
//    ... // other extension parameters
//    'openid.ui.language' : 'fr_CH',
//    ... };
popupManager.createPopupOpener = (function(openidParams) {
  var interval_ = null;
  var popupWindow_ = null;
  var that = this;
  var shouldEscape_ = ('shouldEncodeUrls' in openidParams) ? openidParams.shouldEncodeUrls : true;
  var encodeIfRequested_ = function(url) {
    return (shouldEscape_ ? encodeURIComponent(url) : url);
  };
  var identifier_ = ('identifier' in openidParams) ? encodeIfRequested_(openidParams.identifier) :
      this.constants.openidSpec.identifier_select;
  var identity_ = ('identity' in openidParams) ? encodeIfRequested_(openidParams.identity) :
      this.constants.openidSpec.identifier_select;
  var openidNs_ = ('namespace' in openidParams) ? encodeIfRequested_(openidParams.namespace) :
      this.constants.openidSpec.namespace2;
  var onOpenHandler_ = (('onOpenHandler' in openidParams) &&
      ('function' === typeof(openidParams.onOpenHandler))) ?
          openidParams.onOpenHandler : this.darkenScreen;
  var onCloseHandler_ = (('onCloseHandler' in openidParams) &&
      ('function' === typeof(openidParams.onCloseHandler))) ?
          openidParams.onCloseHandler : null;
  var returnToUrl_ = ('returnToUrl' in openidParams) ? openidParams.returnToUrl : null;
  var realm_ = ('realm' in openidParams) ? openidParams.realm : null;
  var endpoint_ = ('opEndpoint' in openidParams) ? openidParams.opEndpoint : null;
  var extensions_ = ('extensions' in openidParams) ? openidParams.extensions : null;

  // processes key value pairs, escaping any input;
  var keyValueConcat_ = function(keyValuePairs) {
    var result = "";
    for (key in keyValuePairs) {
      result += ['&', key, '=', encodeIfRequested_(keyValuePairs[key])].join('');
    }
    return result;
  };

  //Assembles the OpenID request from customizable parameters
  var buildUrlToOpen_ = function() {
    var connector = '&';
    var encodedUrl = null;
    var urlToOpen = null;
    if ((null === endpoint_) || (null === returnToUrl_)) {
      return;
    }
    if (endpoint_.indexOf('?') === -1) {
      connector = '?';
    }
    encodedUrl = encodeIfRequested_(returnToUrl_);
    urlToOpen = [ endpoint_, connector,
        'openid.ns=', openidNs_,
        '&openid.mode=checkid_setup',
        '&openid.claimed_id=', identifier_,
        '&openid.identity=', identity_,
        '&openid.return_to=', encodedUrl ].join('');
    if (realm_ !== null) {
      urlToOpen += "&openid.realm=" + encodeIfRequested_(realm_);
    }
    if (extensions_ !== null) {
      urlToOpen += keyValueConcat_(extensions_);
    }
    urlToOpen += '&openid.ns.ui=' + encodeURIComponent(
        'http://specs.openid.net/extensions/ui/1.0');
    urlToOpen += '&openid.ui.mode=popup';
    return urlToOpen;
  };

  // Tests that the popup window has closed
  var isPopupClosed_ = function() {
    return (!popupWindow_ || popupWindow_.closed);
  };

  // Check to perform at each execution of the timed loop. It also triggers
  // the action that follows the closing of the popup
  var waitForPopupClose_ = function() {
    if (isPopupClosed_()) {
      popupWindow_ = null;
      var darkCover = window.document.getElementById(window.popupManager.constants['darkCover']);
      if (darkCover) {
        darkCover.style.visibility = 'hidden';
      }
      if (onCloseHandler_ !== null) {
        onCloseHandler_();
      }
      if ((null !== interval_)) {
        window.clearInterval(interval_);
        interval_ = null;
      }
    }
  };

  return {
    // Function that opens the window.
    popup: function(width, height) {
      var urlToOpen = buildUrlToOpen_();
      if (onOpenHandler_ !== null) {
        onOpenHandler_();
      }
      var coordinates = that.getCenteredCoords(width, height);
      popupWindow_ = window.open(urlToOpen, "",
          "width=" + width + ",height=" + height +
          ",status=1,location=1,resizable=yes" +
          ",left=" + coordinates[0] +",top=" + coordinates[1]);
      interval_ = window.setInterval(waitForPopupClose_, 80);
      return true;
    }
  };
});