// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_GCM_DRIVER_GCM_CLIENT_IMPL_H_
#define COMPONENTS_GCM_DRIVER_GCM_CLIENT_IMPL_H_

#include <stdint.h>

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/gcm_driver/gcm_client.h"
#include "components/gcm_driver/gcm_stats_recorder_impl.h"
#include "google_apis/gcm/base/mcs_message.h"
#include "google_apis/gcm/engine/gcm_store.h"
#include "google_apis/gcm/engine/gservices_settings.h"
#include "google_apis/gcm/engine/mcs_client.h"
#include "google_apis/gcm/engine/registration_request.h"
#include "google_apis/gcm/engine/unregistration_request.h"
#include "google_apis/gcm/protocol/android_checkin.pb.h"
#include "google_apis/gcm/protocol/checkin.pb.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/http/http_status_code.h"
#include "services/network/public/mojom/proxy_resolving_socket.mojom.h"

class GURL;

namespace base {
class Clock;
class Time;
}  // namespace base

namespace mcs_proto {
class DataMessageStanza;
}  // namespace mcs_proto

namespace network {
class NetworkConnectionTracker;
class SharedURLLoaderFactory;
}  // namespace network

namespace gcm {

class CheckinRequest;
class ConnectionFactory;
class GCMClientImplTest;

// Helper class for building GCM internals. Allows tests to inject fake versions
// as necessary.
class GCMInternalsBuilder {
 public:
  GCMInternalsBuilder();
  virtual ~GCMInternalsBuilder();

  virtual base::Clock* GetClock();
  virtual std::unique_ptr<MCSClient> BuildMCSClient(
      const std::string& version,
      base::Clock* clock,
      ConnectionFactory* connection_factory,
      GCMStore* gcm_store,
      scoped_refptr<base::SequencedTaskRunner> io_task_runner,
      GCMStatsRecorder* recorder);
  virtual std::unique_ptr<ConnectionFactory> BuildConnectionFactory(
      const std::vector<GURL>& endpoints,
      const net::BackoffEntry::Policy& backoff_policy,
      base::RepeatingCallback<void(
          mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>)>
          get_socket_factory_callback,
      scoped_refptr<base::SequencedTaskRunner> io_task_runner,
      GCMStatsRecorder* recorder,
      network::NetworkConnectionTracker* network_connection_tracker);
};

// Implements the GCM Client. It is used to coordinate MCS Client (communication
// with MCS) and other pieces of GCM infrastructure like Registration and
// Checkins. It also allows for registering user delegates that host
// applications that send and receive messages.
class GCMClientImpl
    : public GCMClient, public GCMStatsRecorder::Delegate,
      public ConnectionFactory::ConnectionListener {
 public:
  // State representation of the GCMClient.
  // Any change made to this enum should have corresponding change in the
  // GetStateString(...) function.
  enum State {
    // Uninitialized.
    UNINITIALIZED,
    // Initialized,
    INITIALIZED,
    // GCM store loading is in progress.
    LOADING,
    // GCM store is loaded.
    LOADED,
    // Initial device checkin is in progress.
    INITIAL_DEVICE_CHECKIN,
    // Ready to accept requests.
    READY,
  };

  explicit GCMClientImpl(
      std::unique_ptr<GCMInternalsBuilder> internals_builder);

  GCMClientImpl(const GCMClientImpl&) = delete;
  GCMClientImpl& operator=(const GCMClientImpl&) = delete;

  ~GCMClientImpl() override;

  // GCMClient implementation.
  void Initialize(
      const ChromeBuildInfo& chrome_build_info,
      const base::FilePath& store_path,
      const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
      scoped_refptr<base::SequencedTaskRunner> io_task_runner,
      base::RepeatingCallback<void(
          mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>)>
          get_socket_factory_callback,
      const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
      network::NetworkConnectionTracker* network_connection_tracker,
      std::unique_ptr<Encryptor> encryptor,
      GCMClient::Delegate* delegate) override;
  void Start(StartMode start_mode) override;
  void Stop() override;
  void Register(scoped_refptr<RegistrationInfo> registration_info) override;
  bool ValidateRegistration(scoped_refptr<RegistrationInfo> registration_info,
                            const std::string& registration_id) override;
  void Unregister(scoped_refptr<RegistrationInfo> registration_info) override;
  void Send(const std::string& app_id,
            const std::string& receiver_id,
            const OutgoingMessage& message) override;
  void RecordDecryptionFailure(const std::string& app_id,
                               GCMDecryptionResult result) override;
  void SetRecording(bool recording) override;
  void ClearActivityLogs() override;
  GCMStatistics GetStatistics() const override;
  void SetAccountTokens(
      const std::vector<AccountTokenInfo>& account_tokens) override;
  void UpdateAccountMapping(const AccountMapping& account_mapping) override;
  void RemoveAccountMapping(const CoreAccountId& account_id) override;
  void SetLastTokenFetchTime(const base::Time& time) override;
  void UpdateHeartbeatTimer(
      std::unique_ptr<base::RetainingOneShotTimer> timer) override;
  void AddInstanceIDData(const std::string& app_id,
                         const std::string& instance_id,
                         const std::string& extra_data) override;
  void RemoveInstanceIDData(const std::string& app_id) override;
  void GetInstanceIDData(const std::string& app_id,
                         std::string* instance_id,
                         std::string* extra_data) override;
  void AddHeartbeatInterval(const std::string& scope, int interval_ms) override;
  void RemoveHeartbeatInterval(const std::string& scope) override;

  // GCMStatsRecorder::Delegate implemenation.
  void OnActivityRecorded() override;

  // ConnectionFactory::ConnectionListener implementation.
  void OnConnected(const GURL& current_server,
                   const net::IPEndPoint& ip_endpoint) override;
  void OnDisconnected() override;

 private:
  // The check-in info for the device.
  // TODO(fgorski): Convert to a class with explicit getters/setters.
  struct CheckinInfo {
    CheckinInfo();
    ~CheckinInfo();
    bool IsValid() const { return android_id != 0 && secret != 0; }
    void Reset();

    // Android ID of the device as assigned by the server.
    uint64_t android_id;
    // Security token of the device as assigned by the server.
    uint64_t secret;
    // True if accounts were already provided through SetAccountsForCheckin(),
    // or when |last_checkin_accounts| was loaded as empty.
    bool accounts_set;
  };

  // Reasons for resetting the GCM Store.
  // Note: this enum is recorded into a histogram. Do not change enum value
  // or order.
  enum ResetReason {
    LOAD_FAILURE,      // GCM store failed to load, but the store exists.
    CHECKIN_REJECTED,  // Checkin was rejected by server.

    RESET_REASON_COUNT,
  };

  // Collection of pending registration requests. Keys are RegistrationInfo
  // instance, while values are pending registration requests to obtain a
  // registration ID for requesting application.
  using PendingRegistrationRequests =
      std::map<scoped_refptr<RegistrationInfo>,
               std::unique_ptr<RegistrationRequest>,
               RegistrationInfoComparer>;

  // Collection of pending unregistration requests. Keys are RegistrationInfo
  // instance, while values are pending unregistration requests to disable the
  // registration ID currently assigned to the application.
  using PendingUnregistrationRequests =
      std::map<scoped_refptr<RegistrationInfo>,
               std::unique_ptr<UnregistrationRequest>,
               RegistrationInfoComparer>;

  friend class GCMClientImplTest;
  friend class GCMClientInstanceIDTest;

  // Returns text representation of the enum State.
  std::string GetStateString() const;

  // Callbacks for the MCSClient.
  // Receives messages and dispatches them to relevant user delegates.
  void OnMessageReceivedFromMCS(const gcm::MCSMessage& message);
  // Receives confirmation of sent messages or information about errors.
  void OnMessageSentToMCS(int64_t user_serial_number,
                          const std::string& app_id,
                          const std::string& message_id,
                          MCSClient::MessageSendStatus status);
  // Receives information about mcs_client_ errors.
  void OnMCSError();

  // Runs after GCM Store load is done to trigger continuation of the
  // initialization.
  void OnLoadCompleted(std::unique_ptr<GCMStore::LoadResult> result);
  // Starts the GCM.
  void StartGCM();
  // Initializes mcs_client_, which handles the connection to MCS.
  void InitializeMCSClient();
  // Complets the first time device checkin.
  void OnFirstTimeDeviceCheckinCompleted(const CheckinInfo& checkin_info);
  // Starts a login on mcs_client_.
  void StartMCSLogin();
  // Resets the GCM store when it is corrupted.
  void ResetStore();
  // Sets state to ready. This will initiate the MCS login and notify the
  // delegates.
  void OnReady(const std::vector<AccountMapping>& account_mappings,
               const base::Time& last_token_fetch_time);

  // Starts a first time device checkin.
  void StartCheckin();
  // Completes the device checkin request by parsing the |checkin_response|.
  // Function also cleans up the pending checkin.
  void OnCheckinCompleted(
      net::HttpStatusCode response_code,
      const checkin_proto::AndroidCheckinResponse& checkin_response);

  // Callback passed to GCMStore::SetGServicesSettings.
  void SetGServicesSettingsCallback(bool success);

  // Schedules next periodic device checkin and makes sure there is at most one
  // pending checkin at a time. This function is meant to be called after a
  // successful checkin.
  void SchedulePeriodicCheckin();
  // Gets the time until next checkin.
  base::TimeDelta GetTimeToNextCheckin() const;
  // Callback for setting last checkin information in the |gcm_store_|.
  void SetLastCheckinInfoCallback(bool success);

  // Callback for persisting device credentials in the |gcm_store_|.
  void SetDeviceCredentialsCallback(bool success);

  // Callback for persisting registration info in the |gcm_store_|.
  void UpdateRegistrationCallback(bool success);

  // Callback for all store operations that do not try to recover, if write in
  // |gcm_store_| fails.
  void DefaultStoreCallback(bool success);

  // Callback for store operation where result does not matter.
  void IgnoreWriteResultCallback(const std::string& operation_suffix_for_uma,
                                 bool success);

  // Callback for destroying the GCM store.
  void DestroyStoreCallback(bool success);

  // Callback for resetting the GCM store. The store will be reloaded.
  void ResetStoreCallback(bool success);

  // Completes the registration request.
  void OnRegisterCompleted(scoped_refptr<RegistrationInfo> registration_info,
                           RegistrationRequest::Status status,
                           const std::string& registration_id);

  // Completes the unregistration request.
  void OnUnregisterCompleted(scoped_refptr<RegistrationInfo> registration_info,
                             UnregistrationRequest::Status status);

  // Completes the GCM store destroy request.
  void OnGCMStoreDestroyed(bool success);

  // Handles incoming data message and dispatches it the delegate of this class.
  void HandleIncomingMessage(const gcm::MCSMessage& message);

  // Fires OnMessageReceived event on the delegate of this class, based on the
  // details in |data_message_stanza| and |message_data|.
  void HandleIncomingDataMessage(
      const std::string& app_id,
      bool was_subtype,
      const mcs_proto::DataMessageStanza& data_message_stanza,
      MessageData& message_data);

  // Fires OnMessagesDeleted event on the delegate of this class, based on the
  // details in |data_message_stanza| and |message_data|.
  void HandleIncomingDeletedMessages(
      const std::string& app_id,
      const mcs_proto::DataMessageStanza& data_message_stanza,
      MessageData& message_data);

  // Fires OnMessageSendError event on the delegate of this class, based on the
  // details in |data_message_stanza| and |message_data|.
  void HandleIncomingSendError(
      const std::string& app_id,
      const mcs_proto::DataMessageStanza& data_message_stanza,
      MessageData& message_data);

  // Is there any standalone app being registered for GCM?
  bool HasStandaloneRegisteredApp() const;

  // Destroys the store when it is not needed.
  void DestroyStoreWhenNotNeeded();

  // Reset all cahced values.
  void ResetCache();

  // Builder for the GCM internals (mcs client, etc.).
  std::unique_ptr<GCMInternalsBuilder> internals_builder_;

  // Recorder that logs GCM activities.
  GCMStatsRecorderImpl recorder_;

  // State of the GCM Client Implementation.
  State state_;

  raw_ptr<GCMClient::Delegate> delegate_;

  // Flag to indicate if the GCM should be delay started until it is actually
  // used in either of the following cases:
  // 1) The GCM store contains the registration records.
  // 2) GCM functionailities are explicitly called.
  StartMode start_mode_;

  // Device checkin info (android ID and security token used by device).
  CheckinInfo device_checkin_info_;

  // Clock used for timing of retry logic. Passed in for testing.
  raw_ptr<base::Clock> clock_;

  // Information about the chrome build.
  // TODO(fgorski): Check if it can be passed in constructor and made const.
  ChromeBuildInfo chrome_build_info_;

  // Persistent data store for keeping device credentials, messages and user to
  // serial number mappings.
  std::unique_ptr<GCMStore> gcm_store_;

  // Data loaded from the GCM store.
  std::unique_ptr<GCMStore::LoadResult> load_result_;

  // Tracks if the GCM store has been reset. This is used to prevent from
  // resetting and loading from the store again and again.
  bool gcm_store_reset_;

  std::unique_ptr<ConnectionFactory> connection_factory_;
  base::RepeatingCallback<void(
      mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>)>
      get_socket_factory_callback_;

  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  raw_ptr<network::NetworkConnectionTracker> network_connection_tracker_;

  scoped_refptr<base::SequencedTaskRunner> io_task_runner_;

  // Controls receiving and sending of packets and reliable message queueing.
  // Must be destroyed before |network_session_|.
  std::unique_ptr<MCSClient> mcs_client_;

  std::unique_ptr<CheckinRequest> checkin_request_;

  // Cached registration info.
  RegistrationInfoMap registrations_;

  // Currently pending registration requests. GCMClientImpl owns the
  // RegistrationRequests.
  PendingRegistrationRequests pending_registration_requests_;

  // Currently pending unregistration requests. GCMClientImpl owns the
  // UnregistrationRequests.
  PendingUnregistrationRequests pending_unregistration_requests_;

  // G-services settings that were provided by MCS.
  GServicesSettings gservices_settings_;

  // Time of the last successful checkin.
  base::Time last_checkin_time_;

  // Cached instance ID data, key is app ID and value is pair of instance ID
  // and extra data.
  std::map<std::string, std::pair<std::string, std::string>> instance_id_data_;

  // Factory for creating references when scheduling periodic checkin.
  base::WeakPtrFactory<GCMClientImpl> periodic_checkin_ptr_factory_{this};

  // Factory for wiping out GCM store.
  base::WeakPtrFactory<GCMClientImpl> destroying_gcm_store_ptr_factory_{this};

  // Factory for creating references in callbacks.
  base::WeakPtrFactory<GCMClientImpl> weak_ptr_factory_{this};
};

}  // namespace gcm

#endif  // COMPONENTS_GCM_DRIVER_GCM_CLIENT_IMPL_H_
