// 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.

#ifndef CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_SETUP_H_
#define CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_SETUP_H_

#include <string>

#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/extensions/app_notify_channel_ui.h"
#include "chrome/common/net/gaia/oauth2_access_token_consumer.h"
#include "chrome/common/net/gaia/oauth2_access_token_fetcher.h"
#include "content/public/common/url_fetcher.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "googleurl/src/gurl.h"

class AppNotifyChannelSetupTest;
class Profile;

// This class uses the browser login credentials to setup app notifications
// for a given app.
//
// Performs the following steps when Start() is called:
// 1. If the user is not logged in, prompt the user to login.
// 2. OAuth2: Record a notifications grant for the user and the given app.
// 3. Get notifications channel id for the current user.
// 4. Call the delegate passed in to the constructor with the results of
//    the above steps.
class AppNotifyChannelSetup
    : public content::URLFetcherDelegate,
      public AppNotifyChannelUI::Delegate,
      public OAuth2AccessTokenConsumer,
      public base::RefCountedThreadSafe<AppNotifyChannelSetup> {
 public:
  class Delegate {
   public:
    // If successful, |channel_id| will be non-empty. On failure, |channel_id|
    // will be empty and |error| will contain an error to report to the JS
    // callback.
    virtual void AppNotifyChannelSetupComplete(
        const std::string& channel_id,
        const std::string& error,
        const AppNotifyChannelSetup* setup) = 0;
  };

  // For tests, we allow intercepting the request to setup the channel and
  // forcing the return of a certain result to the delegate.
  class InterceptorForTests {
   public:
    virtual void DoIntercept(const AppNotifyChannelSetup* setup,
                             std::string* result_channel_id,
                             std::string* result_error) = 0;
  };
  static void SetInterceptorForTests(InterceptorForTests* interceptor);

  // Ownership of |ui| is transferred to this object.
  AppNotifyChannelSetup(Profile* profile,
                        const std::string& extension_id,
                        const std::string& client_id,
                        const GURL& requestor_url,
                        int return_route_id,
                        int callback_id,
                        AppNotifyChannelUI* ui,
                        base::WeakPtr<Delegate> delegate);

  AppNotifyChannelUI* ui() { return ui_.get(); }

  // This begins the process of fetching the channel id using the browser login
  // credentials (or using |ui_| to prompt for login if needed).
  void Start();

  // OAuth2AccessTokenConsumer implementation.
  virtual void OnGetTokenSuccess(const std::string& access_token) OVERRIDE;
  virtual void OnGetTokenFailure(const GoogleServiceAuthError& error) OVERRIDE;


  // Getters for various members.
  const std::string& extension_id() const { return extension_id_; }
  const std::string& client_id() const { return client_id_; }
  int return_route_id() const { return return_route_id_; }
  int callback_id() const { return callback_id_; }

 protected:
  // content::URLFetcherDelegate.
  virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE;

  // AppNotifyChannelUI::Delegate.
  virtual void OnSyncSetupResult(bool enabled) OVERRIDE;

 private:
  enum State {
    INITIAL,
    LOGIN_STARTED,
    LOGIN_DONE,
    FETCH_ACCESS_TOKEN_STARTED,
    FETCH_ACCESS_TOKEN_DONE,
    RECORD_GRANT_STARTED,
    RECORD_GRANT_DONE,
    CHANNEL_ID_SETUP_STARTED,
    CHANNEL_ID_SETUP_DONE,
    ERROR_STATE
  };

  friend class base::RefCountedThreadSafe<AppNotifyChannelSetup>;
  friend class AppNotifyChannelSetupTest;

  virtual ~AppNotifyChannelSetup();

  // Creates an instance of URLFetcher that does not send or save cookies.
  // The URLFether's method will be GET if body is empty, POST otherwise.
  // Caller owns the returned instance.
  content::URLFetcher* CreateURLFetcher(
    const GURL& url, const std::string& body, const std::string& auth_token);
  void BeginLogin();
  void EndLogin(bool success);
  void BeginGetAccessToken();
  void EndGetAccessToken(bool success);
  void BeginRecordGrant();
  void EndRecordGrant(const content::URLFetcher* source);
  void BeginGetChannelId();
  void EndGetChannelId(const content::URLFetcher* source);

  void ReportResult(const std::string& channel_id, const std::string& error);

  static GURL GetCWSChannelServiceURL();
  static GURL GetOAuth2IssueTokenURL();
  static std::string MakeOAuth2IssueTokenBody(
      const std::string& oauth_client_id, const std::string& extension_id);
  static std::string MakeAuthorizationHeader(const std::string& auth_token);
  static bool ParseCWSChannelServiceResponse(
      const std::string& data, std::string* result);
  // Checks if the user needs to be prompted for login.
  bool ShouldPromptForLogin() const;
  void RegisterForTokenServiceNotifications();
  void UnregisterForTokenServiceNotifications();

  Profile* profile_;
  std::string extension_id_;
  std::string client_id_;
  GURL requestor_url_;
  int return_route_id_;
  int callback_id_;
  base::WeakPtr<Delegate> delegate_;
  scoped_ptr<content::URLFetcher> url_fetcher_;
  scoped_ptr<OAuth2AccessTokenFetcher> oauth2_fetcher_;
  scoped_ptr<AppNotifyChannelUI> ui_;
  State state_;
  std::string oauth2_access_token_;
  // Keeps track of whether we have encountered failure in OAuth2 access
  // token generation already. We use this to prevent us from doing an
  // infinite loop of trying to generate access token, if that fails, try
  // to login the user and generate access token, etc.
  bool oauth2_access_token_failure_;

  DISALLOW_COPY_AND_ASSIGN(AppNotifyChannelSetup);
};

#endif  // CHROME_BROWSER_EXTENSIONS_APP_NOTIFY_CHANNEL_SETUP_H_