// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/net/gaia/gaia_oauth_client.h"

#include "base/json/json_reader.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
#include "chrome/common/net/http_return.h"
#include "chrome/common/net/url_fetcher.h"
#include "googleurl/src/gurl.h"
#include "net/base/escape.h"
#include "net/url_request/url_request_context_getter.h"

namespace {
const char kAccessTokenValue[] = "access_token";
const char kRefreshTokenValue[] = "refresh_token";
const char kExpiresInValue[] = "expires_in";
}

namespace gaia {

class GaiaOAuthClient::Core
    : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
      public URLFetcher::Delegate {
 public:
  Core(const std::string& gaia_url,
       net::URLRequestContextGetter* request_context_getter)
           : gaia_url_(gaia_url),
             num_retries_(0),
             request_context_getter_(request_context_getter),
             delegate_(NULL) {}

  virtual ~Core() { }

  void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
                             const std::string& auth_code,
                             int max_retries,
                             GaiaOAuthClient::Delegate* delegate);
  void RefreshToken(const OAuthClientInfo& oauth_client_info,
                    const std::string& refresh_token,
                    int max_retries,
                    GaiaOAuthClient::Delegate* delegate);

  // URLFetcher::Delegate implementation.
  virtual void OnURLFetchComplete(
    const URLFetcher* source,
    const GURL& url,
    const net::URLRequestStatus& status,
    int response_code,
    const ResponseCookies& cookies,
    const std::string& data);

 private:
  void MakeGaiaRequest(std::string post_body,
                       int max_retries,
                       GaiaOAuthClient::Delegate* delegate);
  void HandleResponse(const URLFetcher* source,
                      const GURL& url,
                      const net::URLRequestStatus& status,
                      int response_code,
                      const std::string& data,
                      bool* should_retry_request);

  GURL gaia_url_;
  int num_retries_;
  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
  GaiaOAuthClient::Delegate* delegate_;
  scoped_ptr<URLFetcher> request_;
};

void GaiaOAuthClient::Core::GetTokensFromAuthCode(
    const OAuthClientInfo& oauth_client_info,
    const std::string& auth_code,
    int max_retries,
    GaiaOAuthClient::Delegate* delegate) {
  std::string post_body =
      "code=" + EscapeUrlEncodedData(auth_code) +
      "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) +
      "&client_secret=" +
      EscapeUrlEncodedData(oauth_client_info.client_secret) +
      "&redirect_uri=oob&grant_type=authorization_code";
  MakeGaiaRequest(post_body, max_retries, delegate);
}

void GaiaOAuthClient::Core::RefreshToken(
    const OAuthClientInfo& oauth_client_info,
    const std::string& refresh_token,
    int max_retries,
    GaiaOAuthClient::Delegate* delegate) {
  std::string post_body =
      "refresh_token=" + EscapeUrlEncodedData(refresh_token) +
      "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) +
      "&client_secret=" +
      EscapeUrlEncodedData(oauth_client_info.client_secret) +
      "&grant_type=refresh_token";
  MakeGaiaRequest(post_body, max_retries, delegate);
}

void GaiaOAuthClient::Core::MakeGaiaRequest(
    std::string post_body,
    int max_retries,
    GaiaOAuthClient::Delegate* delegate) {
  DCHECK(!request_.get()) << "Tried to fetch two things at once!";
  delegate_ = delegate;
  num_retries_ = 0;
  request_.reset(URLFetcher::Create(0, gaia_url_, URLFetcher::POST, this));
  request_->set_request_context(request_context_getter_);
  request_->set_upload_data("application/x-www-form-urlencoded", post_body);
  request_->set_max_retries(max_retries);
  request_->Start();
}

// URLFetcher::Delegate implementation.
void GaiaOAuthClient::Core::OnURLFetchComplete(
    const URLFetcher* source,
    const GURL& url,
    const net::URLRequestStatus& status,
    int response_code,
    const ResponseCookies& cookies,
    const std::string& data) {
  bool should_retry = false;
  HandleResponse(source, url, status, response_code, data, &should_retry);
  if (should_retry) {
    // If the response code is greater than or equal to 500, then the back-off
    // period has been increased at the network level; otherwise, explicitly
    // call ReceivedContentWasMalformed() to count the current request as a
    // failure and increase the back-off period.
    if (response_code < 500)
      request_->ReceivedContentWasMalformed();
    num_retries_++;
    request_->Start();
  } else {
    request_.reset();
  }
}

void GaiaOAuthClient::Core::HandleResponse(
    const URLFetcher* source,
    const GURL& url,
    const net::URLRequestStatus& status,
    int response_code,
    const std::string& data,
    bool* should_retry_request) {
  *should_retry_request = false;
  // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
  // done here.
  if (response_code == RC_BAD_REQUEST) {
    delegate_->OnOAuthError();
    return;
  }
  std::string access_token;
  std::string refresh_token;
  int expires_in_seconds = 0;
  if (response_code == RC_REQUEST_OK) {
    scoped_ptr<Value> message_value(
        base::JSONReader::Read(data, false));
    if (message_value.get() &&
        message_value->IsType(Value::TYPE_DICTIONARY)) {
      scoped_ptr<DictionaryValue> response_dict(
          static_cast<DictionaryValue*>(message_value.release()));
      response_dict->GetString(kAccessTokenValue, &access_token);
      response_dict->GetString(kRefreshTokenValue, &refresh_token);
      response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
    }
  }
  if (access_token.empty()) {
    // If we don't have an access token yet and the the error was not
    // RC_BAD_REQUEST, we may need to retry.
    if ((-1 != source->max_retries()) &&
        (num_retries_ > source->max_retries())) {
      // Retry limit reached. Give up.
      delegate_->OnNetworkError(response_code);
    } else {
      *should_retry_request = true;
    }
  } else if (refresh_token.empty()) {
    // If we only have an access token, then this was a refresh request.
    delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
  } else {
    delegate_->OnGetTokensResponse(refresh_token,
                                   access_token,
                                   expires_in_seconds);
  }
}

GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url,
                                 net::URLRequestContextGetter* context_getter) {
  core_ = new Core(gaia_url, context_getter);
}

GaiaOAuthClient::~GaiaOAuthClient() {
}


void GaiaOAuthClient::GetTokensFromAuthCode(
    const OAuthClientInfo& oauth_client_info,
    const std::string& auth_code,
    int max_retries,
    Delegate* delegate) {
  return core_->GetTokensFromAuthCode(oauth_client_info,
                                      auth_code,
                                      max_retries,
                                      delegate);
}

void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info,
                                   const std::string& refresh_token,
                                   int max_retries,
                                   Delegate* delegate) {
  return core_->RefreshToken(oauth_client_info,
                             refresh_token,
                             max_retries,
                             delegate);
}

}  // namespace gaia