// Copyright 2015 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 COMPONENTS_CRYPTAUTH_CRYPTAUTH_DEVICE_MANAGER_H_
#define COMPONENTS_CRYPTAUTH_CRYPTAUTH_DEVICE_MANAGER_H_

#include <memory>

#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/cryptauth/cryptauth_gcm_manager.h"
#include "components/cryptauth/proto/cryptauth_api.pb.h"
#include "components/cryptauth/sync_scheduler.h"

class PrefService;
class PrefRegistrySimple;

namespace cryptauth {

class CryptAuthClient;
class CryptAuthClientFactory;

// This class manages syncing and storing the user's phones that are registered
// with CryptAuth and are capable of unlocking the user's other devices. These
// phones are called "unlock keys".
// The manager periodically syncs the user's devices from CryptAuth to keep the
// list of unlock keys fresh. If a sync attempts fails, the manager will
// schedule the next sync more aggressively to recover.
class CryptAuthDeviceManager : public SyncScheduler::Delegate,
                               public CryptAuthGCMManager::Observer {
 public:
  // Respresents the success result of a sync attempt.
  enum class SyncResult { SUCCESS, FAILURE };

  // Represents whether the list of unlock keys has changed after a sync
  // attempt completes.
  enum class DeviceChangeResult {
    UNCHANGED,
    CHANGED,
  };

  class Observer {
   public:
    // Called when a sync attempt is started.
    virtual void OnSyncStarted() {}

    // Called when a sync attempt finishes with the |success| of the request.
    // |devices_changed| specifies if the sync caused the stored unlock keys to
    // change.
    virtual void OnSyncFinished(SyncResult sync_result,
                                DeviceChangeResult device_change_result) {}

    virtual ~Observer() {}
  };

  // Creates the manager:
  // |clock|: Used to determine the time between sync attempts.
  // |client_factory|: Creates CryptAuthClient instances to perform each sync.
  // |gcm_manager|: Notifies when GCM push messages trigger device syncs.
  //                Not owned and must outlive this instance.
  // |pref_service|: Stores syncing metadata and unlock key information to
  //                 persist across browser restarts. Must already be registered
  //                 with RegisterPrefs().
  CryptAuthDeviceManager(std::unique_ptr<base::Clock> clock,
                         std::unique_ptr<CryptAuthClientFactory> client_factory,
                         CryptAuthGCMManager* gcm_manager,
                         PrefService* pref_service);

  ~CryptAuthDeviceManager() override;

  // Registers the prefs used by this class to the given |registry|.
  static void RegisterPrefs(PrefRegistrySimple* registry);

  // Starts device manager to begin syncing devices.
  void Start();

  // Adds an observer.
  void AddObserver(Observer* observer);

  // Removes an observer.
  void RemoveObserver(Observer* observer);

  // Skips the waiting period and forces a sync immediately. If a
  // sync attempt is already in progress, this function does nothing.
  // |invocation_reason| specifies the reason that the sync was triggered,
  // which is upload to the server.
  void ForceSyncNow(InvocationReason invocation_reason);

  // Returns the timestamp of the last successful sync. If no sync
  // has ever been made, then returns a null base::Time object.
  base::Time GetLastSyncTime() const;

  // Returns the time to the next sync attempt.
  base::TimeDelta GetTimeToNextAttempt() const;

  // Returns true if a device sync attempt is currently in progress.
  bool IsSyncInProgress() const;

  // Returns true if the last device sync failed and the manager is now
  // scheduling sync attempts more aggressively to recover. If no enrollment
  // has ever been recorded, then this function will also return true.
  bool IsRecoveringFromFailure() const;

  // Returns a list of all remote devices that have been synced.
  virtual std::vector<ExternalDeviceInfo> GetSyncedDevices() const;

  // Returns a list of remote devices that can unlock the user's other devices.
  virtual std::vector<ExternalDeviceInfo> GetUnlockKeys() const;
  // Like GetUnlockKeys(), but only returns Pixel devices.
  virtual std::vector<ExternalDeviceInfo> GetPixelUnlockKeys() const;

  // Returns a list of remote devices that can host tether hotspots.
  virtual std::vector<ExternalDeviceInfo> GetTetherHosts() const;
  // Like GetTetherHosts(), but only returns Pixel devices.
  virtual std::vector<ExternalDeviceInfo> GetPixelTetherHosts() const;

 protected:
  // Empty constructor, to be used by tests to mock the device manager. Do not
  // use this constructor outside of tests.
  CryptAuthDeviceManager();

  void SetSyncSchedulerForTest(std::unique_ptr<SyncScheduler> sync_scheduler) {
    scheduler_ = std::move(sync_scheduler);
  }

 private:
  // CryptAuthGCMManager::Observer:
  void OnResyncMessage() override;

  // Updates |unlock_keys_| by fetching the list stored in |pref_service_|.
  void UpdateUnlockKeysFromPrefs();

  // SyncScheduler::Delegate:
  void OnSyncRequested(
      std::unique_ptr<SyncScheduler::SyncRequest> sync_request) override;

  // Callback when |cryptauth_client_| completes with the response.
  void OnGetMyDevicesSuccess(const GetMyDevicesResponse& response);
  void OnGetMyDevicesFailure(const std::string& error);

  // Used to determine the time.
  std::unique_ptr<base::Clock> clock_;

  // Creates CryptAuthClient instances for each sync attempt.
  std::unique_ptr<CryptAuthClientFactory> client_factory_;

  // Notifies when GCM push messages trigger device sync. Not owned and must
  // outlive this instance.
  CryptAuthGCMManager* gcm_manager_;

  // Contains preferences that outlive the lifetime of this object and across
  // process restarts. |pref_service_| must outlive the lifetime of this
  // instance.
  PrefService* const pref_service_;

  // All devices currently synced from CryptAuth.
  std::vector<ExternalDeviceInfo> synced_devices_;

  // Schedules the time between device sync attempts.
  std::unique_ptr<SyncScheduler> scheduler_;

  // Contains the SyncRequest that |scheduler_| requests when a device sync
  // attempt is made.
  std::unique_ptr<SyncScheduler::SyncRequest> sync_request_;

  // The CryptAuthEnroller instance for the current sync attempt. A new
  // instance will be created for each individual attempt.
  std::unique_ptr<CryptAuthClient> cryptauth_client_;

  // List of observers.
  base::ObserverList<Observer> observers_;

  base::WeakPtrFactory<CryptAuthDeviceManager> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(CryptAuthDeviceManager);
};

}  // namespace cryptauth

#endif  // COMPONENTS_CRYPTAUTH_CRYPTAUTH_DEVICE_MANAGER_H_
