/*
 * Copyright 2016 Google LLC
 *
 * 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.
 */

#ifndef FIREBASE_AUTH_SRC_INCLUDE_FIREBASE_AUTH_H_
#define FIREBASE_AUTH_SRC_INCLUDE_FIREBASE_AUTH_H_

#include <string>
#include <vector>

#include "firebase/app.h"
#include "firebase/auth/user.h"
#include "firebase/future.h"
#include "firebase/internal/common.h"

#if !defined(DOXYGEN)
#ifndef SWIG
FIREBASE_APP_REGISTER_CALLBACKS_REFERENCE(auth)
#endif  // SWIG
#endif  // !defined(DOXYGEN)

namespace firebase {

/// @brief Firebase Authentication API.
///
/// Firebase Authentication provides backend services to securely authenticate
/// users. It can authenticate users using passwords and federated identity
/// provider credentials, and it can integrate with a custom auth backend.
namespace auth {

// Predeclarations.
struct AuthData;
class AuthStateListener;
class IdTokenListener;
class PhoneAuthProvider;
class AuthRequest;
struct AuthResultCompletionHandle;
class FederatedAuthProvider;
class FederatedOAuthProvider;

/// @brief Firebase authentication object.
///
/// <SWIG>
/// @if swig_examples
/// Firebase.Auth.FirebaseAuth is the gateway to the Firebase authentication
/// API. With it, you can reference Firebase.Auth.FirebaseAuth objects to
/// manage user accounts and credentials.
///
/// Each Firebase.FirebaseApp has up to one Firebase.Auth.FirebaseAuth
/// class. You acquire the Firebase.Auth.FirebaseAuth class through the static
/// function Firebase.Auth.FirebaseAuth.GetAuth.
///
/// For example:
/// @code{.cs}
///  // Get the Auth class for your App.
///  Firebase.Auth.FirebaseAuth auth = Firebase.Auth.FirebaseAuth.GetAuth(app);
///
///  // Request anonymous sign-in and wait until asynchronous call completes.
///  auth.SignInAnonymouslyAsync().ContinueWith((authTask) => {
///  // Print sign in results.
///    if (authTask.IsCanceled) {
///      DebugLog("Sign-in canceled.");
///    } else if (authTask.IsFaulted) {
///      DebugLog("Sign-in encountered an error.");
///      DebugLog(authTask.Exception.ToString());
///    } else if (authTask.IsCompleted) {
///      Firebase.Auth.User user = authTask.Result;
///      DebugLog(String.Format("Signed in as {0} user.",
///          user.Anonymous ? "an anonymous" : "a non-anonymous"));
///      DebugLog("Signing out.");
///      auth.SignOut();
///  });
/// @endcode
/// @endif
/// </SWIG>
/// @if cpp_examples
///
/// firebase::auth::Auth is the gateway to the Firebase authentication API.
/// With it, you can reference firebase::auth::User objects to manage user
/// accounts and credentials.
///
/// Each firebase::App has up to one firebase::auth::Auth class. You
/// acquire the firebase::auth::Auth class through the static function
/// firebase::auth::Auth::GetAuth.
///
/// For example:
/// @code{.cpp}
///
/// // Get the Auth class for your App.
/// firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);
///
/// // Request anonymous sign-in and wait until asynchronous call completes.
/// firebase::Future<firebase::auth::AuthResult> sign_in_future =
///     auth->SignInAnonymously();
/// while(sign_in_future.status() == firebase::kFutureStatusPending) {
///     // when polling, like this, make sure you service your platform's
///     // message loop
///     // see https://github.com/firebase/quickstart-cpp for a sample
///     ProcessEvents(300);
///     std::cout << "Signing in...\n";
/// }
///
/// const firebase::auth::AuthError error =
///     static_cast<firebase::auth::AuthError>(sign_in_future.error());
/// if (error != firebase::auth::kAuthErrorNone) {
///     std::cout << "Sign in failed with error '"
///         << sign_in_future.error_message() << "'\n";
/// } else {
///     firebase::auth::User user = sign_in_future.result()->user;
///     // is_anonymous from Anonymous
///     std::cout << "Signed in as "
///         << (user.is_anonymous() ? "an anonymous" : "a non-anonymous")
///         << " user\n";
/// }
/// @endcode
/// @endif
class Auth {
 public:
  /// @brief Results of calls FetchProvidersForEmail.
  ///
  /// <SWIG>
  /// @if swig_examples
  /// @see FirebaseAuth.FetchProvidersForEmailAsync().
  /// @endif
  /// </SWIG>
  struct FetchProvidersResult {
    /// The IDPs (identity providers) that can be used for `email`.
    /// An array of length `num_providers` of null-terminated strings.
    /// <SWIG>
    /// The C# doc string is in the SWIG file because nested structs are
    /// causing problems b/35780150
    /// </SWIG>
    std::vector<std::string> providers;
  };

  ~Auth();

  /// Synchronously gets the cached current user, or returns an object where
  /// is_valid() == false if there is none.
  ///
  /// @note This function may block and wait until the Auth instance finishes
  /// loading the saved user's state. This should only happen for a short
  /// time after the Auth instance is created.
  User current_user();

  /// The current user language code. This can be set to the app’s current
  /// language by calling set_language_code. The string must be a language code
  /// that follows BCP 47.  This will return an empty string if the app default
  /// language code is being used.
  std::string language_code() const;

  /// Sets the user-facing language code for auth operations that can be
  /// internationalized, such as FirebaseUser.sendEmailVerification(). This
  /// language code should follow the conventions defined by the IETF in BCP 47.
  void set_language_code(const char* language_code);

  /// Sets the user-facing language code to be the default app language. This
  /// uses a language associated with the device's locale data.  On desktop
  /// this will set the language code to the Firebase service's default. You
  /// may subsequently customize the language code again by invoking
  /// set_language_code().
  void UseAppLanguage();

  // ----- Providers -------------------------------------------------------
  /// Asynchronously requests the IDPs (identity providers) that can be used
  /// for the given email address.
  ///
  /// Useful for an "identifier-first" login flow.
  ///
  /// @if cpp_examples
  /// The following sample code illustrates a possible login screen
  /// that allows the user to pick an identity provider.
  /// @code{.cpp}
  ///  // This function is called every frame to display the login screen.
  ///  // Returns the identity provider name, or "" if none selected.
  ///  const char* DisplayIdentityProviders(firebase::auth::Auth& auth,
  ///                                       const char* email) {
  ///    // Get results of most recent call to FetchProvidersForEmail().
  ///    firebase::Future<firebase::auth::Auth::FetchProvidersResult> future =
  ///        auth.FetchProvidersForEmailLastResult();
  ///    const firebase::auth::Auth::FetchProvidersResult* result =
  ///        future.result();
  ///
  ///    // Header.
  ///    ShowTextBox("Sign in %s", email);
  ///
  ///    // Fetch providers from the server if we need to.
  ///    const bool refetch =
  ///        future.status() == firebase::kFutureStatusInvalid ||
  ///        (result != nullptr && strcmp(email, result->email.c_str()) != 0);
  ///    if (refetch) {
  ///      auth.FetchProvidersForEmail(email);
  ///    }
  ///
  ///    // Show a waiting icon if we're waiting for the asynchronous call to
  ///    // complete.
  ///    if (future.status() != firebase::kFutureStatusComplete) {
  ///      ShowImage("waiting icon");
  ///      return "";
  ///    }
  ///
  ///    // Show error code if the call failed.
  ///    if (future.error() != firebase::auth::kAuthErrorNone) {
  ///      ShowTextBox("Error fetching providers: %s", future.error_message());
  ///    }
  ///
  ///    // Show a button for each provider available to this email.
  ///    // Return the provider for the button that's pressed.
  ///    for (size_t i = 0; i < result->providers.size(); ++i) {
  ///      const bool selected = ShowTextButton(result->providers[i].c_str());
  ///      if (selected) return result->providers[i].c_str();
  ///    }
  ///    return "";
  ///  }
  /// @endcode
  /// @endif
  Future<FetchProvidersResult> FetchProvidersForEmail(const char* email);

  /// Get results of the most recent call to FetchProvidersForEmail.
  Future<FetchProvidersResult> FetchProvidersForEmailLastResult() const;

  /// Asynchronously logs into Firebase with the given Auth token.
  ///
  /// An error is returned if the token is invalid, expired or otherwise not
  /// accepted by the server.
  Future<AuthResult> SignInWithCustomToken(const char* custom_token);

  /// Get results of the most recent call to SignInWithCustomToken.
  Future<AuthResult> SignInWithCustomTokenLastResult() const;

  /// Convenience method for SignInAndRetrieveDataWithCredential that
  /// doesn't return additional identity provider data.
  Future<User> SignInWithCredential(const Credential& credential);

  /// Sign-in a user authenticated via a federated auth provider.
  ///
  /// @param[in] provider Contains information on the provider to authenticate
  /// with.
  ///
  /// @return A Future with the result of the sign-in request.
  ///
  /// @note: This operation is supported only on iOS, tvOS and Android
  /// platforms. On other platforms this method will return a Future with a
  /// preset error code: kAuthErrorUnimplemented.
  Future<AuthResult> SignInWithProvider(FederatedAuthProvider* provider);

  /// Asynchronously logs into Firebase with the given credentials.
  ///
  /// For example, the credential could wrap a Facebook login access token or
  /// a Twitter token/token-secret pair.
  ///
  /// The AuthResult contains both a reference to the User (which is_valid()
  /// will return false if the sign in failed), and AdditionalUserInfo, which
  /// holds details specific to the Identity Provider used to sign in.
  ///
  /// An error is returned if the token is invalid, expired, or otherwise not
  /// accepted by the server.
  Future<AuthResult> SignInAndRetrieveDataWithCredential(
      const Credential& credential);

  /// Get results of the most recent call to
  /// SignInAndRetrieveDataWithCredential.
  Future<AuthResult> SignInAndRetrieveDataWithCredentialLastResult() const;

  /// Asynchronously creates and becomes an anonymous user.
  /// If there is already an anonymous user signed in, that user will be
  /// returned instead.
  /// If there is any other existing user, that user will be signed out.
  ///
  /// <SWIG>
  /// @if swig_examples
  /// @code{.cs}
  ///  bool SignIn(Firebase.Auth.FirebaseAuth auth) {
  ///    auth.SignInAnonymouslyAsync().ContinueWith((authTask) => {
  ///      if (authTask.IsCanceled) {
  ///        DebugLog("Anonymous sign in canceled.");
  ///      } else if (authTask.IsFaulted) {
  ///        DebugLog("Anonymous sign in encountered an error.");
  ///        DebugLog(authTask.Exception.ToString());
  ///      } else if (authTask.IsCompleted) {
  ///        DebugLog("Anonymous sign in successful!");
  ///      }
  ///    });
  ///  }
  /// @endcode
  /// @endif
  /// </SWIG>
  /// @if cpp_examples
  /// The following sample code illustrates the sign-in flow that might be
  /// used by a game or some other program with a regular (for example, 30Hz)
  /// update loop.
  ///
  /// The sample calls SignIn() every frame. We don’t maintain our own
  /// Futures but instead call SignInAnonymouslyLastResult() to get the Future
  /// of our most recent call.
  ///
  /// @code{.cpp}
  ///  // Try to ensure that we get logged in.
  ///  // This function is called every frame.
  ///  bool SignIn(firebase::auth::Auth& auth) {
  ///    // Grab the result of the latest sign-in attempt.
  ///    firebase::Future<firebase::auth::AuthResult> future =
  ///        auth.SignInAnonymouslyLastResult();
  ///
  ///    // If we're in a state where we can try to sign in, do so.
  ///    if (future.status() == firebase::kFutureStatusInvalid ||
  ///        (future.status() == firebase::kFutureStatusComplete &&
  ///         future.error() != firebase::auth::kAuthErrorNone)) {
  ///      auth.SignInAnonymously();
  ///    }
  ///
  ///    // We're signed in if the most recent result was successful.
  ///    return future.status() == firebase::kFutureStatusComplete &&
  ///           future.error() == firebase::auth::kAuthErrorNone;
  ///  }
  /// @endcode
  /// @endif
  Future<AuthResult> SignInAnonymously();

  /// Get results of the most recent call to SignInAnonymously.
  Future<AuthResult> SignInAnonymouslyLastResult() const;

  /// Signs in using provided email address and password.
  /// An error is returned if the password is wrong or otherwise not accepted
  /// by the server.
  Future<AuthResult> SignInWithEmailAndPassword(const char* email,
                                                const char* password);

  /// Get results of the most recent call to
  /// SignInWithEmailAndPassword.
  Future<AuthResult> SignInWithEmailAndPasswordLastResult() const;

  /// Creates, and on success, logs in a user with the given email address
  /// and password.
  ///
  /// An error is returned when account creation is unsuccessful
  /// (due to another existing account, invalid password, etc.).
  Future<AuthResult> CreateUserWithEmailAndPassword(const char* email,
                                                    const char* password);

  /// Get results of the most recent call to
  /// CreateUserWithEmailAndPassword.
  Future<AuthResult> CreateUserWithEmailAndPasswordLastResult() const;

  /// Removes any existing authentication credentials from this client.
  /// This function always succeeds.
  void SignOut();

  // ----- Password Reset -------------------------------------------------
  /// Initiates a password reset for the given email address.
  ///
  /// If the email address is not registered, then the returned task has a
  /// status of IsFaulted.
  ///
  /// <SWIG>
  /// @if swig_examples
  /// @code{.cs}
  ///  void ResetPassword(string email) {
  ///    auth.SendPasswordResetEmail(email).ContinueWith((authTask) => {
  ///      if (authTask.IsCanceled) {
  ///        DebugLog("Password reset was canceled.");
  ///      } else if (authTask.IsFaulted) {
  ///        DebugLog("Password reset encountered an error.");
  ///        DebugLog(authTask.Exception.ToString());
  ///      } else if (authTask.IsCompleted) {
  ///        DebugLog("Password reset successful!");
  ///      }
  ///    });
  ///  }
  /// @endcode
  /// @endif
  /// </SWIG>
  /// @if cpp_examples
  /// The following sample code illustrating a possible password reset flow.
  /// Like in the Anonymous Sign-In example above, the ResetPasswordScreen()
  /// function is called once per frame (say 30 times per second).
  ///
  /// No state is persisted by the caller in this example. The state of the
  /// most recent calls are instead accessed through calls to functions like
  /// auth.SendPasswordResetEmailLastResult().
  /// @code{.cpp}
  ///  const char* ImageNameForStatus(const firebase::FutureBase& future) {
  ///    assert(future.status() != firebase::kFutureStatusInvalid);
  ///    return future.status() == firebase::kFutureStatusPending
  ///               ? "waiting icon"
  ///               : future.error() == firebase::auth::kAuthErrorNone
  ///                    ? "checkmark icon"
  ///                    : "x mark icon";
  ///  }
  ///
  ///  // This function is called once per frame.
  ///  void ResetPasswordScreen(firebase::auth::Auth& auth) {
  ///    // Gather email address.
  ///    // ShowInputBox() returns a value when `enter` is pressed.
  ///    const std::string email = ShowInputBox("Enter e-mail");
  ///    if (email != "") {
  ///      auth.SendPasswordResetEmail(email.c_str());
  ///    }
  ///
  ///    // Show checkmark, X-mark, or waiting icon beside the
  ///    // email input box, to indicate if email has been sent.
  ///    firebase::Future<void> send_future =
  ///        auth.SendPasswordResetEmailLastResult();
  ///    ShowImage(ImageNameForStatus(send_future));
  ///
  ///    // Display error message if the e-mail could not be sent.
  ///    if (send_future.status() == firebase::kFutureStatusComplete &&
  ///        send_future.error() != firebase::auth::kAuthErrorNone) {
  ///      ShowTextBox(send_future.error_message());
  ///    }
  ///  }
  /// @endcode
  /// @endif
  Future<void> SendPasswordResetEmail(const char* email);

  /// Get results of the most recent call to SendPasswordResetEmail.
  Future<void> SendPasswordResetEmailLastResult() const;

#ifndef SWIG
  /// @brief Registers a listener to changes in the authentication state.
  ///
  /// There can be more than one listener registered at the same time.
  /// The listeners are called asynchronously, possibly on a different thread.
  ///
  /// Authentication state changes are:
  ///   - Right after the listener has been registered
  ///   - When a user signs in
  ///   - When the current user signs out
  ///   - When the current user changes
  ///
  /// It is a recommended practice to always listen to sign-out events, as you
  /// may want to prompt the user to sign in again and maybe restrict the
  /// information or actions they have access to.
  ///
  /// Use RemoveAuthStateListener to unregister a listener.
  ///
  /// @note The caller owns `listener` and is responsible for destroying it.
  /// When `listener` is destroyed, or when Auth is destroyed,
  /// RemoveAuthStateListener is called automatically.
  void AddAuthStateListener(AuthStateListener* listener);

  /// @brief Unregisters a listener of authentication changes.
  ///
  /// Listener must previously been added with AddAuthStateListener.
  ///
  /// Note that listeners unregister themselves automatically when they
  /// are destroyed, and the Auth class unregisters its listeners when the
  /// Auth class itself is destroyed, so this function does not normally need
  /// to be called explicitly.
  void RemoveAuthStateListener(AuthStateListener* listener);

  /// @brief Registers a listener to changes in the ID token state.
  ///
  /// There can be more than one listener registered at the same time.
  /// The listeners are called asynchronously, possibly on a different thread.
  ///
  /// Authentication state changes are:
  ///   - Right after the listener has been registered
  ///   - When a user signs in
  ///   - When the current user signs out
  ///   - When the current user changes
  ///   - When there is a change in the current user's token
  ///
  /// Use RemoveIdTokenListener to unregister a listener.
  ///
  /// @note The caller owns `listener` and is responsible for destroying it.
  /// When `listener` is destroyed, or when Auth is destroyed,
  /// RemoveIdTokenListener is called automatically.
  void AddIdTokenListener(IdTokenListener* listener);

  /// @brief Unregisters a listener of ID token changes.
  ///
  /// Listener must previously been added with AddIdTokenListener.
  ///
  /// Note that listeners unregister themselves automatically when they
  /// are destroyed, and the Auth class unregisters its listeners when the
  /// Auth class itself is destroyed, so this function does not normally need
  /// to be called explicitly.
  void RemoveIdTokenListener(IdTokenListener* listener);
#endif  // not SWIG

#if !defined(DOXYGEN) && !defined(SWIG)
  ///
  /// Modify this Auth instance to communicate with the Firebase Authentication
  /// emulator.
  ///
  void UseEmulator(std::string host, uint32_t port);
#endif  //! defined(DOXYGEN), to hide the api from public documentation.

  /// Gets the App this auth object is connected to.
  App& app();

  /// Returns the Auth object for an App. Creates the Auth if required.
  ///
  /// To get the Auth object for the default app, use,
  /// GetAuth(GetDefaultFirebaseApp());
  ///
  /// If the library Auth fails to initialize, init_result_out will be
  /// written with the result status (if a pointer is given).
  ///
  /// @param[in] app The App to use for the Auth object.
  /// @param[out] init_result_out Optional: If provided, write the init result
  /// here. Will be set to kInitResultSuccess if initialization succeeded, or
  /// kInitResultFailedMissingDependency on Android if Google Play services is
  /// not available on the current device.
  static Auth* GetAuth(App* app, InitResult* init_result_out = nullptr);

 private:
  /// @cond FIREBASE_APP_INTERNAL
  friend class ::firebase::App;
  friend class ::firebase::auth::PhoneAuthProvider;
  friend class ::firebase::auth::AuthRequest;
  friend class IdTokenRefreshListener;
  friend class IdTokenRefreshThread;
  friend class UserDataPersist;
  friend class UserDesktopTest;
  friend class AuthDesktopTest;

#ifndef SWIG
  friend void EnableTokenAutoRefresh(AuthData* authData);
  friend void DisableTokenAutoRefresh(AuthData* authData);
  friend void ResetTokenRefreshCounter(AuthData* authData);
  friend void LogHeartbeat(Auth* auth);
#endif  // not SWIG
  /// @endcond

  // Find Auth instance using App.  Return null if the instance does not exist.
  static Auth* FindAuth(App* app);

  // Provides access to the auth token for the current user.  Returns the
  // current user's auth token, or an empty string, if there isn't one.
  // Note that this can potentially return an expired token from the cache.
  static bool GetAuthTokenForRegistry(App* app, void* /*unused*/, void* out);

  // Provides asynchronous access to the auth token for the current user. Allow
  // the caller to force-refresh the token.  Even without force-refresh, this
  // ensure the future contain a fresh current user's auth token.  This function
  // returns invalid future if user data is not available.
  static bool GetAuthTokenAsyncForRegistry(App* app, void* force_refresh,
                                           void* out_future);

  // Provides access to the current user's uid, equivalent to calling
  // this->current_user().uid(). Returns the current user's uid or
  // an empty string, if there isn't one. The out pointer is expected to point
  // to an instance of std::string.
  static bool GetCurrentUserUidForRegistry(App* app, void* /*unused*/,
                                           void* out);

  // Starts and stops a thread to ensure that the cached auth token is never
  // kept long enough for it to expire.  Refcounted, so multiple classes can
  // register this without causing problems.
  static bool StartTokenRefreshThreadForRegistry(App* app, void* /*unused*/,
                                                 void* /*unused*/);
  static bool StopTokenRefreshThreadForRegistry(App* app, void* /*unused*/,
                                                void* /*unused*/);

  // Adds an indirect auth state listener implemented as a callback and a
  // context object.
  //
  // @param callback a function pointer that takes a single void* argument and
  //     returns void (i.e. it has type void (*)(void*)).
  // @param context a pointer to an arbitrary object that Auth will pass to
  //     the callback when the auth state changes.
  static bool AddAuthStateListenerForRegistry(App* app, void* callback,
                                              void* context);

  // Removes the indirect auth state listener that was added with the same
  // arguments.
  static bool RemoveAuthStateListenerForRegistry(App* app, void* callback,
                                                 void* context);

  // Init and Destroy the platform specific auth data.
  void InitPlatformAuth(AuthData* const auth_data);
  void DestroyPlatformAuth(AuthData* const auth_data);

  // Call GetAuth() to create an Auth object.
  // Constructors and destructors don't make any external calls.
  // They just initialize and deinitialize internal variables.
  Auth(App* app, void* auth_impl);

  // Delete the internal AuthData object.
  void DeleteInternal();

  // This class uses the pimpl mechanism to avoid exposing platform-dependent
  // implementation.
  AuthData* auth_data_;
};

#ifndef SWIG
/// @brief Listener called when there is a change in the authentication state.
///
/// Override base class method to handle authentication state changes.
/// Methods are invoked asynchronously and may be invoked on other threads.
class AuthStateListener {
 public:
  /// @note: Destruction of the listener automatically calls
  /// RemoveAuthStateListener() from the Auths this listener is registered with,
  /// if those Auths have not yet been destroyed.
  virtual ~AuthStateListener();

  /// Called when the authentication state of `auth` changes.
  ///   - Right after the listener has been registered
  ///   - When a user is signed in
  ///   - When the current user is signed out
  ///   - When the current user changes
  ///
  /// @param[in] auth Disambiguates which Auth instance the event
  /// corresponds to, in the case where you are using more than one at the same
  /// time.
  virtual void OnAuthStateChanged(Auth* auth) = 0;

 private:
  /// @cond FIREBASE_APP_INTERNAL
  friend class Auth;
  /// @endcond

  /// The Auths with which this listener has been registered.
  std::vector<Auth*> auths_;
};
#endif  // not SWIG

#ifndef SWIG
/// @brief Listener called when there is a change in the ID token.
///
/// Override base class method to handle ID token changes.
/// Methods are invoked asynchronously and may be invoked on other threads.
class IdTokenListener {
 public:
  /// @note: Destruction of the listener automatically calls
  /// RemoveIdTokenListener() from the Auths this listener is registered with,
  /// if those Auths have not yet been destroyed.
  virtual ~IdTokenListener();

  /// Called when there is a change in the current user's token.
  ///   - Right after the listener has been registered
  ///   - When a user signs in
  ///   - When the current user signs out
  ///   - When the current user changes
  ///   - When there is a change in the current user's token
  ///
  /// @param[in] auth Disambiguates which Auth instance the event
  /// corresponds to, in the case where you are using more than one at the same
  /// time.
  virtual void OnIdTokenChanged(Auth* auth) = 0;

 private:
  /// @cond FIREBASE_APP_INTERNAL
  friend class Auth;
  /// @endcond

  /// The Auths with which this listener has been registered.
  std::vector<Auth*> auths_;
};

#endif  // not SWIG

/// @brief Used to authenticate with Federated Auth Providers.
///
/// The federated auth provider implementation may facilitate multiple provider
/// types in the future, with support for OAuth to start.
class FederatedAuthProvider {
 public:
#ifdef INTERNAL_EXPERIMENTAL
#ifndef SWIG
  /// @brief Contains resulting information of a user authenticated by a
  /// Federated Auth Provider.  This information will be used by the internal
  /// implementation to construct a corresponding User object.
  struct AuthenticatedUserData {
    /// The unique ID identifies the IdP account.
    const char* uid;

    /// [opt] The email of the account.
    const char* email;

    /// Whether the sign-in email is verified.
    bool is_email_verified;

    /// [opt] The display name for the account.
    const char* display_name;

    /// [opt] The username for the account.
    const char* user_name;

    /// [opt] The photo Url for the account, if one exists.
    const char* photo_url;

    /// The linked provider ID (e.g. "google.com" for the Google provider).
    const char* provider_id;

    /// A Firebase Auth ID token for the authenticated user.
    const char* access_token;

    /// A Firebase Auth refresh token for the authenticated user.
    const char* refresh_token;

    /// [opt] IdP user profile data corresponding to the provided credential.
    std::map<Variant, Variant> raw_user_info;

    /// The number of seconds in which the ID token expires.
    uint64_t token_expires_in_seconds;
  };

  /// @brief Handlers for client applications to facilitate federated auth
  /// requests on non-mobile systems.
  template <class T>
  class Handler {
   public:
    virtual ~Handler() {}

    /// @brief Application sign-in handler.
    ///
    /// Invoked in SignIn flows that use Auth::SignInWithProvider.
    ///
    /// The application must implement this method to handle federated auth user
    /// sign-in requests on non-mobile systems.
    ///
    /// @param[in] provider_data Contains information on the provider to
    /// authenticate with.
    /// @param[in] completion_handle Internal data pertaining to this operation
    /// which must be passed to SignInComplete once the handler has completed
    /// the sign in operation.
    ///
    /// @see Auth#SignInWithProvider
    /// @see SignInComplete
    virtual void OnSignIn(const T& provider_data,
                          AuthResultCompletionHandle* completion_handle) {}

    /// Completion for OnSignIn events started by a call to
    /// Auth::SignInWithProvider.
    ///
    /// Invoke this method once the corresponding OnSignIn has been fulfilled.
    /// This method will trigger the associated Future<AuthResult> previously
    /// returned from the Auth::SignInWithProvider method.
    ///
    /// @param[in] completion_handle The handle provided to the application's
    /// FederatedAuthProvider::Handler::OnSignIn method.
    /// @param[in] user_data The application's resulting Firebase user
    /// values following the authorization request.
    /// @param[in] auth_error The enumerated status code of the authorization
    /// request.
    /// @param[in] error_message An optional error message to be set in the
    ///  Future.
    ///
    /// @see OnSignIn
    /// @see Auth::SignInWithProvider
    void SignInComplete(AuthResultCompletionHandle* completion_handle,
                        const AuthenticatedUserData& user_data,
                        AuthError auth_error, const char* error_message);

    /// @brief Application user account link handler.
    ///
    /// Invoked in Link flows that use User::LinkWithProvider.
    ///
    /// The application must implement this method to handle federated auth user
    /// link requests on non-mobile systems.
    ///
    /// @param[in] provider_data Contains information on the provider to
    /// authenticate with.
    /// @param[in] completion_handle Internal data pertaining to this operation
    /// which must be passed to LinkComplete once the handler has completed the
    /// user link operation.
    ///
    /// @see User#LinkWithProvider
    virtual void OnLink(const T& provider_data,
                        AuthResultCompletionHandle* completion_handle) {}

    /// Completion for non-mobile user authorization handlers.
    ///
    /// Invoke this method once the OnLink process has been fulfilled. This
    /// method will trigger the associated Future<AuthResult> previously
    /// returned from an invocation of User::LinkWithProvider.
    ///
    /// @param[in] completion_handle The handle provided to the
    /// application's FederatedAuthProvider::Handler::OnLink method.
    /// @param[in] user_data The application's resulting Firebase user
    /// values following the user link request.
    /// @param[in] auth_error The enumerated status code of the user link
    /// request.
    /// @param[in] error_message An optional error message to be set in the
    ///  Future.
    ///
    /// @see OnLink
    /// @see User#LinkWithProvider
    void LinkComplete(AuthResultCompletionHandle* completion_handle,
                      const AuthenticatedUserData& user_data,
                      AuthError auth_error, const char* error_message);

    /// @brief Application user re-authentication handler.
    ///
    /// The application must implement this method to handle federated auth user
    /// re-authentication requests on non-mobile systems.
    ///
    /// @param[in] provider_data Contains information on the provider to
    /// authenticate with.
    /// @param[in] completion_handle Internal data pertaining to this operation
    /// which must be passed to ReauthenticateComplete once the handler has
    /// completed the reauthentication operation.
    ///
    /// @see User#ReauthenticateWithProviderComplete
    virtual void OnReauthenticate(
        const T& provider_data, AuthResultCompletionHandle* completion_handle) {
    }

    /// Completion for non-mobile user authorization handlers.
    ///
    /// Invoke this method once the OnReauthenticate process has been
    /// fulfilled. This method will trigger the associated Future<AuthResult>
    /// previously returned from an invocation of
    /// User::ReauthenticateWithProvider.
    ///
    /// @param[in] completion_handle The handle provided to the application's
    /// FederatedAuthProvider::Handler::OnReauthenticate method.
    /// @param[in] user_data The application's resulting Firebase user
    /// values following the user re-authentication request.
    /// @param[in] auth_error The enumerated status code of the reauthentication
    /// request.
    /// @param[in] error_message An optional error message to be set in the
    ///  Future.
    ///
    /// @see OnReauthenticate
    /// @see User#ReuthenticateWithProvider
    void ReauthenticateComplete(AuthResultCompletionHandle* completion_handle,
                                const AuthenticatedUserData& user_data,
                                AuthError auth_error,
                                const char* error_message);
  };
#endif  // not SWIG
#endif  // INTERNAL_EXPERIMENTAL

  FederatedAuthProvider() {}
  virtual ~FederatedAuthProvider() {}

#if !defined(DOXYGEN)
  virtual Future<AuthResult> SignIn(AuthData* auth_data) = 0;

  virtual Future<AuthResult> Link(AuthData* auth_data) = 0;

  virtual Future<AuthResult> Reauthenticate(AuthData* auth_data) = 0;
#endif  // !defined(DOXYGEN)

 private:
  friend class ::firebase::auth::Auth;
  friend class ::firebase::auth::User;
};

/// @brief Authenticates with Federated OAuth Providers via the
/// firebase::auth::Auth and firebase::auth::User classes.
///
/// Once configured with a provider id, and with OAuth scope and OAuth custom
/// parameters via an FedeartedOAuthProviderData structure, an object of
/// this class may be used via Auth::SignInWithProvider to sign-in users, or via
/// User::LinkWithProvider and User::ReauthenticateWithProvider for cross
/// account linking and user reauthentication, respectively.
class FederatedOAuthProvider : public FederatedAuthProvider {
 public:
#ifdef INTERNAL_EXPERIMENTAL
#ifndef SWIG
  /// @brief A FederatedAuthProvider typed specifically for OAuth Authentication
  /// handling.
  ///
  /// To be used on non-mobile environments for custom OAuth implementations and
  /// UI flows.
  typedef FederatedAuthProvider::Handler<FederatedOAuthProviderData>
      AuthHandler;
#endif  // !SWIG
#endif  // INTERNAL_EXPERIMENTAL

  /// Constructs an unconfigured provider.
  FederatedOAuthProvider();

  /// Constructs a FederatedOAuthProvider preconfigured with provider data.
  ///
  /// @param[in] provider_data Contains the federated provider id and OAuth
  /// scopes and OAuth custom parameters required for user authentication and
  /// user linking.
  explicit FederatedOAuthProvider(
      const FederatedOAuthProviderData& provider_data);

#ifdef INTERNAL_EXPERIMENTAL
#ifndef SWIG
  /// @brief Constructs a provider with the required information to authenticate
  /// using an OAuth Provider.
  ///
  /// An AuthHandler is required on desktop platforms to facilitate custom
  /// implementations of OAuth authentication. The AuthHandler must outlive the
  /// instance of this OAuthProvider on desktop systems and is ignored on iOS,
  /// tvOS, and Android platforms.
  ///
  /// @param[in] provider_data Contains information on the provider to
  /// authenticate with.
  /// @param[in] handler An FederatedOAuthProviderData typed
  /// FederatedAuthProvider::Handler which be invoked on non-mobile systems
  /// to handle authentication requests.
  FederatedOAuthProvider(const FederatedOAuthProviderData& provider_data,
                         AuthHandler* handler);
#endif  // !SWIG
#endif  // INTERNAL_EXPERIMENTAL

  ~FederatedOAuthProvider() override;

  /// @brief Configures the provider with OAuth provider information.
  ///
  /// @param[in] provider_data Contains the federated provider id and OAuth
  /// scopes and OAuth custom parameters required for user authentication and
  /// user linking.
  void SetProviderData(const FederatedOAuthProviderData& provider_data);

#ifdef INTERNAL_EXPERIMENTAL
#ifndef SWIG
  /// @brief Configures the use of an AuthHandler for non-mobile systems.
  ///
  /// The existence of a handler is required for non-mobile systems, and is
  /// ignored on iOS, tvOS and Android platforms.
  ///
  /// @param[in] handler An FederatedOAuthProviderData typed
  /// FederatedAuthProvider::Handler which be invoked on non-mobile systems
  /// to handle authentication requests. The handler must outlive the instance
  /// of this FederatedOAuthProvider.
  void SetAuthHandler(AuthHandler* handler);
#endif  // !SWIG
#endif  // INTERNAL_EXPERIMENTAL

 private:
  friend class ::firebase::auth::Auth;

#if !defined(DOXYGEN)
  Future<AuthResult> SignIn(AuthData* auth_data) override;

  Future<AuthResult> Link(AuthData* auth_data) override;

  Future<AuthResult> Reauthenticate(AuthData* auth_data) override;
#endif  // !defined(DOXYGEN)

  FederatedOAuthProviderData provider_data_;
#ifdef INTERNAL_EXPERIMENTAL
  AuthHandler* handler_;
#endif  // INTERNAL_EXPERIMENTAL
};

}  // namespace auth
}  // namespace firebase

#endif  // FIREBASE_AUTH_SRC_INCLUDE_FIREBASE_AUTH_H_
