// Copyright (c) 2012 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.

#ifndef CHROME_BROWSER_SYNC_PROFILE_SYNC_SERVICE_HARNESS_H_
#define CHROME_BROWSER_SYNC_PROFILE_SYNC_SERVICE_HARNESS_H_

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "chrome/browser/sync/backend_migrator.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_observer.h"
#include "chrome/browser/sync/retry_verifier.h"
#include "sync/internal_api/public/base/model_type.h"

class Profile;

namespace invalidation {
class P2PInvalidationService;
}

namespace browser_sync {
namespace sessions {
class SyncSessionSnapshot;
}
}

// An instance of this class is basically our notion of a "sync client" for
// automation purposes. It harnesses the ProfileSyncService member of the
// profile passed to it on construction and automates certain things like setup
// and authentication. It provides ways to "wait" adequate periods of time for
// several clients to get to the same state.
class ProfileSyncServiceHarness
    : public ProfileSyncServiceObserver,
      public browser_sync::MigrationObserver {
 public:
  static ProfileSyncServiceHarness* Create(
      Profile* profile,
      const std::string& username,
      const std::string& password);

  static ProfileSyncServiceHarness* CreateForIntegrationTest(
      Profile* profile,
      const std::string& username,
      const std::string& password,
      invalidation::P2PInvalidationService* invalidation_service);

  virtual ~ProfileSyncServiceHarness();

  // Sets the GAIA credentials with which to sign in to sync.
  void SetCredentials(const std::string& username, const std::string& password);

  // Returns true if sync has been enabled on |profile_|.
  bool IsSyncAlreadySetup();

  // Creates a ProfileSyncService for the profile passed at construction and
  // enables sync for all available datatypes. Returns true only after sync has
  // been fully initialized and authenticated, and we are ready to process
  // changes.
  bool SetupSync();

  // Same as the above method, but enables sync only for the datatypes contained
  // in |synced_datatypes|.
  bool SetupSync(syncer::ModelTypeSet synced_datatypes);

  // ProfileSyncServiceObserver implementation.
  virtual void OnStateChanged() OVERRIDE;
  virtual void OnSyncCycleCompleted() OVERRIDE;

  // MigrationObserver implementation.
  virtual void OnMigrationStateChange() OVERRIDE;

  // Blocks the caller until the sync backend host associated with this harness
  // has been initialized.  Returns true if the wait was successful.
  bool AwaitBackendInitialized();

  // Blocks the caller until this harness has completed a single sync cycle
  // since the previous one.  Returns true if a sync cycle has completed.
  bool AwaitDataSyncCompletion(const std::string& reason);

  // Blocks the caller until this harness has completed as many sync cycles as
  // are required to ensure its progress marker matches the latest available on
  // the server.
  //
  // Note: When other clients are committing changes this will not be reliable.
  // If your test involves changes to multiple clients, you should use one of
  // the other Await* functions, such as AwaitMutualSyncCycleComplete.  Refer to
  // the documentation of those functions for more details.
  bool AwaitFullSyncCompletion(const std::string& reason);

  // Blocks the caller until sync has been disabled for this client. Returns
  // true if sync is disabled.
  bool AwaitSyncDisabled(const std::string& reason);

  // Blocks the caller until exponential backoff has been verified to happen.
  bool AwaitExponentialBackoffVerification();

  // Blocks the caller until the syncer receives an actionable error.
  // Returns true if the sync client received an actionable error.
  bool AwaitActionableError();

  // Blocks until the given set of data types are migrated.
  bool AwaitMigration(syncer::ModelTypeSet expected_migrated_types);

  // Blocks the caller until this harness has observed that the sync engine
  // has downloaded all the changes seen by the |partner| harness's client.
  bool WaitUntilProgressMarkersMatch(
      ProfileSyncServiceHarness* partner, const std::string& reason);

  // Calling this acts as a barrier and blocks the caller until |this| and
  // |partner| have both completed a sync cycle.  When calling this method,
  // the |partner| should be the passive responder who responds to the actions
  // of |this|.  This method relies upon the synchronization of callbacks
  // from the message queue. Returns true if two sync cycles have completed.
  // Note: Use this method when exactly one client makes local change(s), and
  // exactly one client is waiting to receive those changes.
  bool AwaitMutualSyncCycleCompletion(ProfileSyncServiceHarness* partner);

  // Blocks the caller until |this| completes its ongoing sync cycle and every
  // other client in |partners| have achieved identical download progresses.
  // Note: Use this method when exactly one client makes local change(s),
  // and more than one client is waiting to receive those changes.
  bool AwaitGroupSyncCycleCompletion(
      std::vector<ProfileSyncServiceHarness*>& partners);

  // Blocks the caller until every client in |clients| completes its ongoing
  // sync cycle and all the clients' progress markers match.  Note: Use this
  // method when more than one client makes local change(s), and more than one
  // client is waiting to receive those changes.
  static bool AwaitQuiescence(
      std::vector<ProfileSyncServiceHarness*>& clients);

  // Blocks the caller until |service_| indicates that a passphrase is required.
  bool AwaitPassphraseRequired();

  // Blocks the caller until |service_| indicates that the passphrase set by
  // calling SetDecryptionPassphrase has been accepted.
  bool AwaitPassphraseAccepted();

  // Returns the ProfileSyncService member of the sync client.
  ProfileSyncService* service() { return service_; }

  // Returns the status of the ProfileSyncService member of the sync client.
  ProfileSyncService::Status GetStatus();

  // See ProfileSyncService::ShouldPushChanges().
  bool ServiceIsPushingChanges() { return service_->ShouldPushChanges(); }

  // Enables sync for a particular sync datatype. Returns true on success.
  bool EnableSyncForDatatype(syncer::ModelType datatype);

  // Disables sync for a particular sync datatype. Returns true on success.
  bool DisableSyncForDatatype(syncer::ModelType datatype);

  // Enables sync for all sync datatypes. Returns true on success.
  bool EnableSyncForAllDatatypes();

  // Disables sync for all sync datatypes. Returns true on success.
  bool DisableSyncForAllDatatypes();

  // Returns a snapshot of the current sync session.
  syncer::sessions::SyncSessionSnapshot GetLastSessionSnapshot() const;

  // Encrypt the datatype |type|. This method will block while the sync backend
  // host performs the encryption or a timeout is reached.
  // PostCondition:
  //   returns: True if |type| was encrypted and we are fully synced.
  //            False if we timed out.
  bool EnableEncryptionForType(syncer::ModelType type);

  // Wait until |type| is encrypted or we time out.
  // PostCondition:
  //   returns: True if |type| is currently encrypted and we are fully synced.
  //            False if we timed out.
  bool WaitForTypeEncryption(syncer::ModelType type);

  // Check if |type| is encrypted.
  bool IsTypeEncrypted(syncer::ModelType type);

  // Check if |type| is registered and the controller is running.
  bool IsTypeRunning(syncer::ModelType type);

  // Check if |type| is being synced.
  bool IsTypePreferred(syncer::ModelType type);

  // Get the number of sync entries this client has. This includes all top
  // level or permanent items, and can include recently deleted entries.
  size_t GetNumEntries() const;

  // Get the number of sync datatypes registered (ignoring whatever state
  // they're in).
  size_t GetNumDatatypes() const;

  // Gets the |auto_start_enabled_| variable from the |service_|.
  bool AutoStartEnabled();

  // Returns true if a status change took place, false on timeout.
  bool AwaitStatusChangeWithTimeout(int timeout_milliseconds,
                                    const std::string& reason);

 private:
  friend class StateChangeTimeoutEvent;

  enum WaitState {
    // The sync client has just been initialized.
    INITIAL_WAIT_STATE = 0,

    // The sync client awaits the OnBackendInitialized() callback.
    WAITING_FOR_ON_BACKEND_INITIALIZED,

    // The sync client is waiting for the first sync cycle to complete.
    WAITING_FOR_INITIAL_SYNC,

    // The sync client is waiting for data to be synced.
    WAITING_FOR_DATA_SYNC,

    // The sync client is waiting for data and progress markers to be synced.
    WAITING_FOR_FULL_SYNC,

    // The sync client anticipates incoming updates leading to a new sync cycle.
    WAITING_FOR_UPDATES,

    // The sync client is waiting for a passphrase to be required by the
    // cryptographer.
    WAITING_FOR_PASSPHRASE_REQUIRED,

    // The sync client is waiting for its passphrase to be accepted by the
    // cryptographer.
    WAITING_FOR_PASSPHRASE_ACCEPTED,

    // The sync client anticipates encryption of new datatypes.
    WAITING_FOR_ENCRYPTION,

    // The sync client is waiting for the datatype manager to be configured and
    // for sync to be fully initialized. Used after a browser restart, where a
    // full sync cycle is not expected to occur.
    WAITING_FOR_SYNC_CONFIGURATION,

    // The sync client is waiting for sync to be disabled for this client.
    WAITING_FOR_SYNC_DISABLED,

    // The sync client is in the exponential backoff mode. Verify that
    // backoffs are triggered correctly.
    WAITING_FOR_EXPONENTIAL_BACKOFF_VERIFICATION,

    // The sync client is waiting for migration to start.
    WAITING_FOR_MIGRATION_TO_START,

    // The sync client is waiting for migration to finish.
    WAITING_FOR_MIGRATION_TO_FINISH,

    // The sync client is waiting for an actionable error from the server.
    WAITING_FOR_ACTIONABLE_ERROR,

    // The client verification is complete. We don't care about the state of
    // the syncer any more.
    WAITING_FOR_NOTHING,

    // The sync client needs a passphrase in order to decrypt data.
    SET_PASSPHRASE_FAILED,

    // The sync client's credentials were rejected.
    CREDENTIALS_REJECTED,

    // The sync client cannot reach the server.
    SERVER_UNREACHABLE,

    // The sync client is fully synced and there are no pending updates.
    FULLY_SYNCED,

    // Syncing is disabled for the client.
    SYNC_DISABLED,

    NUMBER_OF_STATES,
  };

  ProfileSyncServiceHarness(
      Profile* profile,
      const std::string& username,
      const std::string& password,
      invalidation::P2PInvalidationService* invalidation_service);

  // Listen to migration events if the migrator has been initialized
  // and we're not already listening.  Returns true if we started
  // listening.
  bool TryListeningToMigrationEvents();

  // Called from the observer when the current wait state has been completed.
  void SignalStateCompleteWithNextState(WaitState next_state);

  // Indicates that the operation being waited on is complete.
  void SignalStateComplete();

  // Finite state machine for controlling state.  Returns true only if a state
  // change has taken place.
  bool RunStateChangeMachine();

  // A helper for implementing IsDataSynced() and IsFullySynced().
  bool IsDataSyncedImpl(
      const syncer::sessions::SyncSessionSnapshot& snapshot);

  // Returns true if the sync client has no unsynced items.
  bool IsDataSynced();

  // Returns true if the sync client has no unsynced items and its progress
  // markers are believed to be up to date.
  //
  // Although we can't detect when commits from other clients invalidate our
  // local progress markers, we do know when our own commits have invalidated
  // our timestmaps.  This check returns true when this client has, to the best
  // of its knowledge, downloaded the latest progress markers.
  bool IsFullySynced();

  // Returns true if there is a backend migration in progress.
  bool HasPendingBackendMigration();

  // Returns true if this client has downloaded all the items that the
  // other client has.
  bool MatchesOtherClient(ProfileSyncServiceHarness* partner);

  // Returns a string with relevant info about client's sync state (if
  // available), annotated with |message|. Useful for logging.
  std::string GetClientInfoString(const std::string& message);

  // Gets the current progress marker of the current sync session for a
  // particular datatype. Returns an empty string if the progress marker isn't
  // found.
  std::string GetSerializedProgressMarker(syncer::ModelType model_type) const;

  // Gets detailed status from |service_| in pretty-printable form.
  std::string GetServiceStatus();

  // When in WAITING_FOR_ENCRYPTION state, we check to see if this type is now
  // encrypted to determine if we're done.
  syncer::ModelType waiting_for_encryption_type_;

  // The WaitState in which the sync client currently is. Helps determine what
  // action to take when RunStateChangeMachine() is called.
  WaitState wait_state_;

  // Sync profile associated with this sync client.
  Profile* profile_;

  // ProfileSyncService object associated with |profile_|.
  ProfileSyncService* service_;

  // P2PInvalidationService associated with |profile_|.
  invalidation::P2PInvalidationService* p2p_invalidation_service_;

  // The harness of the client whose update progress marker we're expecting
  // eventually match.
  ProfileSyncServiceHarness* progress_marker_partner_;

  // Credentials used for GAIA authentication.
  std::string username_;
  std::string password_;

  // The current set of data types pending migration.  Used by
  // AwaitMigration().
  syncer::ModelTypeSet pending_migration_types_;

  // The set of data types that have undergone migration.  Used by
  // AwaitMigration().
  syncer::ModelTypeSet migrated_types_;

  // Used for logging.
  const std::string profile_debug_name_;

  // Keeps track of the number of attempts at exponential backoff and its
  // related bookkeeping information for verification.
  browser_sync::RetryVerifier retry_verifier_;

  // Flag set to true when we're waiting for a status change to happen. Used to
  // avoid triggering internal state machine logic on unexpected sync observer
  // callbacks.
  bool waiting_for_status_change_;

  DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceHarness);
};

#endif  // CHROME_BROWSER_SYNC_PROFILE_SYNC_SERVICE_HARNESS_H_