text: /**
   * @file Browser client connection management class
   * @author Shinichi Tomita <shinichi.tomita@gmail.com>
   */
  
  'use strict';
  
  var events = require('events'),
      inherits = require('inherits'),
      qs = require('querystring'),
      _ = require('lodash/core'),
      Connection = require('../connection'),
      OAuth2 = require('../oauth2');
  
  /**
   * @private
   */
  function popupWin(url, w, h) {
    var left = (screen.width/2)-(w/2);
    var top = (screen.height/2)-(h/2);
    return window.open(url, null, 'location=yes,toolbar=no,status=no,menubar=no,width='+w+',height='+h+',top='+top+',left='+left);
  }
  
  function handleCallbackResponse() {
    var res = checkCallbackResponse();
    var state = localStorage.getItem('jsforce_state');
    if (res && state && res.body.state === state) {
      localStorage.removeItem('jsforce_state');
      var states = state.split('.');
      var prefix = states[0], promptType = states[1];
      var cli = new Client(prefix);
      if (res.success) {
        cli._storeTokens(res.body);
        location.hash = '';
      } else {
        cli._storeError(res.body);
      }
      if (promptType === 'popup') { window.close(); }
      return true;
    }
  }
  
  /**
   * @private
   */
  function checkCallbackResponse() {
    var params;
    if (window.location.hash) {
      params = qs.parse(window.location.hash.substring(1));
      if (params.access_token) {
        return { success: true, body: params };
      }
    } else if (window.location.search) {
      params = qs.parse(window.location.search.substring(1));
      if (params.error) {
        return { success: false, body: params };
      }
    }
  }
  
  /** @private **/
  var clientIdx = 0;
  
  
  /**
   * @class
   * @todo add document
   */
  var Client = function(prefix) {
    this._prefix = prefix || 'jsforce' + clientIdx++;
    this.connection = null;
  };
  
  inherits(Client, events.EventEmitter);
  
  /**
   *
   */
  Client.prototype.init = function(config) {
    if (handleCallbackResponse()) { return; }
    this.config = config;
    this.connection = new Connection(config);
    var tokens = this._getTokens();
    if (tokens) {
      this.connection.initialize(tokens);
      var self = this;
      setTimeout(function() {
        self.emit('connect', self.connection);
      }, 10);
    }
  };
  
  /**
   *
   */
  Client.prototype.login = function(options, callback) {
    if (_.isFunction(options)) {
      callback = options;
      options = {};
    }
    options = options || {};
    callback = callback || function(){ };
    _.extend(options, this.config);
    var self = this;
    this._prompt(options, callback);
  };
  
  
  Client.prototype._prompt = function(options, callback) {
    var self = this;
    var oauth2 = new OAuth2(options);
    var rand = Math.random().toString(36).substring(2);
    var state = [ this._prefix, "popup", rand ].join('.');
    localStorage.setItem("jsforce_state", state);
    var authConfig = {
      response_type : 'token',
      scope : options.scope,
      state : state
    }; 
    if(options.prompt){
      authConfig.prompt = options.prompt;
    }
    var authzUrl = oauth2.getAuthorizationUrl(authConfig);
    var size = options.size || {};
    var pw = popupWin(authzUrl, size.width || 912, size.height || 513);
    if (!pw) {
      state = [ this._prefix, "redirect", rand ].join('.');
      localStorage.setItem("jsforce_state", state);
  
      authConfig.state = state;
      authzUrl = oauth2.getAuthorizationUrl(authConfig);
      location.href = authzUrl;
      return;
    }
    self._removeTokens();
    var pid = setInterval(function() {
      try {
        if (!pw || pw.closed) {
          clearInterval(pid);
          var tokens = self._getTokens();
          if (tokens) {
            self.connection.initialize(tokens);
            self.emit('connect', self.connection);
            callback(null, { status: 'connect' });
          } else {
            var err = self._getError();
            if (err) {
              callback(new Error(err.error + ": " + err.error_description));
            } else {
              callback(null, { status: 'cancel' });
            }
          }
        }
      } catch(e) {}
    }, 1000);
  };
  
  /**
   *
   */
  Client.prototype.isLoggedIn = function() {
    return !!(this.connection && this.connection.accessToken);
  };
  
  /**
   *
   */
  Client.prototype.logout = function() {
    this.connection.logout();
    this._removeTokens();
    this.emit('disconnect');
  };
  
  /**
   * @private
   */
  Client.prototype._getTokens = function() {
    var regexp = new RegExp("(^|;\\s*)"+this._prefix+"_loggedin=true(;|$)");
    if (document.cookie.match(regexp)) {
      var issuedAt = Number(localStorage.getItem(this._prefix+'_issued_at'));
      if (Date.now() < issuedAt + 2 * 60 * 60 * 1000) { // 2 hours
        var userInfo;
        var idUrl = localStorage.getItem(this._prefix + '_id');
        if (idUrl) {
          var ids = idUrl.split('/');
          userInfo = { id: ids.pop(), organizationId: ids.pop(), url: idUrl };
        }
        return {
          accessToken: localStorage.getItem(this._prefix + '_access_token'),
          instanceUrl: localStorage.getItem(this._prefix + '_instance_url'),
          userInfo: userInfo
        };
      }
    }
    return null;
  };
  
  /**
   * @private
   */
  Client.prototype._storeTokens = function(params) {
    localStorage.setItem(this._prefix + '_access_token', params.access_token);
    localStorage.setItem(this._prefix + '_instance_url', params.instance_url);
    localStorage.setItem(this._prefix + '_issued_at', params.issued_at);
    localStorage.setItem(this._prefix + '_id', params.id);
    document.cookie = this._prefix + '_loggedin=true;';
  };
  
  /**
   * @private
   */
  Client.prototype._removeTokens = function() {
    localStorage.removeItem(this._prefix + '_access_token');
    localStorage.removeItem(this._prefix + '_instance_url');
    localStorage.removeItem(this._prefix + '_issued_at');
    localStorage.removeItem(this._prefix + '_id');
    document.cookie = this._prefix + '_loggedin=';
  };
  
  /**
   * @private
   */
  Client.prototype._getError = function() {
    try {
      var err = JSON.parse(localStorage.getItem(this._prefix + '_error'));
      localStorage.removeItem(this._prefix + '_error');
      return err;
    } catch(e) {}
  };
  
  /**
   * @private
   */
  Client.prototype._storeError = function(err) {
    localStorage.setItem(this._prefix + '_error', JSON.stringify(err));
  };
  
  /**
   *
   */
  module.exports = new Client();
  
  module.exports.Client = Client;