// 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/sync/test_profile_sync_service.h"

#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/profile_sync_factory.h"
#include "chrome/browser/sync/sessions/session_state.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/test/sync/test_http_bridge_factory.h"

using browser_sync::ModelSafeRoutingInfo;
using browser_sync::sessions::ErrorCounters;
using browser_sync::sessions::SyncSourceInfo;
using browser_sync::sessions::SyncerStatus;
using browser_sync::sessions::SyncSessionSnapshot;
using syncable::DirectoryManager;
using syncable::ModelType;
using syncable::ScopedDirLookup;
using sync_api::UserShare;

namespace browser_sync {

using ::testing::_;
SyncBackendHostForProfileSyncTest::SyncBackendHostForProfileSyncTest(
    Profile* profile,
    bool set_initial_sync_ended_on_init,
    bool synchronous_init)
    : browser_sync::SyncBackendHost(profile),
      synchronous_init_(synchronous_init) {
  ON_CALL(*this, RequestNudge(_)).WillByDefault(
      testing::Invoke(this,
                      &SyncBackendHostForProfileSyncTest::
                      SimulateSyncCycleCompletedInitialSyncEnded));
  EXPECT_CALL(*this, RequestNudge(_)).Times(testing::AnyNumber());
}

SyncBackendHostForProfileSyncTest::~SyncBackendHostForProfileSyncTest() {}

void SyncBackendHostForProfileSyncTest::ConfigureDataTypes(
    const DataTypeController::TypeMap& data_type_controllers,
    const syncable::ModelTypeSet& types,
    CancelableTask* ready_task) {
  SetAutofillMigrationState(syncable::MIGRATED);
  SyncBackendHost::ConfigureDataTypes(
      data_type_controllers, types, ready_task);
}

void SyncBackendHostForProfileSyncTest::
    SimulateSyncCycleCompletedInitialSyncEnded(
    const tracked_objects::Location& location) {
  syncable::ModelTypeBitSet sync_ended;
  ModelSafeRoutingInfo enabled_types;
  GetModelSafeRoutingInfo(&enabled_types);
  std::string download_progress_markers[syncable::MODEL_TYPE_COUNT];
  for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin();
       i != enabled_types.end(); ++i) {
    sync_ended.set(i->first);
  }
  core_->HandleSyncCycleCompletedOnFrontendLoop(new SyncSessionSnapshot(
      SyncerStatus(), ErrorCounters(), 0, false,
      sync_ended, download_progress_markers, false, false, 0, 0, false,
      SyncSourceInfo()));
}

sync_api::HttpPostProviderFactory*
    SyncBackendHostForProfileSyncTest::MakeHttpBridgeFactory(
        net::URLRequestContextGetter* getter) {
  return new browser_sync::TestHttpBridgeFactory;
}

void SyncBackendHostForProfileSyncTest::InitCore(
    const Core::DoInitializeOptions& options) {
  std::wstring user = L"testuser@gmail.com";
  core_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(core_.get(),
                        &SyncBackendHost::Core::DoInitializeForTest,
                        user,
                        options.http_bridge_factory,
                        options.delete_sync_data_folder));

  // TODO(akalin): Figure out a better way to do this.
  if (synchronous_init_) {
    // The SyncBackend posts a task to the current loop when
    // initialization completes.
    MessageLoop::current()->Run();
  }
}

JsBackend* SyncBackendHostForProfileSyncTest::GetJsBackend() {
  // Return a non-NULL result only when the overridden function does.
  if (SyncBackendHost::GetJsBackend()) {
    return this;
  } else {
    NOTREACHED();
    return NULL;
  }
}

void SyncBackendHostForProfileSyncTest::SetParentJsEventRouter(
    JsEventRouter* router) {
  core_->SetParentJsEventRouter(router);
}

void SyncBackendHostForProfileSyncTest::RemoveParentJsEventRouter() {
  core_->RemoveParentJsEventRouter();
}

const JsEventRouter*
    SyncBackendHostForProfileSyncTest::GetParentJsEventRouter() const {
  return core_->GetParentJsEventRouter();
}

void SyncBackendHostForProfileSyncTest::ProcessMessage(
    const std::string& name, const JsArgList& args,
    const JsEventHandler* sender) {
  if (name.find("delay") != name.npos) {
    core_->RouteJsEvent(name, args, sender);
  } else {
    core_->RouteJsEventOnFrontendLoop(name, args, sender);
  }
}

void SyncBackendHostForProfileSyncTest::StartConfiguration(Callback0::Type*) {
  SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop();
}

void SyncBackendHostForProfileSyncTest::
    SetDefaultExpectationsForWorkerCreation(ProfileMock* profile) {
  EXPECT_CALL(*profile, GetPasswordStore(testing::_)).
      WillOnce(testing::Return((PasswordStore*)NULL));
}

void SyncBackendHostForProfileSyncTest::SetHistoryServiceExpectations(
    ProfileMock* profile) {
  EXPECT_CALL(*profile, GetHistoryService(testing::_)).
      WillOnce(testing::Return((HistoryService*)NULL));
}

}  // namespace browser_sync

browser_sync::TestIdFactory* TestProfileSyncService::id_factory() {
  return &id_factory_;
}

browser_sync::SyncBackendHostForProfileSyncTest*
    TestProfileSyncService::GetBackendForTest() {
  return static_cast<browser_sync::SyncBackendHostForProfileSyncTest*>(
      ProfileSyncService::GetBackendForTest());
}

TestProfileSyncService::TestProfileSyncService(
    ProfileSyncFactory* factory,
                       Profile* profile,
                       const std::string& test_user,
                       bool synchronous_backend_initialization,
                       Task* initial_condition_setup_task)
    : ProfileSyncService(factory, profile, test_user),
      synchronous_backend_initialization_(
          synchronous_backend_initialization),
      synchronous_sync_configuration_(false),
      initial_condition_setup_task_(initial_condition_setup_task),
      set_initial_sync_ended_on_init_(true) {
  RegisterPreferences();
  SetSyncSetupCompleted();
}

TestProfileSyncService::~TestProfileSyncService() {}

void TestProfileSyncService::SetInitialSyncEndedForEnabledTypes() {
  UserShare* user_share = GetUserShare();
  DirectoryManager* dir_manager = user_share->dir_manager.get();

  ScopedDirLookup dir(dir_manager, user_share->name);
  if (!dir.good())
    FAIL();

  ModelSafeRoutingInfo enabled_types;
  backend_->GetModelSafeRoutingInfo(&enabled_types);
  for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin();
       i != enabled_types.end(); ++i) {
    dir->set_initial_sync_ended_for_type(i->first, true);
  }
}

void TestProfileSyncService::OnBackendInitialized() {
  // Set this so below code can access GetUserShare().
  backend_initialized_ = true;

  // Set up any nodes the test wants around before model association.
  if (initial_condition_setup_task_) {
    initial_condition_setup_task_->Run();
    initial_condition_setup_task_ = NULL;
  }

  // Pretend we downloaded initial updates and set initial sync ended bits
  // if we were asked to.
  bool send_passphrase_required = false;
  if (set_initial_sync_ended_on_init_) {
    UserShare* user_share = GetUserShare();
    DirectoryManager* dir_manager = user_share->dir_manager.get();

    ScopedDirLookup dir(dir_manager, user_share->name);
    if (!dir.good())
      FAIL();

    if (!dir->initial_sync_ended_for_type(syncable::NIGORI)) {
      ProfileSyncServiceTestHelper::CreateRoot(
          syncable::NIGORI, GetUserShare(),
          id_factory());

      // A side effect of adding the NIGORI mode (normally done by the syncer)
      // is a decryption attempt, which will fail the first time.
      send_passphrase_required = true;
    }

    SetInitialSyncEndedForEnabledTypes();
  }

  ProfileSyncService::OnBackendInitialized();
  if (send_passphrase_required)
    OnPassphraseRequired(true);

  // TODO(akalin): Figure out a better way to do this.
  if (synchronous_backend_initialization_) {
    MessageLoop::current()->Quit();
  }
}

void TestProfileSyncService::Observe(NotificationType type,
                                     const NotificationSource& source,
                                     const NotificationDetails& details) {
  ProfileSyncService::Observe(type, source, details);
  if (type == NotificationType::SYNC_CONFIGURE_DONE &&
      !synchronous_sync_configuration_) {
    MessageLoop::current()->Quit();
  }
}

void TestProfileSyncService::dont_set_initial_sync_ended_on_init() {
  set_initial_sync_ended_on_init_ = false;
}
void TestProfileSyncService::set_synchronous_sync_configuration() {
  synchronous_sync_configuration_ = true;
}

void TestProfileSyncService::CreateBackend() {
  backend_.reset(new browser_sync::SyncBackendHostForProfileSyncTest(
      profile(),
      set_initial_sync_ended_on_init_,
      synchronous_backend_initialization_));
}

std::string TestProfileSyncService::GetLsidForAuthBootstraping() {
  return "foo";
}