/*
 * 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_CLIENT_CPP_SRC_INCLUDE_FIREBASE_AUTH_CREDENTIAL_H_
#define FIREBASE_AUTH_CLIENT_CPP_SRC_INCLUDE_FIREBASE_AUTH_CREDENTIAL_H_

#include <stdint.h>

#include <string>

#include "firebase/internal/common.h"
#include "firebase/auth/types.h"

namespace firebase {

// Predeclarations.
class App;

template <typename T>
class Future;

namespace auth {

// Predeclarations.
class Auth;
class User;

// Opaque internal types.
struct AuthData;
class ForceResendingTokenData;
struct PhoneAuthProviderData;
struct PhoneListenerData;

/// @brief Authentication credentials for an authentication provider.
///
/// An authentication provider is a service that allows you to authenticate
/// a user. Firebase provides email/password authentication, but there are also
/// external authentication providers such as Facebook.
class Credential {
  /// @cond FIREBASE_APP_INTERNAL
  friend class EmailAuthProvider;
  friend class FacebookAuthProvider;
  friend class GitHubAuthProvider;
  friend class GoogleAuthProvider;
  friend class JniAuthPhoneListener;
  friend class OAuthProvider;
  friend class PhoneAuthProvider;
  friend class PlayGamesAuthProvider;
  friend class TwitterAuthProvider;
  friend class GameCenterAuthProvider;
  /// @endcond

 private:
  /// Should only be created by `Provider` classes.
  ///
  /// @see EmailAuthProvider::GetCredential()
  /// @see FacebookAuthProvider::GetCredential()
  /// @see GoogleAuthProvider::GetCredential()
  explicit Credential(void* impl) : impl_(impl), error_code_(kAuthErrorNone) {}

 public:
  Credential() : impl_(nullptr), error_code_(kAuthErrorNone) {}
  ~Credential();

  /// Copy constructor.
  Credential(const Credential& rhs);

  /// Copy a Credential.
  Credential& operator=(const Credential& rhs);

  /// Gets the name of the Identification Provider (IDP) for the credential.
  ///
  std::string provider() const;

  /// Get whether this credential is valid. A credential can be
  /// invalid in an error condition, e.g. empty username/password.
  ///
  /// @returns True if the credential is valid, false otherwise.
  bool is_valid() const;

 protected:
  /// @cond FIREBASE_APP_INTERNAL
  friend class Auth;
  friend class User;

  /// Platform-specific implementation.
  /// For example, FIRAuthCredential* on iOS.
  void* impl_;

  // If not kAuthErrorNone, then use this error code and string to override
  // whatever error we would normally return when trying to sign-in with this
  // credential.
  AuthError error_code_;
  std::string error_message_;
  /// @endcond
};

/// @brief Use email and password to authenticate.
///
/// Allows developers to use the email and password credentials as they could
/// other auth providers.  For example, this can be used to change passwords,
/// log in, etc.
class EmailAuthProvider {
 public:
  /// Generate a credential from the given email and password.
  ///
  /// @param email E-mail to generate the credential from.
  /// @param password Password to use for the new credential.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* email, const char* password);
};

/// @brief Use an access token provided by Facebook to authenticate.
class FacebookAuthProvider {
 public:
  /// Generate a credential from the given Facebook token.
  ///
  /// @param access_token Facebook token to generate the credential from.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* access_token);

};

/// @brief Use an access token provided by GitHub to authenticate.
class GitHubAuthProvider {
 public:
  /// Generate a credential from the given GitHub token.
  ///
  /// @param token The GitHub OAuth access token.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* token);

};

/// @brief Use an ID token and access token provided by Google to authenticate.
class GoogleAuthProvider {
 public:
  /// Generate a credential from the given Google ID token and/or access token.
  ///
  /// @param id_token Google Sign-In ID token.
  /// @param access_token Google Sign-In access token.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* id_token,
                                  const char* access_token);

};

/// @brief Use a server auth code provided by Google Play Games to authenticate.
class PlayGamesAuthProvider {
 public:
  /// Generate a credential from the given Server Auth Code.
  ///
  /// @param server_auth_code Play Games Sign in Server Auth Code.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* server_auth_code);

};

/// @brief Use a token and secret provided by Twitter to authenticate.
class TwitterAuthProvider {
 public:
  /// Generate a credential from the given Twitter token and password.
  ///
  /// @param token The Twitter OAuth token.
  /// @param secret The Twitter OAuth secret.
  ///
  /// @returns New Credential.
  static Credential GetCredential(const char* token, const char* secret);

};




/// @brief OAuth2.0+UserInfo auth provider (OIDC compliant and non-compliant).
class OAuthProvider {
 public:
  /// Generate a credential for an OAuth2 provider.
  ///
  /// @param provider_id Name of the OAuth2 provider
  ///    TODO(jsanmiya) add examples.
  /// @param id_token The authentication token (OIDC only).
  /// @param access_token TODO(jsanmiya) add explanation (currently missing
  ///    from Android and iOS implementations).
  static Credential GetCredential(const char* provider_id, const char* id_token,
                                  const char* access_token);
};

/// @brief GameCenter (iOS) auth provider
class GameCenterAuthProvider {
 public:
  /// Generate a credential from GameCenter for the current user.
  ///
  /// @return a Future that will be fulfilled with the resulting credential.
  static Future<Credential> GetCredential();

  /// Get the result of the most recent GetCredential() call.
  ///
  /// @return an object which can be used to retrieve the Credential.
  static Future<Credential> GetCredentialLastResult();

  /// Tests to see if the current user is signed in to GameCenter.
  ///
  /// @return true if the user is signed in, false otherwise.
  static bool IsPlayerAuthenticated();
};

/// @brief Use phone number text messages to authenticate.
///
/// Allows developers to use the phone number and SMS verification codes
/// to authenticate a user.
///
/// The verification flow results in a Credential that can be used to,
/// * Sign in to an existing phone number account/sign up with a new
///   phone number
/// * Link a phone number to a current user. This provider will be added to
///   the user.
/// * Update a phone number on an existing user.
/// * Re-authenticate an existing user. This may be needed when a sensitive
///   operation requires the user to be recently logged in.
///
/// Possible verification flows:
/// (1) User manually enters verification code.
/// @if cpp_examples
///     - App calls @ref VerifyPhoneNumber.
///     - Web verification page is displayed to user where they may need to
///       solve a CAPTCHA. [iOS only].
///     - Auth server sends the verification code via SMS to the provided
///       phone number. App receives verification id via Listener::OnCodeSent().
///     - User receives SMS and enters verification code in app's GUI.
///     - App uses user's verification code to call
///       @ref PhoneAuthProvider::GetCredential.
/// @endif
///
/// (2) SMS is automatically retrieved (Android only).
///     - App calls @ref VerifyPhoneNumber with `timeout_ms` > 0.
///     - Auth server sends the verification code via SMS to the provided
///       phone number.
///     - SMS arrives and is automatically retrieved by the operating system.
///       Credential is automatically created and passed to the app via
///       @if cpp_examples
///       Listener::OnVerificationCompleted().
///       @endif
///
/// (3) Phone number is instantly verified (Android only).
///     - App calls @ref VerifyPhoneNumber.
///     - The operating system validates the phone number without having to
///       send an SMS. Credential is automatically created and passed to
///       the app via
///       @if cpp_examples
///       Listener::OnVerificationCompleted().
///       @endif
///
/// @if cpp_examples
/// All three flows can be handled with the example code below.
/// The flow is complete when PhoneVerifier::credential() returns non-NULL.
///
/// @code{.cpp}
/// class PhoneVerifier : public PhoneAuthProvider::Listener {
///  public:
///   PhoneVerifier(const char* phone_number,
///                 PhoneAuthProvider* phone_auth_provider)
///     : display_message_("Sending SMS with verification code"),
///       display_verification_code_input_box_(false),
///       display_resend_sms_button_(false),
///       phone_auth_provider_(phone_auth_provider),
///       phone_number_(phone_number) {
///     SendSms();
///   }
///
///   ~PhoneVerifier() override {}
///
///   void OnVerificationCompleted(Credential credential) override {
///     // Grab `mutex_` for the scope of `lock`. Callbacks can be called on
///     // other threads, so this mutex ensures data access is atomic.
///     MutexLock lock(mutex_);
///     credential_ = credential;
///   }
///
///   void OnVerificationFailed(const std::string& error) override {
///     MutexLock lock(mutex_);
///     display_message_ = "Verification failed with error: " + error;
///   }
///
///   void OnCodeSent(const std::string& verification_id,
///                   const PhoneAuthProvider::ForceResendingToken&
///                       force_resending_token) override {
///     MutexLock lock(mutex_);
///     verification_id_ = verification_id;
///     force_resending_token_ = force_resending_token;
///
///     display_verification_code_input_box_ = true;
///     display_message_ = "Waiting for SMS";
///   }
///
///   void OnCodeAutoRetrievalTimeOut(
///       const std::string& verification_id) override {
///     MutexLock lock(mutex_);
///     display_resend_sms_button_ = true;
///   }
///
///   // Draw the verification GUI on screen and process input events.
///   void Draw() {
///     MutexLock lock(mutex_);
///
///     // Draw an informative message describing what's currently happening.
///     ShowTextBox(display_message_.c_str());
///
///     // Once the time out expires, display a button to resend the SMS.
///     // If the button is pressed, call VerifyPhoneNumber again using the
///     // force_resending_token_.
///     if (display_resend_sms_button_ && !verification_id_.empty()) {
///       const bool resend_sms = ShowTextButton("Resend SMS");
///       if (resend_sms) {
///         SendSms();
///       }
///     }
///
///     // Once the SMS has been sent, allow the user to enter the SMS
///     // verification code into a text box. When the user has completed
///     // entering it, call GetCredential() to complete the flow.
///     if (display_verification_code_input_box_) {
///       const std::string verification_code =
///         ShowInputBox("Verification code");
///       if (!verification_code.empty()) {
///         credential_ = phone_auth_provider_->GetCredential(
///             verification_id_.c_str(), verification_code.c_str());
///       }
///     }
///   }
///
///   // The phone number verification flow is complete when this returns
///   // non-NULL.
///   Credential* credential() {
///     MutexLock lock(mutex_);
///     return credential_.is_valid() ? &credential_ : nullptr;
///   }
///
///  private:
///   void SendSms() {
///     static const uint32_t kAutoVerifyTimeOut = 2000;
///     MutexLock lock(mutex_);
///     phone_auth_provider_->VerifyPhoneNumber(
///         phone_number_.c_str(), kAutoVerifyTimeOut, &force_resending_token_,
///         this);
///     display_resend_sms_button_ = false;
///   }
///
///   // GUI-related variables.
///   std::string display_message_;
///   bool display_verification_code_input_box_;
///   bool display_resend_sms_button_;
///
///   // Phone flow related variables.
///   PhoneAuthProvider* phone_auth_provider_;
///   std::string phone_number_;
///   std::string verification_id_;
///   PhoneAuthProvider::ForceResendingToken force_resending_token_;
///   Credential credential_;
///
///   // Callbacks can be called on other threads, so guard them with a mutex.
///   Mutex mutex_;
/// };
/// @endcode
/// @endif
class PhoneAuthProvider {
 public:
  /// @brief Token to maintain current phone number verification session.
  /// Acquired via @ref Listener::OnCodeSent. Used in @ref VerifyPhoneNumber.
  class ForceResendingToken {
   public:
    /// This token will be invalid until it is assigned a value sent via
    /// @ref Listener::OnCodeSent. It can still be passed into
    /// @ref VerifyPhoneNumber, but it will be ignored.
    ForceResendingToken();

    /// Make `this` token refer to the same phone session as `rhs`.
    ForceResendingToken(const ForceResendingToken& rhs);

    /// Releases internal resources when destructing.
    ~ForceResendingToken();

    /// Make `this` token refer to the same phone session as `rhs`.
    ForceResendingToken& operator=(const ForceResendingToken& rhs);

    /// Return true if `rhs` is refers to the same phone number session as
    /// `this`.
    bool operator==(const ForceResendingToken& rhs) const;

    /// Return true if `rhs` is refers to a different phone number session as
    /// `this`.
    bool operator!=(const ForceResendingToken& rhs) const;

   private:
    friend class JniAuthPhoneListener;
    friend class PhoneAuthProvider;
    ForceResendingTokenData* data_;
  };

  /// @brief Receive callbacks from @ref VerifyPhoneNumber events.
  ///
  /// Please see @ref PhoneAuthProvider for a sample implementation.
  class Listener {
   public:
    Listener();
    virtual ~Listener();

    /// @brief Phone number auto-verification succeeded.
    ///
    /// Called when,
    ///  - auto-sms-retrieval has succeeded--flow (2) in @ref PhoneAuthProvider
    ///  - instant validation has succeeded--flow (3) in @ref PhoneAuthProvider
    ///
    /// @note This callback is never called on iOS, since iOS does not have
    ///    auto-validation. It is always called immediately in the stub desktop
    ///    implementation, however, since it fakes immediate success.
    ///
    /// @param[in] credential The completed credential from the phone number
    ///    verification flow.
    virtual void OnVerificationCompleted(Credential credential) = 0;

    /// @brief Phone number verification failed with an error.
    ///
    /// Called when and error occurred doing phone number authentication.
    /// For example,
    ///  - quota exceeded
    ///  - unknown phone number format
    ///
    /// @param[in] error A description of the failure.
    virtual void OnVerificationFailed(const std::string& error) = 0;

    /// @brief SMS message with verification code sent to phone number.
    ///
    /// Called immediately after Auth server sends a verification SMS.
    /// Once receiving this, you can allow users to manually input the
    /// verification code (even if you're also performing auto-verification).
    /// For user manual input case, get the SMS verification code from the user
    /// and then call @ref GetCredential with the user's code.
    ///
    /// @param[in] verification_id Pass to @ref GetCredential along with the
    ///   user-input verification code to complete the phone number verification
    ///   flow.
    /// @param[in] force_resending_token If the user requests that another SMS
    ///    message be sent, use this when you recall @ref VerifyPhoneNumber.
    virtual void OnCodeSent(const std::string& verification_id,
                            const ForceResendingToken& force_resending_token);

    /// @brief The timeout specified in @ref VerifyPhoneNumber has expired.
    ///
    /// Called once `auto_verify_time_out_ms` has passed.
    /// If using auto SMS retrieval, you can choose to block the UI (do not
    /// allow manual input of the verification code) until timeout is hit.
    ///
    /// @note This callback is called immediately on iOS, since iOS does not
    ///    have auto-validation.
    ///
    /// @param[in] verification_id Identify the transaction that has timed out.
    virtual void OnCodeAutoRetrievalTimeOut(const std::string& verification_id);

   private:
    friend class PhoneAuthProvider;

    /// Back-pointer to the data of the PhoneAuthProvider that
    /// @ref VerifyPhoneNumber was called with. Used internally.
    PhoneListenerData* data_;
  };

  /// Maximum value of `auto_verify_time_out_ms` in @ref VerifyPhoneNumber.
  /// Larger values will be clamped.
  static const uint32_t kMaxTimeoutMs;

  /// Start the phone number authentication operation.
  ///
  /// @param[in] phone_number The phone number identifier supplied by the user.
  ///    Its format is normalized on the server, so it can be in any format
  ///    here.
  /// @param[in] auto_verify_time_out_ms The time out for SMS auto retrieval, in
  ///    miliseconds. Currently SMS auto retrieval is only supported on Android.
  ///    If 0, do not do SMS auto retrieval.
  ///    If positive, try to auto-retrieve the SMS verification code.
  ///    If larger than kMaxTimeoutMs, clamped to kMaxTimeoutMs.
  ///    When the time out is exceeded, listener->OnCodeAutoRetrievalTimeOut()
  ///    is called.
  /// @param[in] force_resending_token If NULL, assume this is a new phone
  ///    number to verify. If not-NULL, bypass the verification session deduping
  ///    and force resending a new SMS.
  ///    This token is received in @ref Listener::OnCodeSent.
  ///    This should only be used when the user presses a Resend SMS button.
  /// @param[in,out] listener Class that receives notification whenever an SMS
  ///    verification event occurs. See sample code at top of class.
  void VerifyPhoneNumber(const char* phone_number,
                         uint32_t auto_verify_time_out_ms,
                         const ForceResendingToken* force_resending_token,
                         Listener* listener);

  /// Generate a credential for the given phone number.
  ///
  /// @param[in] verification_id The id returned when sending the verification
  ///    code. Sent to the caller via @ref Listener::OnCodeSent.
  /// @param[in] verification_code The verification code supplied by the user,
  ///    most likely by a GUI where the user manually enters the code
  ///    received in the SMS sent by @ref VerifyPhoneNumber.
  ///
  /// @returns New Credential.
  Credential GetCredential(const char* verification_id,
                           const char* verification_code);

  /// Return the PhoneAuthProvider for the specified `auth`.
  ///
  /// @param[in] auth The Auth session for which we want to get a
  ///    PhoneAuthProvider.
  static PhoneAuthProvider& GetInstance(Auth* auth);

 private:
  friend struct AuthData;
  friend class JniAuthPhoneListener;

  // Use @ref GetInstance to access the PhoneAuthProvider.
  PhoneAuthProvider();

  // The PhoneAuthProvider is owned by the Auth class.
  ~PhoneAuthProvider();

  PhoneAuthProviderData* data_;
};

}  // namespace auth
}  // namespace firebase

#endif  // FIREBASE_AUTH_CLIENT_CPP_SRC_INCLUDE_FIREBASE_AUTH_CREDENTIAL_H_
