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

// StatusController handles all counter and status related number crunching and
// state tracking on behalf of a SyncSession.  It 'controls' the model data
// defined in session_state.h.  The most important feature of StatusController
// is the ScopedModelSafetyRestriction. When one of these is active, the
// underlying data set exposed via accessors is swapped out to the appropriate
// set for the restricted ModelSafeGroup behind the scenes.  For example, if
// GROUP_UI is set, then accessors such as conflict_progress() and commit_ids()
// are implicitly restricted to returning only data pertaining to GROUP_UI.
// You can see which parts of status fall into this "restricted" category, or
// the global "shared" category for all model types, by looking at the struct
// declarations in session_state.h. If these accessors are invoked without a
// restriction in place, this is a violation and will cause debug assertions
// to surface improper use of the API in development.  Likewise for
// invocation of "shared" accessors when a restriction is in place; for
// safety's sake, an assertion will fire.
//
// NOTE: There is no concurrent access protection provided by this class. It
// assumes one single thread is accessing this class for each unique
// ModelSafeGroup, and also only one single thread (in practice, the
// SyncerThread) responsible for all "shared" access when no restriction is in
// place. Thus, every bit of data is to be accessed mutually exclusively with
// respect to threads.
//
// StatusController can also track if changes occur to certain parts of state
// so that various parts of the sync engine can avoid broadcasting
// notifications if no changes occurred.

#ifndef CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_
#define CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_
#pragma once

#include <map>

#include "base/stl_util-inl.h"
#include "chrome/browser/sync/sessions/ordered_commit_set.h"
#include "chrome/browser/sync/sessions/session_state.h"

namespace browser_sync {
namespace sessions {

class StatusController {
 public:
  explicit StatusController(const ModelSafeRoutingInfo& routes);
  ~StatusController();

  // Returns true if some portion of the session state has changed (is dirty)
  // since it was created or was last reset.
  bool TestAndClearIsDirty();

  // Progress counters.
  const ConflictProgress& conflict_progress() {
    return GetOrCreateModelSafeGroupState(true,
        group_restriction_)->conflict_progress;
  }
  ConflictProgress* mutable_conflict_progress() {
    return &GetOrCreateModelSafeGroupState(true,
        group_restriction_)->conflict_progress;
  }
  const UpdateProgress& update_progress() {
    return GetOrCreateModelSafeGroupState(true,
        group_restriction_)->update_progress;
  }
  UpdateProgress* mutable_update_progress() {
    return &GetOrCreateModelSafeGroupState(true,
        group_restriction_)->update_progress;
  }
  // Some unrestricted, non-ModelChangingSyncerCommand commands need to store
  // meta information about updates.
  UpdateProgress* GetUnrestrictedUpdateProgress(ModelSafeGroup group) {
    return &GetOrCreateModelSafeGroupState(false, group)->update_progress;
  }

  // ClientToServer messages.
  const ClientToServerMessage& commit_message() {
    return shared_.commit_message;
  }
  ClientToServerMessage* mutable_commit_message() {
    return &shared_.commit_message;
  }
  const ClientToServerResponse& commit_response() const {
    return shared_.commit_response;
  }
  ClientToServerResponse* mutable_commit_response() {
    return &shared_.commit_response;
  }
  const syncable::ModelTypeBitSet& updates_request_types() const {
    return shared_.updates_request_types;
  }
  void set_updates_request_types(const syncable::ModelTypeBitSet& value) {
    shared_.updates_request_types = value;
  }
  const ClientToServerResponse& updates_response() const {
    return shared_.updates_response;
  }
  ClientToServerResponse* mutable_updates_response() {
    return &shared_.updates_response;
  }

  // Errors and SyncerStatus.
  const ErrorCounters& error_counters() const {
    return shared_.error_counters.value();
  }
  const SyncerStatus& syncer_status() const {
    return shared_.syncer_status.value();
  }

  // Changelog related state.
  int64 num_server_changes_remaining() const {
    return shared_.num_server_changes_remaining.value();
  }

  // Commit path data.
  const std::vector<syncable::Id>& commit_ids() const {
    DCHECK(!group_restriction_in_effect_) << "Group restriction in effect!";
    return shared_.commit_set.GetAllCommitIds();
  }
  const OrderedCommitSet::Projection& commit_id_projection() {
    DCHECK(group_restriction_in_effect_)
        << "No group restriction for projection.";
    return shared_.commit_set.GetCommitIdProjection(group_restriction_);
  }
  const syncable::Id& GetCommitIdAt(size_t index) {
    DCHECK(CurrentCommitIdProjectionHasIndex(index));
    return shared_.commit_set.GetCommitIdAt(index);
  }
  syncable::ModelType GetCommitIdModelTypeAt(size_t index) {
    DCHECK(CurrentCommitIdProjectionHasIndex(index));
    return shared_.commit_set.GetModelTypeAt(index);
  }
  const std::vector<int64>& unsynced_handles() const {
    DCHECK(!group_restriction_in_effect_)
        << "unsynced_handles is unrestricted.";
    return shared_.unsynced_handles.value();
  }

  // Control parameters for sync cycles.
  bool conflict_sets_built() const {
    return shared_.control_params.conflict_sets_built;
  }
  bool conflicts_resolved() const {
    return shared_.control_params.conflicts_resolved;
  }
  bool did_commit_items() const {
    return shared_.control_params.items_committed;
  }

  // If a GetUpdates for any data type resulted in downloading an update that
  // is in conflict, this method returns true.
  bool HasConflictingUpdates() const;

  // Aggregate sum of ConflictingItemSize() over all ConflictProgress objects
  // (one for each ModelSafeGroup currently in-use).
  int TotalNumConflictingItems() const;

  // Returns the number of updates received from the sync server.
  int64 CountUpdates() const;

  // Returns true iff any of the commit ids added during this session are
  // bookmark related, and the bookmark group restriction is in effect.
  bool HasBookmarkCommitActivity() const {
    return ActiveGroupRestrictionIncludesModel(syncable::BOOKMARKS) &&
        shared_.commit_set.HasBookmarkCommitId();
  }

  // Returns true if the last download_updates_command received a valid
  // server response.
  bool download_updates_succeeded() const {
    return updates_response().has_get_updates();
  }

  // Returns true if the last updates response indicated that we were fully
  // up to date.  This is subtle: if it's false, it could either mean that
  // the server said there WAS more to download, or it could mean that we
  // were unable to reach the server.  If we didn't request every enabled
  // datatype, then we can't say for sure that there's nothing left to
  // download: in that case, this also returns false.
  bool ServerSaysNothingMoreToDownload() const;

  ModelSafeGroup group_restriction() const {
    return group_restriction_;
  }

  // Check whether a particular model is included by the active group
  // restriction.
  bool ActiveGroupRestrictionIncludesModel(syncable::ModelType model) const {
    if (!group_restriction_in_effect_)
      return true;
    ModelSafeRoutingInfo::const_iterator it = routing_info_.find(model);
    if (it == routing_info_.end())
      return false;
    return group_restriction() == it->second;
  }

  // A toolbelt full of methods for updating counters and flags.
  void increment_num_conflicting_commits_by(int value);
  void reset_num_conflicting_commits();
  void set_num_consecutive_transient_error_commits(int value);
  void increment_num_consecutive_transient_error_commits_by(int value);
  void set_num_consecutive_errors(int value);
  void increment_num_consecutive_errors();
  void increment_num_consecutive_errors_by(int value);
  void set_num_server_changes_remaining(int64 changes_remaining);
  void set_invalid_store(bool invalid_store);
  void set_syncer_stuck(bool syncer_stuck);
  void set_syncing(bool syncing);
  void set_num_successful_bookmark_commits(int value);
  void increment_num_successful_commits();
  void increment_num_successful_bookmark_commits();
  void increment_num_updates_downloaded_by(int value);
  void increment_num_tombstone_updates_downloaded_by(int value);
  void set_types_needing_local_migration(const syncable::ModelTypeSet& types);
  void set_unsynced_handles(const std::vector<int64>& unsynced_handles);

  void set_commit_set(const OrderedCommitSet& commit_set);
  void update_conflict_sets_built(bool built);
  void update_conflicts_resolved(bool resolved);
  void reset_conflicts_resolved();
  void set_items_committed();

 private:
  friend class ScopedModelSafeGroupRestriction;

  // Returns true iff the commit id projection for |group_restriction_|
  // references position |index| into the full set of commit ids in play.
  bool CurrentCommitIdProjectionHasIndex(size_t index);

  // Helper to lazily create objects for per-ModelSafeGroup state.
  PerModelSafeGroupState* GetOrCreateModelSafeGroupState(bool restrict,
                                                         ModelSafeGroup group);

  AllModelTypeState shared_;
  std::map<ModelSafeGroup, PerModelSafeGroupState*> per_model_group_;

  STLValueDeleter<std::map<ModelSafeGroup, PerModelSafeGroupState*> >
      per_model_group_deleter_;

  // Set to true if any DirtyOnWrite pieces of state we maintain are changed.
  // Reset to false by TestAndClearIsDirty.
  bool is_dirty_;

  // Used to fail read/write operations on state that don't obey the current
  // active ModelSafeWorker contract.
  bool group_restriction_in_effect_;
  ModelSafeGroup group_restriction_;

  const ModelSafeRoutingInfo routing_info_;

  DISALLOW_COPY_AND_ASSIGN(StatusController);
};

// A utility to restrict access to only those parts of the given
// StatusController that pertain to the specified ModelSafeGroup.
class ScopedModelSafeGroupRestriction {
 public:
  ScopedModelSafeGroupRestriction(StatusController* to_restrict,
                                  ModelSafeGroup restriction)
      : status_(to_restrict) {
    DCHECK(!status_->group_restriction_in_effect_);
    status_->group_restriction_ = restriction;
    status_->group_restriction_in_effect_ = true;
  }
  ~ScopedModelSafeGroupRestriction() {
    DCHECK(status_->group_restriction_in_effect_);
    status_->group_restriction_in_effect_ = false;
  }
 private:
  StatusController* status_;
  DISALLOW_COPY_AND_ASSIGN(ScopedModelSafeGroupRestriction);
};

}
}

#endif  // CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_