(function($) {

Sammy = Sammy || {};

// Sammy.OAuth2 is a plugin for using OAuth 2.0 to authenticate users and
// access your application's API. Requires Sammy.Session.
//
// Triggers the following events:
// - oauth.connected -- Access token set and ready to use. Triggered when new
// access token acquired, of when application starts and already has access
// token.
// - oauth.disconnected -- Access token reset. Triggered by
// loseAccessToken().
// - oauth.denied -- Authorization attempt rejected.
//
// ### Example
// this.use(Sammy.Storage);
// this.use(Sammy.OAuth2);
// this.oauthorize = "/oauth/authorize";
//
// // The quick & easy way 
// this.requireOAuth();
// // Specific path
// this.requireOAuth("/private");
// // Filter you can apply to specific URLs
// this.before(function(context) { return context.requireOAuth(); })
// // Apply to specific request
// this.get("/private", function(context) {
//   this.requireOAuth(function() {
//     // Do something
//   })
// })
//
// // Sign in/sign out.
// this.bind("oauth.connected", function() { $("#signin").hide() });
// this.bind("oauth.disconnected", function() { $("#signin").show() });
//
// // Handle access denied and other errors
// this.bind("oauth.denied", function(evt, error) {
//   this.partial("admin/views/no_access.tmpl", { error: error.message });
// });
//
// // Sign out.
// this.get("#/signout", function(context) {
//   context.loseAccessToken();
//   context.redirect("#/");
// });
//
Sammy.OAuth2 = function(app) {
  app.use(Sammy.JSON);
  this.authorize = "/oauth/authorize";

  // Use this on request that require OAuth token. You can use this in a
  // filter: it will redirect and return false if the access token is missing.
  // You can use it in a route, it will redirect to get the access token, or
  // call the callback function if it has an access token.
  this.helper("requireOAuth", function(cb) {
    if (this.app.getAccessToken()) {
      if (cb)
        cb.apply(this);
    } else {
      this.redirect(this.app.authorize + "?state=" + escape(this.path));
      return false;
    }
  });

  // Use this to sign out.
  this.helper("loseAccessToken", function() {
    this.app.loseAccessToken();
  });

  // Use this in your application to require an OAuth access token on all, or
  // the specified paths. It sets up a before filter on the specified paths.
  this.requireOAuth = function(options) {
    this.before(options || {}, function(context) {
      return context.requireOAuth();
    });
  }

  // Returns the access token. Uses Sammy.Session to store the token.
  this.getAccessToken = function() {
    return this.session("oauth.token");
  }
  // Stores the access token in the session.
  this.setAccessToken = function(token) {
    this.session("oauth.token", token);
    this.trigger("oauth.connected");
  }
  // Lose access token: use this to sign out.
  this.loseAccessToken = function() {
    this.session("oauth.token", null);
    this.trigger("oauth.disconnected");
  }

  // Add OAuth 2.0 access token to all XHR requests.
  $(document).ajaxSend(function(evt, xhr) {
    var token = app.getAccessToken();
    if (token)
      xhr.setRequestHeader("Authorization", "OAuth " + token);
  });

  // Converts query string parameters in fragment identifier to object.
  function parseParams(hash) {
    var pairs = hash.substring(1).split("&"), params = {};
    for (var i in pairs) {
      var splat = pairs[i].split("=");
      params[splat[0]] = splat[1].replace(/\+/g, " ");
    }
    return params;
  }

  var start_url;
  // Capture the application's start URL, we'll need that later on for
  // redirection.
  this.bind("run", function(evt, params) {
    start_url = params.start_url || "#";
    if (this.app.getAccessToken())
      this.trigger("oauth.connected");
  });

  // Intercept OAuth authorization response with access token, stores it and
  // redirects to original URL, or application root.
  this.before(/^#(access_token=|[^\\].*\&access_token=)/, function(context) {
    var params = parseParams(context.path);
    this.app.setAccessToken(params.access_token);
    // When the filter redirected the original request, it passed the original
    // request's URL in the state parameter, which we get back after
    // authorization.
    context.redirect(params.state.length == 0 ? this.app.start_url : unescape(params.state));
    return false;
  }).get(/^#(access_token=|[^\\].*\&access_token=)/, function(context) { });

  // Intercept OAuth authorization response with error (typically access
  // denied).
  this.before(/^#(error=|[^\\].*\&error=)/, function(context) {
    var params = parseParams(context.path);
    var message = params.error_description || "Access denined";
    context.trigger("oauth.denied", { code: params.error, message: message });
    return false;
  }).get(/^#(error=|[^\\].*\&error=)/, function(context) { });

}

})(jQuery);