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

#import "ios/chrome/browser/passwords/password_generation_agent.h"

#include <stddef.h>

#import "base/ios/weak_nsobject.h"
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_block.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/password_generator.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/password_form.h"
#include "components/autofill/core/common/password_generation_util.h"
#import "components/autofill/ios/browser/js_suggestion_manager.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "google_apis/gaia/gaia_urls.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#include "ios/chrome/browser/experimental_flags.h"
#import "ios/chrome/browser/passwords/js_password_manager.h"
#import "ios/chrome/browser/passwords/password_generation_edit_view.h"
#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
#include "ios/web/public/url_scheme_util.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#include "ios/web/public/web_state/web_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace {

// Target length of generated passwords.
const int kGeneratedPasswordLength = 20;

// The minimum number of text fields that a form needs to be considered as
// an account creation form.
const size_t kMinimumTextFieldsForAccountCreation = 3;

// Returns true if |urls| contains |url|.
bool VectorContainsURL(const std::vector<GURL>& urls, const GURL& url) {
  return std::find(urls.begin(), urls.end(), url) != urls.end();
}

// Returns whether |field| should be considered a text field. Implementation
// mirrors that of password_controller.js.
// TODO(dconnelly): Figure out how (and if) to determine if |field| is visible.
// http://crbug.com/433856
bool IsTextField(const autofill::FormFieldData& field) {
  return field.form_control_type == "text" ||
         field.form_control_type == "email" ||
         field.form_control_type == "number" ||
         field.form_control_type == "tel" || field.form_control_type == "url" ||
         field.form_control_type == "search" ||
         field.form_control_type == "password";
}

}  // namespace

@interface PasswordGenerationAgent ()<CRWWebStateObserver,
                                      FormInputAccessoryViewProvider,
                                      PasswordGenerationOfferDelegate,
                                      PasswordGenerationPromptDelegate>

// Clears all per-page state.
- (void)clearState;

// Returns YES if |form| belongs to the GAIA realm.
- (BOOL)formHasGAIARealm:(const autofill::PasswordForm&)form;

// Returns YES if |form| contains enough text fields to be considered as an
// account creation form.
- (BOOL)formHasEnoughTextFieldsForAccountCreation:
    (const autofill::PasswordForm&)form;

// Returns a list of all password fields in |form|.
- (std::vector<autofill::FormFieldData>)passwordFieldsInForm:
    (const autofill::PasswordForm&)form;

// Merges the data from local heuristics, the autofill server, and the password
// manager to find the field that should trigger the password generation UI
// when selected by the user. The resulting field is stored in
// |_passwordGenerationField|. This logic is nearly identical to that of the
// upstream autofill::PasswordGenerationAgent::DetermineGenerationElement.
// TODO(dconnelly): Try to find a way to share this code with the upstream
// implementation, even though it lives in the renderer.
// http://crbug.com/434679.
- (void)determinePasswordGenerationField;

// Returns YES if the specified form and field should trigger the
// password generation UI.
- (BOOL)isGenerationForm:(const base::string16&)formName
                   field:(const base::string16&)fieldName;

// The name of the form identified as an account creation form, if it exists.
- (NSString*)passwordGenerationFormName;

// Hides and deletes the alert with generation prompt, if it exists.
- (void)hideAlert;

// Returns an autoreleased input accessory view corresponding to the current
// password generation state. Should only be used when password generation
// should be offered for the currently-focused form field.
- (UIView*)currentAccessoryView;

// Initializes PasswordGenerationAgent, which observes the specified web state,
// and allows injecting JavaScript managers.
- (instancetype)
         initWithWebState:(web::WebState*)webState
          passwordManager:(password_manager::PasswordManager*)passwordManager
    passwordManagerDriver:(password_manager::PasswordManagerDriver*)driver
        JSPasswordManager:(JsPasswordManager*)JSPasswordManager
      JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
      passwordsUiDelegate:(id<PasswordsUiDelegate>)UIDelegate
    NS_DESIGNATED_INITIALIZER;

@end

@implementation PasswordGenerationAgent {
  // Bridge to observe the web state from Objective-C.
  scoped_ptr<web::WebStateObserverBridge> _webStateObserverBridge;

  // The origin URLs of forms on the current page that contain account creation
  // forms as reported by autofill.
  std::vector<GURL> _accountCreationFormOrigins;

  // The origin URLs of forms on the current page that have not been blacklisted
  // by the password manager.
  std::vector<GURL> _allowedGenerationFormOrigins;

  // Stores the account creation form we detected on the page.
  scoped_ptr<autofill::PasswordForm> _possibleAccountCreationForm;

  // Password fields found in |_possibleAccountCreationForm|.
  std::vector<autofill::FormFieldData> _passwordFields;

  // The password field that triggers the password generation UI.
  scoped_ptr<autofill::FormFieldData> _passwordGenerationField;

  // Wrapper for suggestion JavaScript. Used for form navigation.
  base::scoped_nsobject<JsSuggestionManager> _JSSuggestionManager;

  // Wrapper for passwords JavaScript. Used for form filling.
  base::scoped_nsobject<JsPasswordManager> _JSPasswordManager;

  // Driver that is passed to PasswordManager when a password is generated.
  password_manager::PasswordManagerDriver* _passwordManagerDriver;

  // PasswordManager to inform when a password is generated.
  password_manager::PasswordManager* _passwordManager;

  // Callback to update the custom keyboard accessory view. Will be non-nil when
  // this PasswordGenerationAgent controls the keyboard accessory view.
  base::mac::ScopedBlock<AccessoryViewReadyCompletion>
      _accessoryViewReadyCompletion;

  // The delegate for controlling the password generation UI.
  base::scoped_nsprotocol<id<PasswordsUiDelegate>> _passwords_ui_delegate;

  // The password that was generated and accepted by the user.
  base::scoped_nsobject<NSString> _generatedPassword;
}

- (instancetype)init {
  NOTREACHED();
  return nil;
}

- (instancetype)
         initWithWebState:(web::WebState*)webState
          passwordManager:(password_manager::PasswordManager*)passwordManager
    passwordManagerDriver:(password_manager::PasswordManagerDriver*)driver
      passwordsUiDelegate:(id<PasswordsUiDelegate>)UIDelegate {
  JsPasswordManager* JSPasswordManager =
      base::mac::ObjCCast<JsPasswordManager>([webState->GetJSInjectionReceiver()
          instanceOfClass:[JsPasswordManager class]]);
  JsSuggestionManager* JSSuggestionManager =
      base::mac::ObjCCast<JsSuggestionManager>(
          [webState->GetJSInjectionReceiver()
              instanceOfClass:[JsSuggestionManager class]]);
  return [self initWithWebState:webState
                passwordManager:passwordManager
          passwordManagerDriver:driver
              JSPasswordManager:JSPasswordManager
            JSSuggestionManager:JSSuggestionManager
            passwordsUiDelegate:UIDelegate];
}

- (instancetype)
         initWithWebState:(web::WebState*)webState
          passwordManager:(password_manager::PasswordManager*)passwordManager
    passwordManagerDriver:(password_manager::PasswordManagerDriver*)driver
        JSPasswordManager:(JsPasswordManager*)JSPasswordManager
      JSSuggestionManager:(JsSuggestionManager*)JSSuggestionManager
      passwordsUiDelegate:(id<PasswordsUiDelegate>)UIDelegate {
  DCHECK([NSThread isMainThread]);
  DCHECK(webState);
  DCHECK_EQ([self class], [PasswordGenerationAgent class]);
  self = [super init];
  if (self) {
    _passwordManager = passwordManager;
    _passwordManagerDriver = driver;
    _JSPasswordManager.reset([JSPasswordManager retain]);
    _JSSuggestionManager.reset([JSSuggestionManager retain]);
    _webStateObserverBridge.reset(
        new web::WebStateObserverBridge(webState, self));
    _passwords_ui_delegate.reset([UIDelegate retain]);
  }
  return self;
}

- (void)dealloc {
  DCHECK([NSThread isMainThread]);
  [super dealloc];
}

- (autofill::PasswordForm*)possibleAccountCreationForm {
  return _possibleAccountCreationForm.get();
}

- (const std::vector<autofill::FormFieldData>&)passwordFields {
  return _passwordFields;
}

- (autofill::FormFieldData*)passwordGenerationField {
  return _passwordGenerationField.get();
}

- (void)clearState {
  [self hideAlert];
  _accountCreationFormOrigins.clear();
  _allowedGenerationFormOrigins.clear();
  _possibleAccountCreationForm.reset();
  _passwordFields.clear();
  _passwordGenerationField.reset();
  _generatedPassword.reset();
}

- (BOOL)formHasGAIARealm:(const autofill::PasswordForm&)form {
  // Do not generate password for GAIA since it is used to retrieve the
  // generated paswords.
  return GURL(form.signon_realm) ==
         GaiaUrls::GetInstance()->gaia_login_form_realm();
}

- (BOOL)formHasEnoughTextFieldsForAccountCreation:
    (const autofill::PasswordForm&)form {
  size_t numVisibleTextFields = 0;
  for (const auto& formFieldData : form.form_data.fields)
    if (IsTextField(formFieldData))
      ++numVisibleTextFields;
  return (numVisibleTextFields >= kMinimumTextFieldsForAccountCreation);
}

- (std::vector<autofill::FormFieldData>)passwordFieldsInForm:
    (const autofill::PasswordForm&)form {
  std::vector<autofill::FormFieldData> passwordFields;
  for (const auto& formFieldData : form.form_data.fields)
    if (formFieldData.form_control_type == "password")
      passwordFields.push_back(formFieldData);
  return passwordFields;
}

- (void)registerAccountCreationForms:
    (const std::vector<autofill::FormData>&)forms {
  for (const auto& form : forms)
    _accountCreationFormOrigins.push_back(form.origin);
  [self determinePasswordGenerationField];
}

- (void)allowPasswordGenerationForForm:(const autofill::PasswordForm&)form {
  _allowedGenerationFormOrigins.push_back(form.origin);
  [self determinePasswordGenerationField];
}

- (void)processParsedPasswordForms:
    (const std::vector<autofill::PasswordForm>&)forms {
  // TODO(dconnelly): Find a way to share some of this logic with the desktop
  // agent. http://crbug.com/434679.
  for (const auto& passwordForm : forms) {
    if ([self formHasGAIARealm:passwordForm])
      continue;
    if (![self formHasEnoughTextFieldsForAccountCreation:passwordForm])
      continue;
    std::vector<autofill::FormFieldData> passwordFields(
        [self passwordFieldsInForm:passwordForm]);
    if (passwordFields.empty())
      continue;
    // This form checks out.
    _possibleAccountCreationForm.reset(
        new autofill::PasswordForm(passwordForm));
    _passwordFields = passwordFields;
    break;
  }
  [self determinePasswordGenerationField];
}

- (void)determinePasswordGenerationField {
  // If the current page hasn't been parsed yet or doesn't contain any account
  // creation forms, wait.
  if (!_possibleAccountCreationForm)
    return;
  if (_passwordFields.empty())
    return;

  // If the form origin hasn't been cleared by both the autofill and the
  // password manager, wait.
  GURL origin = _possibleAccountCreationForm->origin;
  if (!experimental_flags::UseOnlyLocalHeuristicsForPasswordGeneration()) {
    if (!VectorContainsURL(_allowedGenerationFormOrigins, origin))
      return;
    if (!VectorContainsURL(_accountCreationFormOrigins, origin))
      return;
  }

  // Use the first password field in the form as the generation field.
  _passwordGenerationField.reset(
      new autofill::FormFieldData(_passwordFields[0]));
  autofill::password_generation::LogPasswordGenerationEvent(
      autofill::password_generation::GENERATION_AVAILABLE);
}

- (id<FormInputAccessoryViewProvider>)accessoryViewProvider {
  return self;
}

- (BOOL)isGenerationForm:(const base::string16&)formName
                   field:(const base::string16&)fieldName {
  return _possibleAccountCreationForm &&
         _possibleAccountCreationForm->form_data.name == formName &&
         _passwordGenerationField &&
         _passwordGenerationField->name == fieldName;
}

- (NSString*)passwordGenerationFormName {
  return base::SysUTF16ToNSString(_possibleAccountCreationForm->form_data.name);
}

- (void)hideAlert {
  [_passwords_ui_delegate hideGenerationAlert];
}

- (UIView*)currentAccessoryView {
  return [_generatedPassword length] > 0
             ? [[[PasswordGenerationEditView alloc]
                   initWithPassword:_generatedPassword] autorelease]
             : [[[PasswordGenerationOfferView alloc] initWithDelegate:self]
                   autorelease];
}

#pragma mark -
#pragma mark CRWWebStateObserver

- (void)webStateDidLoadPage:(web::WebState*)webState {
  [self clearState];
}

- (void)webStateDestroyed:(web::WebState*)webState {
  [self clearState];
  _webStateObserverBridge.reset();
}

#pragma mark -
#pragma mark PasswordGenerationPromptDelegate

- (void)acceptPasswordGeneration:(id)sender {
  [self hideAlert];
  base::WeakNSObject<PasswordGenerationAgent> weakSelf(self);
  id completionHandler = ^(BOOL success) {
    if (!success)
      return;
    base::scoped_nsobject<PasswordGenerationAgent> strongSelf(
        [weakSelf retain]);
    if (!strongSelf)
      return;
    if (strongSelf.get()->_passwordManager) {
      // Might be null in tests.
      strongSelf.get()->_passwordManager->SetHasGeneratedPasswordForForm(
          strongSelf.get()->_passwordManagerDriver,
          *strongSelf.get()->_possibleAccountCreationForm, true);
    }
    if (strongSelf.get()->_accessoryViewReadyCompletion) {
      strongSelf.get()->_accessoryViewReadyCompletion.get()(
          [strongSelf currentAccessoryView], strongSelf);
    }
  };
  [_JSPasswordManager fillPasswordForm:[self passwordGenerationFormName]
                 withGeneratedPassword:_generatedPassword
                     completionHandler:completionHandler];
}

- (void)showSavedPasswords:(id)sender {
  [self hideAlert];
  base::scoped_nsobject<GenericChromeCommand> command(
      [[GenericChromeCommand alloc]
          initWithTag:IDC_SHOW_SAVE_PASSWORDS_SETTINGS]);
  [command executeOnMainWindow];
}

#pragma mark -
#pragma mark PasswordGenerationOfferDelegate

- (void)generatePassword {
  _generatedPassword.reset([base::SysUTF8ToNSString(
      autofill::PasswordGenerator(kGeneratedPasswordLength).Generate()) copy]);
  [_passwords_ui_delegate showGenerationAlertWithPassword:_generatedPassword
                                        andPromptDelegate:self];
}

#pragma mark -
#pragma mark FormInputAccessoryViewProvider

- (id<FormInputAccessoryViewDelegate>)accessoryViewDelegate {
  return nil;
}

- (void)setAccessoryViewDelegate:(id<FormInputAccessoryViewDelegate>)delegate {
  // Unused.
}

- (void)
    checkIfAccessoryViewIsAvailableForFormNamed:(const std::string&)formName
                                      fieldName:(const std::string&)fieldName
                                       webState:(web::WebState*)webState
                              completionHandler:
                                  (AccessoryViewAvailableCompletion)
                                      completionHandler {
  completionHandler(
      _passwordGenerationField &&
      [self isGenerationForm:base::UTF8ToUTF16(formName)
                       field:base::UTF8ToUTF16(fieldName)]);
}

- (void)retrieveAccessoryViewForFormNamed:(const std::string&)formName
                                fieldName:(const std::string&)fieldName
                                    value:(const std::string&)value
                                     type:(const std::string&)type
                                 webState:(web::WebState*)webState
                 accessoryViewUpdateBlock:
                     (AccessoryViewReadyCompletion)accessoryViewUpdateBlock {
  DCHECK(!_accessoryViewReadyCompletion);
  if ([_generatedPassword length] > 0)
    _generatedPassword.reset([base::SysUTF8ToNSString(value) copy]);
  accessoryViewUpdateBlock([self currentAccessoryView], self);
  _accessoryViewReadyCompletion.reset([accessoryViewUpdateBlock copy]);
}

- (void)inputAccessoryViewControllerDidReset:
    (FormInputAccessoryViewController*)controller {
  [self hideAlert];
  DCHECK(_accessoryViewReadyCompletion);
  _accessoryViewReadyCompletion.reset();
}

- (void)resizeAccessoryView {
  DCHECK(_accessoryViewReadyCompletion);
  _accessoryViewReadyCompletion.get()([self currentAccessoryView], self);
}

- (BOOL)getLogKeyboardAccessoryMetrics {
  // Only store metrics for regular Autofill, not passwords.
  return NO;
}

@end