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

#include <algorithm>

#include "base/string_number_conversions.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
#include "chrome/browser/sync/sessions/session_state.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"

using syncable::ModelTypeSet;

namespace browser_sync {

using sessions::SyncSessionSnapshot;

BackendMigrator::BackendMigrator(ProfileSyncService* service,
                                 DataTypeManager* manager)
    : state_(IDLE), service_(service), manager_(manager),
      restart_migration_(false),
      method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
  registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE,
                 Source<DataTypeManager>(manager_));
  service_->AddObserver(this);
}

BackendMigrator::~BackendMigrator() {
  service_->RemoveObserver(this);
}

bool BackendMigrator::HasStartedMigrating() const {
  return state_ >= DISABLING_TYPES;
}

void BackendMigrator::MigrateTypes(const syncable::ModelTypeSet& types) {
  {
    ModelTypeSet temp;
    std::set_union(to_migrate_.begin(), to_migrate_.end(),
                   types.begin(), types.end(),
                   std::inserter(temp, temp.end()));
    to_migrate_ = temp;
  }

  if (HasStartedMigrating()) {
    VLOG(1) << "BackendMigrator::MigrateTypes: STARTED_MIGRATING early-out.";
    restart_migration_ = true;
    return;
  }

  if (manager_->state() != DataTypeManager::CONFIGURED) {
    VLOG(1) << "BackendMigrator::MigrateTypes: manager CONFIGURED early-out.";
    state_ = WAITING_TO_START;
    return;
  }

  // We'll now disable any running types that need to be migrated.
  state_ = DISABLING_TYPES;
  ModelTypeSet full_set;
  service_->GetPreferredDataTypes(&full_set);
  ModelTypeSet difference;
  std::set_difference(full_set.begin(), full_set.end(),
                      to_migrate_.begin(), to_migrate_.end(),
                      std::inserter(difference, difference.end()));
  VLOG(1) << "BackendMigrator disabling types; calling Configure.";
  manager_->Configure(difference);
}

void BackendMigrator::OnStateChanged() {
  if (restart_migration_ == true) {
    VLOG(1) << "BackendMigrator restarting migration in OnStateChanged.";
    state_ = WAITING_TO_START;
    restart_migration_ = false;
    MigrateTypes(to_migrate_);
    return;
  }

  if (state_ != WAITING_FOR_PURGE)
    return;

  size_t num_empty_migrated_markers = 0;
  const SyncSessionSnapshot* snap = service_->GetLastSessionSnapshot();
  for (ModelTypeSet::const_iterator it = to_migrate_.begin();
       it != to_migrate_.end(); ++it) {
    if (snap->download_progress_markers[*it].empty())
      num_empty_migrated_markers++;
  }

  if (num_empty_migrated_markers < to_migrate_.size())
    return;

  state_ = REENABLING_TYPES;
  ModelTypeSet full_set;
  service_->GetPreferredDataTypes(&full_set);
  VLOG(1) << "BackendMigrator re-enabling types.";
  // Don't use |to_migrate_| for the re-enabling because the user may have
  // chosen to disable types during the migration.
  manager_->Configure(full_set);
}

void BackendMigrator::Observe(NotificationType type,
                              const NotificationSource& source,
                              const NotificationDetails& details) {
  DCHECK_EQ(NotificationType::SYNC_CONFIGURE_DONE, type.value);
  if (state_ == IDLE)
    return;

  DataTypeManager::ConfigureResultWithErrorLocation* result =
      Details<DataTypeManager::ConfigureResultWithErrorLocation>(
          details).ptr();

  ModelTypeSet intersection;
  std::set_intersection(result->requested_types.begin(),
      result->requested_types.end(), to_migrate_.begin(), to_migrate_.end(),
      std::inserter(intersection, intersection.end()));

  // The intersection check is to determine if our disable request was
  // interrupted by a user changing preferred types.  May still need to purge.
  // It's pretty wild if we're in WAITING_FOR_PURGE here, because it would mean
  // that after our disable-config finished but before the purge, another config
  // was posted externally _and completed_, which means somehow the nudge to
  // purge was dropped, yet nudges are reliable.
  if (state_ == WAITING_TO_START || state_ == WAITING_FOR_PURGE ||
      (state_ == DISABLING_TYPES && !intersection.empty())) {
    state_ = WAITING_TO_START;
    restart_migration_ = false;
    VLOG(1) << "BackendMigrator::Observe posting MigrateTypes.";
    if (!BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
        method_factory_.NewRunnableMethod(&BackendMigrator::MigrateTypes,
                                          to_migrate_))) {
      // Unittests need this.
      // TODO(tim): Clean this up.
      MigrateTypes(to_migrate_);
    }
    return;
  }

  if (result->result != DataTypeManager::OK) {
    // If this fails, and we're disabling types, a type may or may not be
    // disabled until the user restarts the browser.  If this wasn't an abort,
    // any failure will be reported as an unrecoverable error to the UI. If it
    // was an abort, then typically things are shutting down anyway. There isn't
    // much we can do in any case besides wait until a restart to try again.
    // The server will send down MIGRATION_DONE again for types needing
    // migration as the type will still be enabled on restart.
    LOG(WARNING) << "Unable to migrate, configuration failed!";
    state_ = IDLE;
    to_migrate_.clear();
    return;
  }

  if (state_ == DISABLING_TYPES) {
    state_ = WAITING_FOR_PURGE;
    VLOG(1) << "BackendMigrator waiting for purge.";
  } else if (state_ == REENABLING_TYPES) {
    // We're done!
    state_ = IDLE;

    std::stringstream ss;
    std::copy(to_migrate_.begin(), to_migrate_.end(),
              std::ostream_iterator<syncable::ModelType>(ss, ","));
    VLOG(1) << "BackendMigrator: Migration complete for: " << ss.str();
    to_migrate_.clear();
  }
}

BackendMigrator::State BackendMigrator::state() const {
  return state_;
}

};  // namespace browser_sync