// 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 "build/build_config.h"

#include <algorithm>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/task.h"
#include "base/threading/thread_restrictions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/net/gaia/token_service.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/autofill_model_associator.h"
#include "chrome/browser/sync/glue/autofill_profile_model_associator.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/database_model_worker.h"
#include "chrome/browser/sync/glue/history_model_worker.h"
#include "chrome/browser/sync/glue/http_bridge.h"
#include "chrome/browser/sync/glue/password_model_worker.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/notifier/sync_notifier.h"
#include "chrome/browser/sync/notifier/sync_notifier_factory.h"
#include "chrome/browser/sync/sessions/session_state.h"
// TODO(tim): Remove this! We should have a syncapi pass-thru instead.
#include "chrome/browser/sync/syncable/directory_manager.h"  // Cryptographer.
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/pref_names.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
#include "googleurl/src/gurl.h"
#include "webkit/glue/webkit_glue.h"

static const int kSaveChangesIntervalSeconds = 10;
static const FilePath::CharType kSyncDataFolderName[] =
    FILE_PATH_LITERAL("Sync Data");

using browser_sync::DataTypeController;
using sync_notifier::SyncNotifierFactory;
typedef TokenService::TokenAvailableDetails TokenAvailableDetails;

typedef GoogleServiceAuthError AuthError;

namespace browser_sync {

using sessions::SyncSessionSnapshot;
using sync_api::SyncCredentials;

SyncBackendHost::SyncBackendHost(Profile* profile)
    : core_(new Core(ALLOW_THIS_IN_INITIALIZER_LIST(this))),
      core_thread_("Chrome_SyncCoreThread"),
      frontend_loop_(MessageLoop::current()),
      profile_(profile),
      frontend_(NULL),
      sync_data_folder_path_(
          profile_->GetPath().Append(kSyncDataFolderName)),
      last_auth_error_(AuthError::None()),
      syncapi_initialized_(false) {
}

SyncBackendHost::SyncBackendHost()
    : core_thread_("Chrome_SyncCoreThread"),
      frontend_loop_(MessageLoop::current()),
      profile_(NULL),
      frontend_(NULL),
      last_auth_error_(AuthError::None()),
      syncapi_initialized_(false) {
}

SyncBackendHost::~SyncBackendHost() {
  DCHECK(!core_ && !frontend_) << "Must call Shutdown before destructor.";
  DCHECK(registrar_.workers.empty());
}

void SyncBackendHost::Initialize(
    SyncFrontend* frontend,
    const GURL& sync_service_url,
    const syncable::ModelTypeSet& types,
    net::URLRequestContextGetter* baseline_context_getter,
    const SyncCredentials& credentials,
    bool delete_sync_data_folder) {
  if (!core_thread_.Start())
    return;

  frontend_ = frontend;
  DCHECK(frontend);

  // Create a worker for the UI thread and route bookmark changes to it.
  // TODO(tim): Pull this into a method to reuse.  For now we don't even
  // need to lock because we init before the syncapi exists and we tear down
  // after the syncapi is destroyed.  Make sure to NULL-check workers_ indices
  // when a new type is synced as the worker may already exist and you just
  // need to update routing_info_.
  registrar_.workers[GROUP_DB] = new DatabaseModelWorker();
  registrar_.workers[GROUP_UI] = new UIModelWorker();
  registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker();

  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kEnableSyncTypedUrls) || types.count(syncable::TYPED_URLS)) {
    // TODO(tim): Bug 53916.  HistoryModelWorker crashes, so avoid adding it
    // unless specifically requested until bug is fixed.
    registrar_.workers[GROUP_HISTORY] =
        new HistoryModelWorker(
            profile_->GetHistoryService(Profile::IMPLICIT_ACCESS));
  }

  // Any datatypes that we want the syncer to pull down must
  // be in the routing_info map.  We set them to group passive, meaning that
  // updates will be applied, but not dispatched to the UI thread yet.
  for (syncable::ModelTypeSet::const_iterator it = types.begin();
      it != types.end(); ++it) {
    registrar_.routing_info[(*it)] = GROUP_PASSIVE;
  }

  PasswordStore* password_store =
      profile_->GetPasswordStore(Profile::IMPLICIT_ACCESS);
  if (password_store) {
    registrar_.workers[GROUP_PASSWORD] =
        new PasswordModelWorker(password_store);
  } else {
    LOG_IF(WARNING, types.count(syncable::PASSWORDS) > 0) << "Password store "
        << "not initialized, cannot sync passwords";
    registrar_.routing_info.erase(syncable::PASSWORDS);
  }

  // Nigori is populated by default now.
  registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE;

  // TODO(akalin): Create SyncNotifier here and pass it in as part of
  // DoInitializeOptions.
  core_->CreateSyncNotifier(baseline_context_getter);

  InitCore(Core::DoInitializeOptions(
      sync_service_url,
      MakeHttpBridgeFactory(baseline_context_getter),
      credentials,
      delete_sync_data_folder,
      RestoreEncryptionBootstrapToken(),
      false));
}

void SyncBackendHost::PersistEncryptionBootstrapToken(
    const std::string& token) {
  PrefService* prefs = profile_->GetPrefs();

  prefs->SetString(prefs::kEncryptionBootstrapToken, token);
  prefs->ScheduleSavePersistentPrefs();
}

std::string SyncBackendHost::RestoreEncryptionBootstrapToken() {
  PrefService* prefs = profile_->GetPrefs();
  std::string token = prefs->GetString(prefs::kEncryptionBootstrapToken);
  return token;
}

bool SyncBackendHost::IsNigoriEnabled() const {
  base::AutoLock lock(registrar_lock_);
  // Note that NIGORI is only ever added/removed from routing_info once,
  // during initialization / first configuration, so there is no real 'race'
  // possible here or possibility of stale return value.
  return registrar_.routing_info.find(syncable::NIGORI) !=
      registrar_.routing_info.end();
}

bool SyncBackendHost::IsUsingExplicitPassphrase() {
  return IsNigoriEnabled() && syncapi_initialized_ &&
      core_->syncapi()->InitialSyncEndedForAllEnabledTypes() &&
      core_->syncapi()->IsUsingExplicitPassphrase();
}

bool SyncBackendHost::IsCryptographerReady(
    const sync_api::BaseTransaction* trans) const {
  return syncapi_initialized_ && trans->GetCryptographer()->is_ready();
}

JsBackend* SyncBackendHost::GetJsBackend() {
  if (syncapi_initialized_) {
    return core_.get();
  } else {
    NOTREACHED();
    return NULL;
  }
}

sync_api::HttpPostProviderFactory* SyncBackendHost::MakeHttpBridgeFactory(
    net::URLRequestContextGetter* getter) {
  return new HttpBridgeFactory(getter);
}

void SyncBackendHost::InitCore(const Core::DoInitializeOptions& options) {
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoInitialize,
                        options));
}

void SyncBackendHost::UpdateCredentials(const SyncCredentials& credentials) {
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(),
                        &SyncBackendHost::Core::DoUpdateCredentials,
                        credentials));
}

void SyncBackendHost::StartSyncingWithServer() {
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoStartSyncing));
}

void SyncBackendHost::SetPassphrase(const std::string& passphrase,
                                    bool is_explicit) {
  if (!IsNigoriEnabled()) {
    LOG(WARNING) << "Silently dropping SetPassphrase request.";
    return;
  }

  // This should only be called by the frontend.
  DCHECK_EQ(MessageLoop::current(), frontend_loop_);
  if (core_->processing_passphrase()) {
    VLOG(1) << "Attempted to call SetPassphrase while already waiting for "
            << " result from previous SetPassphrase call. Silently dropping.";
    return;
  }
  core_->set_processing_passphrase();

  // If encryption is enabled and we've got a SetPassphrase
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoSetPassphrase,
                        passphrase, is_explicit));
}

void SyncBackendHost::Shutdown(bool sync_disabled) {
  // Thread shutdown should occur in the following order:
  // - SyncerThread
  // - CoreThread
  // - UI Thread (stops some time after we return from this call).
  if (core_thread_.IsRunning()) {  // Not running in tests.
    core_thread_.message_loop()->PostTask(FROM_HERE,
        NewRunnableMethod(core_.get(),
                          &SyncBackendHost::Core::DoShutdown,
                          sync_disabled));
  }

  // Before joining the core_thread_, we wait for the UIModelWorker to
  // give us the green light that it is not depending on the frontend_loop_ to
  // process any more tasks. Stop() blocks until this termination condition
  // is true.
  if (ui_worker())
    ui_worker()->Stop();

  // Stop will return once the thread exits, which will be after DoShutdown
  // runs. DoShutdown needs to run from core_thread_ because the sync backend
  // requires any thread that opened sqlite handles to relinquish them
  // personally. We need to join threads, because otherwise the main Chrome
  // thread (ui loop) can exit before DoShutdown finishes, at which point
  // virtually anything the sync backend does (or the post-back to
  // frontend_loop_ by our Core) will epically fail because the CRT won't be
  // initialized.
  // Since we are blocking the UI thread here, we need to turn ourselves in
  // with the ThreadRestriction police.  For sentencing and how we plan to fix
  // this, see bug 19757.
  {
    base::ThreadRestrictions::ScopedAllowIO allow_io;
    core_thread_.Stop();
  }

  registrar_.routing_info.clear();
  registrar_.workers[GROUP_DB] = NULL;
  registrar_.workers[GROUP_HISTORY] = NULL;
  registrar_.workers[GROUP_UI] = NULL;
  registrar_.workers[GROUP_PASSIVE] = NULL;
  registrar_.workers[GROUP_PASSWORD] = NULL;
  registrar_.workers.erase(GROUP_DB);
  registrar_.workers.erase(GROUP_HISTORY);
  registrar_.workers.erase(GROUP_UI);
  registrar_.workers.erase(GROUP_PASSIVE);
  registrar_.workers.erase(GROUP_PASSWORD);
  frontend_ = NULL;
  core_ = NULL;  // Releases reference to core_.
}

syncable::AutofillMigrationState
    SyncBackendHost::GetAutofillMigrationState() {
  return core_->syncapi()->GetAutofillMigrationState();
}

void SyncBackendHost::SetAutofillMigrationState(
    syncable::AutofillMigrationState state) {
  return core_->syncapi()->SetAutofillMigrationState(state);
}

syncable::AutofillMigrationDebugInfo
    SyncBackendHost::GetAutofillMigrationDebugInfo() {
  return core_->syncapi()->GetAutofillMigrationDebugInfo();
}

void SyncBackendHost::SetAutofillMigrationDebugInfo(
    syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set,
    const syncable::AutofillMigrationDebugInfo& info) {
  return core_->syncapi()->SetAutofillMigrationDebugInfo(property_to_set, info);
}

void SyncBackendHost::ConfigureAutofillMigration() {
  if (GetAutofillMigrationState() == syncable::NOT_DETERMINED) {
    sync_api::ReadTransaction trans(GetUserShare());
    sync_api::ReadNode autofil_root_node(&trans);

    // Check for the presence of autofill node.
    if (!autofil_root_node.InitByTagLookup(browser_sync::kAutofillTag)) {
        SetAutofillMigrationState(syncable::INSUFFICIENT_INFO_TO_DETERMINE);
      return;
    }

    // Check for children under autofill node.
    if (autofil_root_node.GetFirstChildId() == static_cast<int64>(0)) {
      SetAutofillMigrationState(syncable::INSUFFICIENT_INFO_TO_DETERMINE);
      return;
    }

    sync_api::ReadNode autofill_profile_root_node(&trans);

    // Check for the presence of autofill profile root node.
    if (!autofill_profile_root_node.InitByTagLookup(
       browser_sync::kAutofillProfileTag)) {
      SetAutofillMigrationState(syncable::NOT_MIGRATED);
      return;
    }

    // If our state is not determined then we should not have the autofill
    // profile node.
    DCHECK(false);

    // just set it as not migrated.
    SetAutofillMigrationState(syncable::NOT_MIGRATED);
    return;
  }
}

SyncBackendHost::PendingConfigureDataTypesState::
PendingConfigureDataTypesState() : deleted_type(false) {}

SyncBackendHost::PendingConfigureDataTypesState::
~PendingConfigureDataTypesState() {}

// static
SyncBackendHost::PendingConfigureDataTypesState*
    SyncBackendHost::MakePendingConfigModeState(
        const DataTypeController::TypeMap& data_type_controllers,
        const syncable::ModelTypeSet& types,
        CancelableTask* ready_task,
        ModelSafeRoutingInfo* routing_info) {
  PendingConfigureDataTypesState* state = new PendingConfigureDataTypesState();
  for (DataTypeController::TypeMap::const_iterator it =
           data_type_controllers.begin();
       it != data_type_controllers.end(); ++it) {
    syncable::ModelType type = it->first;
    // If a type is not specified, remove it from the routing_info.
    if (types.count(type) == 0) {
      state->deleted_type = true;
      routing_info->erase(type);
    } else {
      // Add a newly specified data type as GROUP_PASSIVE into the
      // routing_info, if it does not already exist.
      if (routing_info->count(type) == 0) {
        (*routing_info)[type] = GROUP_PASSIVE;
        state->added_types.set(type);
      }
    }
  }

  state->ready_task.reset(ready_task);
  state->initial_types = types;
  return state;
}

void SyncBackendHost::ConfigureDataTypes(
    const DataTypeController::TypeMap& data_type_controllers,
    const syncable::ModelTypeSet& types,
    CancelableTask* ready_task) {
  // Only one configure is allowed at a time.
  DCHECK(!pending_config_mode_state_.get());
  DCHECK(!pending_download_state_.get());
  DCHECK(syncapi_initialized_);

  if (types.count(syncable::AUTOFILL_PROFILE) != 0) {
    ConfigureAutofillMigration();
  }

  {
    base::AutoLock lock(registrar_lock_);
    pending_config_mode_state_.reset(
        MakePendingConfigModeState(data_type_controllers, types, ready_task,
                                   &registrar_.routing_info));
  }

  StartConfiguration(NewCallback(core_.get(),
      &SyncBackendHost::Core::FinishConfigureDataTypes));
}

void SyncBackendHost::StartConfiguration(Callback0::Type* callback) {
  // Put syncer in the config mode. DTM will put us in normal mode once it is.
  // done. This is to ensure we dont do a normal sync when we are doing model
  // association.
  core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
    core_.get(),&SyncBackendHost::Core::DoStartConfiguration, callback));
}

void SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop() {
  DCHECK_EQ(MessageLoop::current(), frontend_loop_);
  // Nudge the syncer. This is necessary for both datatype addition/deletion.
  //
  // Deletions need a nudge in order to ensure the deletion occurs in a timely
  // manner (see issue 56416).
  //
  // In the case of additions, on the next sync cycle, the syncer should
  // notice that the routing info has changed and start the process of
  // downloading updates for newly added data types.  Once this is
  // complete, the configure_state_.ready_task_ is run via an
  // OnInitializationComplete notification.

  if (pending_config_mode_state_->deleted_type) {
    core_thread_.message_loop()->PostTask(FROM_HERE,
        NewRunnableMethod(core_.get(),
        &SyncBackendHost::Core::DeferNudgeForCleanup));
  }

  if (pending_config_mode_state_->added_types.none() &&
      !core_->syncapi()->InitialSyncEndedForAllEnabledTypes()) {
    LOG(WARNING) << "No new types, but initial sync not finished."
                 << "Possible sync db corruption / removal.";
    // TODO(tim): Log / UMA / count this somehow?
    // TODO(tim): If no added types, we could (should?) config only for
    // types that are needed... but this is a rare corruption edge case or
    // implies the user mucked around with their syncdb, so for now do all.
    pending_config_mode_state_->added_types =
        syncable::ModelTypeBitSetFromSet(
            pending_config_mode_state_->initial_types);
  }

  // If we've added types, we always want to request a nudge/config (even if
  // the initial sync is ended), in case we could not decrypt the data.
  if (pending_config_mode_state_->added_types.none()) {
    // No new types - just notify the caller that the types are available.
    pending_config_mode_state_->ready_task->Run();
  } else {
    pending_download_state_.reset(pending_config_mode_state_.release());

    syncable::ModelTypeBitSet types_copy(pending_download_state_->added_types);
    if (IsNigoriEnabled())
      types_copy.set(syncable::NIGORI);
    core_thread_.message_loop()->PostTask(FROM_HERE,
         NewRunnableMethod(core_.get(),
                           &SyncBackendHost::Core::DoRequestConfig,
                           types_copy));
  }

  pending_config_mode_state_.reset();

  // Notify the SyncManager about the new types.
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(),
                        &SyncBackendHost::Core::DoUpdateEnabledTypes));
}

void SyncBackendHost::EncryptDataTypes(
    const syncable::ModelTypeSet& encrypted_types) {
  core_thread_.message_loop()->PostTask(FROM_HERE,
     NewRunnableMethod(core_.get(),
                       &SyncBackendHost::Core::DoEncryptDataTypes,
                       encrypted_types));
}

void SyncBackendHost::RequestNudge(const tracked_objects::Location& location) {
  core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge,
                        location));
}

void SyncBackendHost::ActivateDataType(
    DataTypeController* data_type_controller,
    ChangeProcessor* change_processor) {
  base::AutoLock lock(registrar_lock_);

  // Ensure that the given data type is in the PASSIVE group.
  browser_sync::ModelSafeRoutingInfo::iterator i =
      registrar_.routing_info.find(data_type_controller->type());
  DCHECK(i != registrar_.routing_info.end());
  DCHECK((*i).second == GROUP_PASSIVE);
  syncable::ModelType type = data_type_controller->type();
  // Change the data type's routing info to its group.
  registrar_.routing_info[type] = data_type_controller->model_safe_group();

  // Add the data type's change processor to the list of change
  // processors so it can receive updates.
  DCHECK_EQ(processors_.count(type), 0U);
  processors_[type] = change_processor;
}

void SyncBackendHost::DeactivateDataType(
    DataTypeController* data_type_controller,
    ChangeProcessor* change_processor) {
  base::AutoLock lock(registrar_lock_);
  registrar_.routing_info.erase(data_type_controller->type());

  std::map<syncable::ModelType, ChangeProcessor*>::size_type erased =
      processors_.erase(data_type_controller->type());
  DCHECK_EQ(erased, 1U);
}

bool SyncBackendHost::RequestClearServerData() {
  core_thread_.message_loop()->PostTask(FROM_HERE,
     NewRunnableMethod(core_.get(),
     &SyncBackendHost::Core::DoRequestClearServerData));
  return true;
}

SyncBackendHost::Core::~Core() {
}

void SyncBackendHost::Core::NotifyPassphraseRequired(bool for_decryption) {
  if (!host_ || !host_->frontend_)
    return;

  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  if (processing_passphrase_) {
    VLOG(1) << "Core received OnPassphraseRequired while processing a "
            << "passphrase. Silently dropping.";
    return;
  }
  host_->frontend_->OnPassphraseRequired(for_decryption);
}

void SyncBackendHost::Core::NotifyPassphraseFailed() {
  if (!host_ || !host_->frontend_)
    return;

  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  // When a passphrase fails, we just unset our waiting flag and trigger a
  // OnPassphraseRequired(true).
  processing_passphrase_ = false;
  host_->frontend_->OnPassphraseRequired(true);
}

void SyncBackendHost::Core::NotifyPassphraseAccepted(
    const std::string& bootstrap_token) {
  if (!host_ || !host_->frontend_)
    return;

  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  processing_passphrase_ = false;
  host_->PersistEncryptionBootstrapToken(bootstrap_token);
  host_->frontend_->OnPassphraseAccepted();
}

void SyncBackendHost::Core::NotifyUpdatedToken(const std::string& token) {
  if (!host_)
    return;
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  TokenAvailableDetails details(GaiaConstants::kSyncService, token);
  NotificationService::current()->Notify(
      NotificationType::TOKEN_UPDATED,
      NotificationService::AllSources(),
      Details<const TokenAvailableDetails>(&details));
}

void SyncBackendHost::Core::NotifyEncryptionComplete(
    const syncable::ModelTypeSet& encrypted_types) {
  if (!host_)
    return;
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
  host_->frontend_->OnEncryptionComplete(encrypted_types);
}

void SyncBackendHost::Core::FinishConfigureDataTypes() {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop));
}

void SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop() {
  host_->FinishConfigureDataTypesOnFrontendLoop();
}


void SyncBackendHost::Core::CreateSyncNotifier(
    const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) {
  const std::string& client_info = webkit_glue::GetUserAgent(GURL());
  SyncNotifierFactory sync_notifier_factory(client_info);
  sync_notifier_.reset(sync_notifier_factory.CreateSyncNotifier(
      *CommandLine::ForCurrentProcess(),
      request_context_getter));
}

SyncBackendHost::Core::DoInitializeOptions::DoInitializeOptions(
    const GURL& service_url,
    sync_api::HttpPostProviderFactory* http_bridge_factory,
    const sync_api::SyncCredentials& credentials,
    bool delete_sync_data_folder,
    const std::string& restored_key_for_bootstrapping,
    bool setup_for_test_mode)
    : service_url(service_url),
      http_bridge_factory(http_bridge_factory),
      credentials(credentials),
      delete_sync_data_folder(delete_sync_data_folder),
      restored_key_for_bootstrapping(restored_key_for_bootstrapping),
      setup_for_test_mode(setup_for_test_mode) {
}

SyncBackendHost::Core::DoInitializeOptions::~DoInitializeOptions() {}

sync_api::UserShare* SyncBackendHost::GetUserShare() const {
  DCHECK(syncapi_initialized_);
  return core_->syncapi()->GetUserShare();
}

SyncBackendHost::Status SyncBackendHost::GetDetailedStatus() {
  DCHECK(syncapi_initialized_);
  return core_->syncapi()->GetDetailedStatus();
}

SyncBackendHost::StatusSummary SyncBackendHost::GetStatusSummary() {
  DCHECK(syncapi_initialized_);
  return core_->syncapi()->GetStatusSummary();
}

string16 SyncBackendHost::GetAuthenticatedUsername() const {
  DCHECK(syncapi_initialized_);
  return UTF8ToUTF16(core_->syncapi()->GetAuthenticatedUsername());
}

const GoogleServiceAuthError& SyncBackendHost::GetAuthError() const {
  return last_auth_error_;
}

const SyncSessionSnapshot* SyncBackendHost::GetLastSessionSnapshot() const {
  return last_snapshot_.get();
}

void SyncBackendHost::GetWorkers(std::vector<ModelSafeWorker*>* out) {
  base::AutoLock lock(registrar_lock_);
  out->clear();
  for (WorkerMap::const_iterator it = registrar_.workers.begin();
       it != registrar_.workers.end(); ++it) {
    out->push_back((*it).second);
  }
}

void SyncBackendHost::GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
  base::AutoLock lock(registrar_lock_);
  ModelSafeRoutingInfo copy(registrar_.routing_info);
  out->swap(copy);
}

bool SyncBackendHost::HasUnsyncedItems() const {
  DCHECK(syncapi_initialized_);
  return core_->syncapi()->HasUnsyncedItems();
}

SyncBackendHost::Core::Core(SyncBackendHost* backend)
    : host_(backend),
      syncapi_(new sync_api::SyncManager()),
      sync_manager_observer_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      parent_router_(NULL),
      processing_passphrase_(false),
      deferred_nudge_for_cleanup_requested_(false) {
}

// Helper to construct a user agent string (ASCII) suitable for use by
// the syncapi for any HTTP communication. This string is used by the sync
// backend for classifying client types when calculating statistics.
std::string MakeUserAgentForSyncapi() {
  std::string user_agent;
  user_agent = "Chrome ";
#if defined(OS_WIN)
  user_agent += "WIN ";
#elif defined(OS_LINUX)
  user_agent += "LINUX ";
#elif defined(OS_FREEBSD)
  user_agent += "FREEBSD ";
#elif defined(OS_OPENBSD)
  user_agent += "OPENBSD ";
#elif defined(OS_MACOSX)
  user_agent += "MAC ";
#endif
  chrome::VersionInfo version_info;
  if (!version_info.is_valid()) {
    DLOG(ERROR) << "Unable to create chrome::VersionInfo object";
    return user_agent;
  }

  user_agent += version_info.Version();
  user_agent += " (" + version_info.LastChange() + ")";
  if (!version_info.IsOfficialBuild())
    user_agent += "-devel";
  return user_agent;
}

void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  processing_passphrase_ = false;

  // Blow away the partial or corrupt sync data folder before doing any more
  // initialization, if necessary.
  if (options.delete_sync_data_folder) {
    DeleteSyncDataFolder();
  }

  // Make sure that the directory exists before initializing the backend.
  // If it already exists, this will do no harm.
  bool success = file_util::CreateDirectory(host_->sync_data_folder_path());
  DCHECK(success);

  syncapi_->AddObserver(this);
  const FilePath& path_str = host_->sync_data_folder_path();
  success = syncapi_->Init(
      path_str,
      (options.service_url.host() + options.service_url.path()).c_str(),
      options.service_url.EffectiveIntPort(),
      options.service_url.SchemeIsSecure(),
      options.http_bridge_factory,
      host_,  // ModelSafeWorkerRegistrar.
      MakeUserAgentForSyncapi().c_str(),
      options.credentials,
      sync_notifier_.get(),
      options.restored_key_for_bootstrapping,
      options.setup_for_test_mode);
  DCHECK(success) << "Syncapi initialization failed!";
}

void SyncBackendHost::Core::DoUpdateCredentials(
    const SyncCredentials& credentials) {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  syncapi_->UpdateCredentials(credentials);
}

void SyncBackendHost::Core::DoUpdateEnabledTypes() {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  syncapi_->UpdateEnabledTypes();
}

void SyncBackendHost::Core::DoStartSyncing() {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  syncapi_->StartSyncing();
  if (deferred_nudge_for_cleanup_requested_)
    syncapi_->RequestNudge(FROM_HERE);
  deferred_nudge_for_cleanup_requested_ = false;
}

void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase,
                                            bool is_explicit) {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  syncapi_->SetPassphrase(passphrase, is_explicit);
}

bool SyncBackendHost::Core::processing_passphrase() const {
  DCHECK(MessageLoop::current() == host_->frontend_loop_);
  return processing_passphrase_;
}

void SyncBackendHost::Core::set_processing_passphrase() {
  DCHECK(MessageLoop::current() == host_->frontend_loop_);
  processing_passphrase_ = true;
}

void SyncBackendHost::Core::DoEncryptDataTypes(
    const syncable::ModelTypeSet& encrypted_types) {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
  syncapi_->EncryptDataTypes(encrypted_types);
}

void SyncBackendHost::Core::DoRequestConfig(
    const syncable::ModelTypeBitSet& added_types) {
  syncapi_->RequestConfig(added_types);
}

void SyncBackendHost::Core::DoStartConfiguration(Callback0::Type* callback) {
  syncapi_->StartConfigurationMode(callback);
}

UIModelWorker* SyncBackendHost::ui_worker() {
  ModelSafeWorker* w = registrar_.workers[GROUP_UI];
  if (w == NULL)
    return NULL;
  if (w->GetModelSafeGroup() != GROUP_UI)
    NOTREACHED();
  return static_cast<UIModelWorker*>(w);
}

void SyncBackendHost::Core::DoShutdown(bool sync_disabled) {
  DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());

  save_changes_timer_.Stop();
  syncapi_->Shutdown();  // Stops the SyncerThread.
  syncapi_->RemoveObserver(this);
  DisconnectChildJsEventRouter();
  host_->ui_worker()->OnSyncerShutdownComplete();

  if (sync_disabled)
    DeleteSyncDataFolder();

  host_ = NULL;
}

ChangeProcessor* SyncBackendHost::Core::GetProcessor(
    syncable::ModelType model_type) {
  std::map<syncable::ModelType, ChangeProcessor*>::const_iterator it =
      host_->processors_.find(model_type);

  // Until model association happens for a datatype, it will not appear in
  // the processors list.  During this time, it is OK to drop changes on
  // the floor (since model association has not happened yet).  When the
  // data type is activated, model association takes place then the change
  // processor is added to the processors_ list.  This all happens on
  // the UI thread so we will never drop any changes after model
  // association.
  if (it == host_->processors_.end())
    return NULL;

  if (!IsCurrentThreadSafeForModel(model_type)) {
    NOTREACHED() << "Changes applied on wrong thread.";
    return NULL;
  }

  // Now that we're sure we're on the correct thread, we can access the
  // ChangeProcessor.
  ChangeProcessor* processor = it->second;

  // Ensure the change processor is willing to accept changes.
  if (!processor->IsRunning())
    return NULL;

  return processor;
}

void SyncBackendHost::Core::OnChangesApplied(
    syncable::ModelType model_type,
    const sync_api::BaseTransaction* trans,
    const sync_api::SyncManager::ChangeRecord* changes,
    int change_count) {
  if (!host_ || !host_->frontend_) {
    DCHECK(false) << "OnChangesApplied called after Shutdown?";
    return;
  }
  ChangeProcessor* processor = GetProcessor(model_type);
  if (!processor)
    return;

  processor->ApplyChangesFromSyncModel(trans, changes, change_count);
}

void SyncBackendHost::Core::OnChangesComplete(
    syncable::ModelType model_type) {
  if (!host_ || !host_->frontend_) {
    DCHECK(false) << "OnChangesComplete called after Shutdown?";
    return;
  }

  ChangeProcessor* processor = GetProcessor(model_type);
  if (!processor)
    return;

  // This call just notifies the processor that it can commit, it already
  // buffered any changes it plans to makes so needs no further information.
  processor->CommitChangesFromSyncModel();
}


void SyncBackendHost::Core::OnSyncCycleCompleted(
    const SyncSessionSnapshot* snapshot) {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &Core::HandleSyncCycleCompletedOnFrontendLoop,
      new SyncSessionSnapshot(*snapshot)));
}

void SyncBackendHost::Core::HandleSyncCycleCompletedOnFrontendLoop(
    SyncSessionSnapshot* snapshot) {
  if (!host_ || !host_->frontend_)
    return;
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  host_->last_snapshot_.reset(snapshot);

  const syncable::ModelTypeSet& to_migrate =
      snapshot->syncer_status.types_needing_local_migration;
  if (!to_migrate.empty())
    host_->frontend_->OnMigrationNeededForTypes(to_migrate);

  // If we are waiting for a configuration change, check here to see
  // if this sync cycle has initialized all of the types we've been
  // waiting for.
  if (host_->pending_download_state_.get()) {
    bool found_all_added = true;
    for (syncable::ModelTypeSet::const_iterator it =
             host_->pending_download_state_->initial_types.begin();
         it != host_->pending_download_state_->initial_types.end();
         ++it) {
      if (host_->pending_download_state_->added_types.test(*it))
        found_all_added &= snapshot->initial_sync_ended.test(*it);
    }
    if (!found_all_added) {
      NOTREACHED() << "Update didn't return updates for all types requested.";
    } else {
      host_->pending_download_state_->ready_task->Run();
    }
    host_->pending_download_state_.reset();
  }
  host_->frontend_->OnSyncCycleCompleted();
}

void SyncBackendHost::Core::OnInitializationComplete() {
  if (!host_ || !host_->frontend_)
    return;  // We may have been told to Shutdown before initialization
             // completed.

  // We could be on some random sync backend thread, so MessageLoop::current()
  // can definitely be null in here.
  host_->frontend_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this,
                        &Core::HandleInitalizationCompletedOnFrontendLoop));

  // Initialization is complete, so we can schedule recurring SaveChanges.
  host_->core_thread_.message_loop()->PostTask(FROM_HERE,
      NewRunnableMethod(this, &Core::StartSavingChanges));
}

void SyncBackendHost::Core::HandleInitalizationCompletedOnFrontendLoop() {
  if (!host_)
    return;
  host_->HandleInitializationCompletedOnFrontendLoop();
}

void SyncBackendHost::HandleInitializationCompletedOnFrontendLoop() {
  if (!frontend_)
    return;
  syncapi_initialized_ = true;
  frontend_->OnBackendInitialized();
}

bool SyncBackendHost::Core::IsCurrentThreadSafeForModel(
    syncable::ModelType model_type) {
  base::AutoLock lock(host_->registrar_lock_);

  browser_sync::ModelSafeRoutingInfo::const_iterator routing_it =
      host_->registrar_.routing_info.find(model_type);
  if (routing_it == host_->registrar_.routing_info.end())
    return false;
  browser_sync::ModelSafeGroup group = routing_it->second;
  WorkerMap::const_iterator worker_it = host_->registrar_.workers.find(group);
  if (worker_it == host_->registrar_.workers.end())
    return false;
  ModelSafeWorker* worker = worker_it->second;
  return worker->CurrentThreadIsWorkThread();
}

void SyncBackendHost::Core::OnAuthError(const AuthError& auth_error) {
  // Post to our core loop so we can modify state. Could be on another thread.
  host_->frontend_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &Core::HandleAuthErrorEventOnFrontendLoop,
      auth_error));
}

void SyncBackendHost::Core::OnPassphraseRequired(bool for_decryption) {
  host_->frontend_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &Core::NotifyPassphraseRequired, for_decryption));
}

void SyncBackendHost::Core::OnPassphraseFailed() {
  host_->frontend_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &Core::NotifyPassphraseFailed));
}

void SyncBackendHost::Core::OnPassphraseAccepted(
    const std::string& bootstrap_token) {
  host_->frontend_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &Core::NotifyPassphraseAccepted,
          bootstrap_token));
}

void SyncBackendHost::Core::OnStopSyncingPermanently() {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &Core::HandleStopSyncingPermanentlyOnFrontendLoop));
}

void SyncBackendHost::Core::OnUpdatedToken(const std::string& token) {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &Core::NotifyUpdatedToken, token));
}

void SyncBackendHost::Core::OnClearServerDataSucceeded() {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &Core::HandleClearServerDataSucceededOnFrontendLoop));
}

void SyncBackendHost::Core::OnClearServerDataFailed() {
  host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
      &Core::HandleClearServerDataFailedOnFrontendLoop));
}

void SyncBackendHost::Core::OnEncryptionComplete(
    const syncable::ModelTypeSet& encrypted_types) {
  host_->frontend_loop_->PostTask(
      FROM_HERE,
      NewRunnableMethod(this, &Core::NotifyEncryptionComplete,
                        encrypted_types));
}

void SyncBackendHost::Core::RouteJsEvent(
    const std::string& name, const JsArgList& args,
    const JsEventHandler* target) {
  host_->frontend_loop_->PostTask(
      FROM_HERE, NewRunnableMethod(
          this, &Core::RouteJsEventOnFrontendLoop, name, args, target));
}

void SyncBackendHost::Core::HandleStopSyncingPermanentlyOnFrontendLoop() {
  if (!host_ || !host_->frontend_)
    return;
  host_->frontend_->OnStopSyncingPermanently();
}

void SyncBackendHost::Core::HandleClearServerDataSucceededOnFrontendLoop() {
  if (!host_ || !host_->frontend_)
    return;
  host_->frontend_->OnClearServerDataSucceeded();
}

void SyncBackendHost::Core::HandleClearServerDataFailedOnFrontendLoop() {
  if (!host_ || !host_->frontend_)
    return;
  host_->frontend_->OnClearServerDataFailed();
}

void SyncBackendHost::Core::HandleAuthErrorEventOnFrontendLoop(
    const GoogleServiceAuthError& new_auth_error) {
  if (!host_ || !host_->frontend_)
    return;

  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  host_->last_auth_error_ = new_auth_error;
  host_->frontend_->OnAuthError();
}

void SyncBackendHost::Core::RouteJsEventOnFrontendLoop(
    const std::string& name, const JsArgList& args,
    const JsEventHandler* target) {
  if (!host_ || !parent_router_)
    return;

  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);

  parent_router_->RouteJsEvent(name, args, target);
}

void SyncBackendHost::Core::StartSavingChanges() {
  save_changes_timer_.Start(
      base::TimeDelta::FromSeconds(kSaveChangesIntervalSeconds),
      this, &Core::SaveChanges);
}

void SyncBackendHost::Core::DoRequestNudge(
    const tracked_objects::Location& nudge_location) {
  syncapi_->RequestNudge(nudge_location);
}

void SyncBackendHost::Core::DoRequestClearServerData() {
  syncapi_->RequestClearServerData();
}

void SyncBackendHost::Core::SaveChanges() {
  syncapi_->SaveChanges();
}

void SyncBackendHost::Core::DeleteSyncDataFolder() {
  if (file_util::DirectoryExists(host_->sync_data_folder_path())) {
    if (!file_util::Delete(host_->sync_data_folder_path(), true))
      LOG(DFATAL) << "Could not delete the Sync Data folder.";
  }
}

void SyncBackendHost::Core::SetParentJsEventRouter(JsEventRouter* router) {
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
  DCHECK(router);
  parent_router_ = router;
  MessageLoop* core_message_loop = host_->core_thread_.message_loop();
  CHECK(core_message_loop);
  core_message_loop->PostTask(
      FROM_HERE,
      NewRunnableMethod(this,
                        &SyncBackendHost::Core::ConnectChildJsEventRouter));
}

void SyncBackendHost::Core::RemoveParentJsEventRouter() {
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
  parent_router_ = NULL;
  MessageLoop* core_message_loop = host_->core_thread_.message_loop();
  CHECK(core_message_loop);
  core_message_loop->PostTask(
      FROM_HERE,
      NewRunnableMethod(this,
                        &SyncBackendHost::Core::DisconnectChildJsEventRouter));
}

const JsEventRouter* SyncBackendHost::Core::GetParentJsEventRouter() const {
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
  return parent_router_;
}

void SyncBackendHost::Core::ProcessMessage(
    const std::string& name, const JsArgList& args,
    const JsEventHandler* sender) {
  DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
  MessageLoop* core_message_loop = host_->core_thread_.message_loop();
  CHECK(core_message_loop);
  core_message_loop->PostTask(
      FROM_HERE,
      NewRunnableMethod(this,
                        &SyncBackendHost::Core::DoProcessMessage,
                        name, args, sender));
}

void SyncBackendHost::Core::ConnectChildJsEventRouter() {
  DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop());
  // We need this check since AddObserver() can be called at most once
  // for a given observer.
  if (!syncapi_->GetJsBackend()->GetParentJsEventRouter()) {
    syncapi_->GetJsBackend()->SetParentJsEventRouter(this);
    syncapi_->AddObserver(&sync_manager_observer_);
  }
}

void SyncBackendHost::Core::DisconnectChildJsEventRouter() {
  DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop());
  syncapi_->GetJsBackend()->RemoveParentJsEventRouter();
  syncapi_->RemoveObserver(&sync_manager_observer_);
}

void SyncBackendHost::Core::DoProcessMessage(
    const std::string& name, const JsArgList& args,
    const JsEventHandler* sender) {
  DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop());
  syncapi_->GetJsBackend()->ProcessMessage(name, args, sender);
}

void SyncBackendHost::Core::DeferNudgeForCleanup() {
  DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop());
  deferred_nudge_for_cleanup_requested_ = true;
}

}  // namespace browser_sync