// Copyright 2013 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_SYNC_ENGINE_CYCLE_NUDGE_TRACKER_H_
#define COMPONENTS_SYNC_ENGINE_CYCLE_NUDGE_TRACKER_H_

#include <stddef.h>

#include <memory>
#include <optional>

#include "base/compiler_specific.h"
#include "base/time/time.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/sync_invalidation.h"
#include "components/sync/engine/cycle/data_type_tracker.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"

namespace sync_pb {
class GetUpdateTriggers;
enum SyncEnums_GetUpdatesOrigin : int;
}  // namespace sync_pb

namespace syncer {

// A class to track the outstanding work required to bring the client back into
// sync with the server.
class NudgeTracker {
 public:
  NudgeTracker();

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

  ~NudgeTracker();

  // Returns true if there is a good reason for performing a sync cycle.
  // This does not take into account whether or not this is a good *time* to
  // perform a sync cycle; that's the scheduler's job.
  bool IsSyncRequired(DataTypeSet types) const;

  // Returns true if there is a good reason for performing a get updates
  // request as part of the next sync cycle.
  bool IsGetUpdatesRequired(DataTypeSet types) const;

  // Tells this class that a commit message has been sent (note that each sync
  // cycle may include an arbitrary number of commit messages).
  void RecordSuccessfulCommitMessage(DataTypeSet types);

  // Tells this class that all required update fetching or committing has
  // completed successfully, as the result of a "normal" sync cycle.
  // Any blocked data types will ignore this, but non-blocked types and the
  // overall state will still get updated.
  void RecordSuccessfulSyncCycleIfNotBlocked(DataTypeSet types);

  // Tells this class that the initial sync has happened for the given `types`,
  // generally due to a "configuration" cycle.
  void RecordInitialSyncDone(DataTypeSet types);

  // Takes note of a local change.
  // Returns the current nudge delay for local changes to `type`.
  base::TimeDelta RecordLocalChange(DataType type, bool is_single_client);

  // Takes note of a locally issued request to refresh a data type.
  // Returns the nudge delay for a local refresh.
  base::TimeDelta RecordLocalRefreshRequest(DataTypeSet types);

  // Takes note of the receipt of an invalidation notice from the server.
  // Returns the nudge delay for a remote invalidation.
  base::TimeDelta GetRemoteInvalidationDelay(DataType type) const;

  // Take note that an initial sync is pending for this type.
  void RecordInitialSyncRequired(DataType type);

  // Takes note that the conflict happended for this type, need to sync to
  // resolve conflict locally.
  void RecordCommitConflict(DataType type);

  // These functions should be called to keep this class informed of the status
  // of the connection to the invalidations server.
  void OnInvalidationsEnabled();
  void OnInvalidationsDisabled();

  // Marks `types` as being throttled from `now` until `now` + `length`.
  void SetTypesThrottledUntil(DataTypeSet types,
                              base::TimeDelta length,
                              base::TimeTicks now);

  // Marks `type` as being backed off from `now` until `now` + `length`.
  void SetTypeBackedOff(DataType type,
                        base::TimeDelta length,
                        base::TimeTicks now);

  // Removes any throttling and backoff that have expired.
  void UpdateTypeThrottlingAndBackoffState();

  void SetHasPendingInvalidations(DataType type,
                                  bool has_pending_invalidations);

  // Returns the time of the next type unthrottling or unbackoff.
  base::TimeDelta GetTimeUntilNextUnblock() const;

  // Returns the time of for type last backing off interval.
  base::TimeDelta GetTypeLastBackoffInterval(DataType type) const;

  // Returns true if any type is currenlty throttled or backed off.
  bool IsAnyTypeBlocked() const;

  // Returns true if `type` is currently blocked.
  bool IsTypeBlocked(DataType type) const;

  // Returns `type`'s blocking mode.
  WaitInterval::BlockingMode GetTypeBlockingMode(DataType type) const;

  // Returns the set of currently throttled or backed off types.
  DataTypeSet GetBlockedTypes() const;

  // Returns the set of types with local changes pending.
  DataTypeSet GetNudgedTypes() const;

  // Returns the set of types that have pending invalidations.
  DataTypeSet GetNotifiedTypes() const;

  // Returns the set of types that have pending refresh requests.
  DataTypeSet GetRefreshRequestedTypes() const;

  // Returns the 'origin' of the GetUpdate request.
  sync_pb::SyncEnums_GetUpdatesOrigin GetOrigin() const;

  // Fills a GetUpdatesTrigger message for the next GetUpdates request.  This is
  // used by the DownloadUpdatesCommand to dump lots of useful per-type state
  // information into the GetUpdate request before sending it off to the server.
  void FillProtoMessage(DataType type, sync_pb::GetUpdateTriggers* msg) const;

  // Update the per-datatype local change nudge delay. No update happens
  // if `delay` is too small (less than the smallest default delay).
  void UpdateLocalChangeDelay(DataType type, const base::TimeDelta& delay);

  // UpdateLocalChangeDelay() usually rejects a delay update if the value
  // is too small. This method ignores that check.
  void SetLocalChangeDelayIgnoringMinForTest(DataType type,
                                             const base::TimeDelta& delay);

  // Updates the parameters for commit quotas for the data types that can
  // receive commits via extension APIs. Empty optional means using the
  // defaults.
  void SetQuotaParamsForExtensionTypes(
      std::optional<int> max_tokens,
      std::optional<base::TimeDelta> refill_interval,
      std::optional<base::TimeDelta> depleted_quota_nudge_delay);

 private:
  using TypeTrackerMap =
      absl::flat_hash_map<DataType, std::unique_ptr<DataTypeTracker>>;

  friend class SyncSchedulerImplTest;

  TypeTrackerMap type_trackers_;

  // Tracks whether or not invalidations are currently enabled.
  bool invalidations_enabled_ = false;

  // This flag is set if suspect that some technical malfunction or known bug
  // may have left us with some unserviced invalidations.
  //
  // Keeps track of whether or not we're fully in sync with the invalidation
  // server.  This can be false even if invalidations are enabled and working
  // correctly.  For example, until we get ack-tracking working properly, we
  // won't persist invalidations between restarts, so we may be out of sync when
  // we restart.  The only way to get back into sync is to have invalidations
  // enabled, then complete a sync cycle to make sure we're fully up to date.
  bool invalidations_out_of_sync_ = true;
};

}  // namespace syncer

#endif  // COMPONENTS_SYNC_ENGINE_CYCLE_NUDGE_TRACKER_H_
