// 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/browser/ui/webui/sync_setup_handler.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/signin_manager.h"
#include "chrome/browser/sync/sync_setup_flow.h"
#include "chrome/browser/sync/util/oauth.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/url_constants.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "ui/base/l10n/l10n_util.h"

using l10n_util::GetStringFUTF16;
using l10n_util::GetStringUTF16;

namespace {

// TODO(jhawkins): Move these to url_constants.h.
const char* kInvalidPasswordHelpUrl =
    "http://www.google.com/support/accounts/bin/answer.py?ctx=ch&answer=27444";
const char* kCanNotAccessAccountUrl =
    "http://www.google.com/support/accounts/bin/answer.py?answer=48598";
#if defined(OS_CHROMEOS)
const char* kEncryptionHelpUrl =
    "http://www.google.com/support/chromeos/bin/answer.py?answer=1181035";
#else
const char* kEncryptionHelpUrl =
    "http://www.google.com/support/chrome/bin/answer.py?answer=1181035";
#endif
const char* kCreateNewAccountUrl =
    "https://www.google.com/accounts/NewAccount?service=chromiumsync";

bool GetAuthData(const std::string& json,
                 std::string* username,
                 std::string* password,
                 std::string* captcha,
                 std::string* access_code) {
  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
    return false;

  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
  if (!result->GetString("user", username) ||
      !result->GetString("pass", password) ||
      !result->GetString("captcha", captcha) ||
      !result->GetString("access_code", access_code)) {
      return false;
  }
  return true;
}

bool GetConfiguration(const std::string& json, SyncConfiguration* config) {
  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
    return false;

  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
  if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything))
    return false;

  // These values need to be kept in sync with where they are written in
  // choose_datatypes.html.
  bool sync_bookmarks;
  if (!result->GetBoolean("syncBookmarks", &sync_bookmarks))
    return false;
  if (sync_bookmarks)
    config->data_types.insert(syncable::BOOKMARKS);

  bool sync_preferences;
  if (!result->GetBoolean("syncPreferences", &sync_preferences))
    return false;
  if (sync_preferences)
    config->data_types.insert(syncable::PREFERENCES);

  bool sync_themes;
  if (!result->GetBoolean("syncThemes", &sync_themes))
    return false;
  if (sync_themes)
    config->data_types.insert(syncable::THEMES);

  bool sync_passwords;
  if (!result->GetBoolean("syncPasswords", &sync_passwords))
    return false;
  if (sync_passwords)
    config->data_types.insert(syncable::PASSWORDS);

  bool sync_autofill;
  if (!result->GetBoolean("syncAutofill", &sync_autofill))
    return false;
  if (sync_autofill)
    config->data_types.insert(syncable::AUTOFILL);

  bool sync_extensions;
  if (!result->GetBoolean("syncExtensions", &sync_extensions))
    return false;
  if (sync_extensions)
    config->data_types.insert(syncable::EXTENSIONS);

  bool sync_typed_urls;
  if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls))
    return false;
  if (sync_typed_urls)
    config->data_types.insert(syncable::TYPED_URLS);

  bool sync_search_engines;
  if (!result->GetBoolean("syncSearchEngines", &sync_search_engines))
    return false;
  if (sync_search_engines)
    config->data_types.insert(syncable::SEARCH_ENGINES);

  bool sync_sessions;
  if (!result->GetBoolean("syncSessions", &sync_sessions))
    return false;
  if (sync_sessions)
    config->data_types.insert(syncable::SESSIONS);

  bool sync_apps;
  if (!result->GetBoolean("syncApps", &sync_apps))
    return false;
  if (sync_apps)
    config->data_types.insert(syncable::APPS);

  // Encryption settings.
  if (!result->GetBoolean("encryptAllData", &config->encrypt_all))
    return false;

  // Passphrase settings.
  bool have_passphrase;
  if (!result->GetBoolean("usePassphrase", &have_passphrase))
    return false;

  if (have_passphrase) {
    bool is_gaia;
    if (!result->GetBoolean("isGooglePassphrase", &is_gaia))
      return false;
    std::string passphrase;
    if (!result->GetString("passphrase", &passphrase))
      return false;
    // The user provided a passphrase - pass it off to SyncSetupFlow as either
    // the secondary or GAIA passphrase as appropriate.
    if (is_gaia) {
      config->set_gaia_passphrase = true;
      config->gaia_passphrase = passphrase;
    } else {
      config->set_secondary_passphrase = true;
      config->secondary_passphrase = passphrase;
    }
  }
  return true;
}

bool GetPassphrase(const std::string& json, std::string* passphrase) {
  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
    return false;

  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
  return result->GetString("passphrase", passphrase);
}

}  // namespace

SyncSetupHandler::SyncSetupHandler() : flow_(NULL) {
}

SyncSetupHandler::~SyncSetupHandler() {
  // This case is hit when the user performs a back navigation.
  if (flow_)
    flow_->OnDialogClosed("");
}

void SyncSetupHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
  GetStaticLocalizedValues(localized_strings);
}

void SyncSetupHandler::GetStaticLocalizedValues(
    DictionaryValue* localized_strings) {
  DCHECK(localized_strings);

  localized_strings->SetString(
      "invalidPasswordHelpURL",
      google_util::StringAppendGoogleLocaleParam(kInvalidPasswordHelpUrl));
  localized_strings->SetString(
      "cannotAccessAccountURL",
      google_util::StringAppendGoogleLocaleParam(kCanNotAccessAccountUrl));
  localized_strings->SetString(
      "createNewAccountURL",
      google_util::StringAppendGoogleLocaleParam(kCreateNewAccountUrl));
  localized_strings->SetString(
      "introduction",
      GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION,
                      GetStringUTF16(IDS_PRODUCT_NAME)));
  localized_strings->SetString(
      "chooseDataTypesInstructions",
      GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS,
                      GetStringUTF16(IDS_PRODUCT_NAME)));
  localized_strings->SetString(
      "encryptionInstructions",
      GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS,
                      GetStringUTF16(IDS_PRODUCT_NAME)));
  localized_strings->SetString(
      "encryptionHelpURL",
      google_util::StringAppendGoogleLocaleParam(kEncryptionHelpUrl));
  localized_strings->SetString(
      "passphraseEncryptionMessage",
      GetStringFUTF16(IDS_SYNC_PASSPHRASE_ENCRYPTION_MESSAGE,
                      GetStringUTF16(IDS_PRODUCT_NAME)));
  localized_strings->SetString(
      "passphraseRecover",
      GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER,
                      ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
                          chrome::kSyncGoogleDashboardURL))));
  localized_strings->SetString(
      "promoTitle",
      GetStringFUTF16(IDS_SYNC_PROMO_TITLE,
                      GetStringUTF16(IDS_PRODUCT_NAME)));
  localized_strings->SetString(
      "promoMessageTitle",
      GetStringFUTF16(IDS_SYNC_PROMO_MESSAGE_TITLE,
                      GetStringUTF16(IDS_SHORT_PRODUCT_NAME)));

  static OptionsStringResource resources[] = {
    { "syncSetupOverlayTitle", IDS_SYNC_SETUP_TITLE },
    { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
    { "signinPrefix", IDS_SYNC_LOGIN_SIGNIN_PREFIX },
    { "signinSuffix", IDS_SYNC_LOGIN_SIGNIN_SUFFIX },
    { "cannotBeBlank", IDS_SYNC_CANNOT_BE_BLANK },
    { "emailLabel", IDS_SYNC_LOGIN_EMAIL },
    { "passwordLabel", IDS_SYNC_LOGIN_PASSWORD },
    { "invalidCredentials", IDS_SYNC_INVALID_USER_CREDENTIALS },
    { "signin", IDS_SYNC_SIGNIN },
    { "couldNotConnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT },
    { "cannotAccessAccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT },
    { "createAccount", IDS_SYNC_CREATE_ACCOUNT },
    { "cancel", IDS_CANCEL },
    { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
    { "errorSigningIn", IDS_SYNC_ERROR_SIGNING_IN },
    { "captchaInstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS },
    { "invalidAccessCode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL },
    { "enterAccessCode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL },
    { "getAccessCodeHelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL },
    { "getAccessCodeURL", IDS_SYNC_GET_ACCESS_CODE_URL },
    { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
    { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
    { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
    { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
    { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
    { "themes", IDS_SYNC_DATATYPE_THEMES },
    { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
    { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
    { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
    { "apps", IDS_SYNC_DATATYPE_APPS },
    { "searchEngines", IDS_SYNC_DATATYPE_SEARCH_ENGINES },
    { "openTabs", IDS_SYNC_DATATYPE_TABS },
    { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR },
    { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
    { "encryptAllLabel", IDS_SYNC_ENCRYPT_ALL_LABEL },
    { "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE },
    { "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT },
    { "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE },
    { "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT },
    { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
    { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
    { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
    { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
    { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
    { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
    { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
    { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
    { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
    { "passphraseSectionTitle", IDS_SYNC_PASSPHRASE_SECTION_TITLE },
    { "privacyDashboardLink", IDS_SYNC_PRIVACY_DASHBOARD_LINK_LABEL },
    { "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE },
    { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
    { "enterOtherPassphraseBody", IDS_SYNC_ENTER_OTHER_PASSPHRASE_BODY },
    { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
    { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
    { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
    { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
    { "cancelWarningHeader", IDS_SYNC_PASSPHRASE_CANCEL_WARNING_HEADER },
    { "cancelWarning", IDS_SYNC_PASSPHRASE_CANCEL_WARNING },
    { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
    { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
    { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
    { "sectionExplicitMessagePostfix",
        IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
    { "encryptedDataTypesTitle", IDS_SYNC_ENCRYPTION_DATA_TYPES_TITLE },
    { "encryptSensitiveOption", IDS_SYNC_ENCRYPT_SENSITIVE_DATA },
    { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA },
    { "encryptAllOption", IDS_SYNC_ENCRYPT_ALL_DATA },
    { "aspWarningText", IDS_SYNC_ASP_PASSWORD_WARNING_TEXT },
    { "promoPageTitle", IDS_NEW_TAB_TITLE},
    { "promoMessageBody", IDS_SYNC_PROMO_MESSAGE_BODY},
    { "promoSkipMessage", IDS_SYNC_PROMO_SKIP_MESSAGE},
    { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON},
    { "promoLearnMoreShow", IDS_SYNC_PROMO_LEARN_MORE_SHOW},
    { "promoLearnMoreHide", IDS_SYNC_PROMO_LEARN_MORE_HIDE},
    { "promoInformation", IDS_SYNC_PROMO_INFORMATION},
  };

  RegisterStrings(localized_strings, resources, arraysize(resources));
}

void SyncSetupHandler::Initialize() {
}

void SyncSetupHandler::OnGetOAuthTokenSuccess(const std::string& oauth_token) {
  flow_->OnUserSubmittedOAuth(oauth_token);
}

void SyncSetupHandler::OnGetOAuthTokenFailure(
    const GoogleServiceAuthError& error) {
  CloseSyncSetup();
}

void SyncSetupHandler::RegisterMessages() {
  web_ui_->RegisterMessageCallback("SyncSetupDidClosePage",
      base::Bind(&SyncSetupHandler::OnDidClosePage,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupSubmitAuth",
      base::Bind(&SyncSetupHandler::HandleSubmitAuth,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupConfigure",
      base::Bind(&SyncSetupHandler::HandleConfigure,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupPassphrase",
      base::Bind(&SyncSetupHandler::HandlePassphraseEntry,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupPassphraseCancel",
      base::Bind(&SyncSetupHandler::HandlePassphraseCancel,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupAttachHandler",
      base::Bind(&SyncSetupHandler::HandleAttachHandler,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupShowErrorUI",
      base::Bind(&SyncSetupHandler::HandleShowErrorUI,
                 base::Unretained(this)));
  web_ui_->RegisterMessageCallback("SyncSetupShowSetupUI",
      base::Bind(&SyncSetupHandler::HandleShowSetupUI,
                 base::Unretained(this)));
}

// Ideal(?) solution here would be to mimic the ClientLogin overlay.  Since
// this UI must render an external URL, that overlay cannot be used directly.
// The current implementation is functional, but fails asthetically.
// TODO(rickcam): Bug 90711: Update UI for OAuth sign-in flow
void SyncSetupHandler::ShowOAuthLogin() {
  DCHECK(browser_sync::IsUsingOAuth());

  Profile* profile = Profile::FromWebUI(web_ui_);
  oauth_login_.reset(new GaiaOAuthFetcher(this,
                                          profile->GetRequestContext(),
                                          profile,
                                          GaiaConstants::kSyncServiceOAuth));
  oauth_login_->SetAutoFetchLimit(GaiaOAuthFetcher::OAUTH1_REQUEST_TOKEN);
  oauth_login_->StartGetOAuthToken();
}

void SyncSetupHandler::ShowGaiaLogin(const DictionaryValue& args) {
  DCHECK(!browser_sync::IsUsingOAuth());
  StringValue page("login");
  web_ui_->CallJavascriptFunction(
      "SyncSetupOverlay.showSyncSetupPage", page, args);
}

void SyncSetupHandler::ShowGaiaSuccessAndClose() {
  web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndClose");
}

void SyncSetupHandler::ShowGaiaSuccessAndSettingUp() {
  web_ui_->CallJavascriptFunction("SyncSetupOverlay.showSuccessAndSettingUp");
}

void SyncSetupHandler::ShowConfigure(const DictionaryValue& args) {
  StringValue page("configure");
  web_ui_->CallJavascriptFunction(
      "SyncSetupOverlay.showSyncSetupPage", page, args);
}

void SyncSetupHandler::ShowPassphraseEntry(const DictionaryValue& args) {
  StringValue page("passphrase");
  web_ui_->CallJavascriptFunction(
      "SyncSetupOverlay.showSyncSetupPage", page, args);
}

void SyncSetupHandler::ShowSettingUp() {
  StringValue page("settingUp");
  web_ui_->CallJavascriptFunction(
      "SyncSetupOverlay.showSyncSetupPage", page);
}

void SyncSetupHandler::ShowSetupDone(const std::wstring& user) {
  StringValue page("done");
  web_ui_->CallJavascriptFunction(
      "SyncSetupOverlay.showSyncSetupPage", page);
}

void SyncSetupHandler::SetFlow(SyncSetupFlow* flow) {
  flow_ = flow;
}

void SyncSetupHandler::Focus() {
  static_cast<RenderViewHostDelegate*>(web_ui_->tab_contents())->Activate();
}

void SyncSetupHandler::OnDidClosePage(const ListValue* args) {
  CloseSyncSetup();
}

void SyncSetupHandler::HandleSubmitAuth(const ListValue* args) {
  std::string json;
  if (!args->GetString(0, &json)) {
    NOTREACHED() << "Could not read JSON argument";
    return;
  }

  if (json.empty())
    return;

  std::string username, password, captcha, access_code;
  if (!GetAuthData(json, &username, &password, &captcha, &access_code)) {
    // The page sent us something that we didn't understand.
    // This probably indicates a programming error.
    NOTREACHED();
    return;
  }

  if (flow_)
    flow_->OnUserSubmittedAuth(username, password, captcha, access_code);
}

void SyncSetupHandler::HandleConfigure(const ListValue* args) {
  std::string json;
  if (!args->GetString(0, &json)) {
    NOTREACHED() << "Could not read JSON argument";
    return;
  }
  if (json.empty()) {
    NOTREACHED();
    return;
  }

  SyncConfiguration configuration;
  if (!GetConfiguration(json, &configuration)) {
    // The page sent us something that we didn't understand.
    // This probably indicates a programming error.
    NOTREACHED();
    return;
  }

  DCHECK(flow_);
  flow_->OnUserConfigured(configuration);
}

void SyncSetupHandler::HandlePassphraseEntry(const ListValue* args) {
  std::string json;
  if (!args->GetString(0, &json)) {
    NOTREACHED() << "Could not read JSON argument";
    return;
  }

  if (json.empty())
    return;

  std::string passphrase;
  if (!GetPassphrase(json, &passphrase)) {
    // Couldn't understand what the page sent.  Indicates a programming error.
    NOTREACHED();
    return;
  }

  DCHECK(flow_);
  flow_->OnPassphraseEntry(passphrase);
}

void SyncSetupHandler::HandlePassphraseCancel(const ListValue* args) {
  DCHECK(flow_);
  flow_->OnPassphraseCancel();
}

void SyncSetupHandler::HandleAttachHandler(const ListValue* args) {
  OpenSyncSetup();
}

void SyncSetupHandler::HandleShowErrorUI(const ListValue* args) {
  DCHECK(!flow_);

  Profile* profile = Profile::FromWebUI(web_ui_);
  ProfileSyncService* service = profile->GetProfileSyncService();
  DCHECK(service);

  service->get_wizard().Step(SyncSetupWizard::NONFATAL_ERROR);

  // Show the Sync Setup page.
  if (service->get_wizard().IsVisible()) {
    service->get_wizard().Focus();
  } else {
    StringValue page("syncSetup");
    web_ui_->CallJavascriptFunction("OptionsPage.navigateToPage", page);
  }
}

void SyncSetupHandler::HandleShowSetupUI(const ListValue* args) {
  DCHECK(!flow_);
  ShowSetupUI();
}

void SyncSetupHandler::CloseSyncSetup() {
  if (flow_) {
    flow_->OnDialogClosed(std::string());
    flow_ = NULL;
  }
}

void SyncSetupHandler::OpenSyncSetup() {
  DCHECK(web_ui_);
  DCHECK(!flow_);

  Profile* profile = Profile::FromWebUI(web_ui_);
  ProfileSyncService* service = profile->GetProfileSyncService();
  if (!service) {
    // If there's no sync service, the user tried to manually invoke a syncSetup
    // URL, but sync features are disabled.  We need to close the overlay for
    // this (rare) case.
    web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay");
    return;
  }

  // If the wizard is not visible, step into the appropriate UI state.
  if (!service->get_wizard().IsVisible())
    ShowSetupUI();

  // The SyncSetupFlow will set itself as the |flow_|.
  if (!service->get_wizard().AttachSyncSetupHandler(this)) {
    // If attach fails, a wizard is already activated and attached to a flow
    // handler.
    web_ui_->CallJavascriptFunction("OptionsPage.closeOverlay");
    service->get_wizard().Focus();
  }
}