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

#include "chrome/browser/sync/sessions/session_state.h"

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

#include "base/base64.h"
#include "base/values.h"
#include "chrome/browser/sync/protocol/proto_enum_conversions.h"

using std::set;
using std::vector;

namespace browser_sync {
namespace sessions {

SyncSourceInfo::SyncSourceInfo()
    : updates_source(sync_pb::GetUpdatesCallerInfo::UNKNOWN) {}

SyncSourceInfo::SyncSourceInfo(
    const syncable::ModelTypePayloadMap& t)
    : updates_source(sync_pb::GetUpdatesCallerInfo::UNKNOWN), types(t) {}

SyncSourceInfo::SyncSourceInfo(
    const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource& u,
    const syncable::ModelTypePayloadMap& t)
    : updates_source(u), types(t) {}

SyncSourceInfo::~SyncSourceInfo() {}

DictionaryValue* SyncSourceInfo::ToValue() const {
  DictionaryValue* value = new DictionaryValue();
  value->SetString("updatesSource",
                   GetUpdatesSourceString(updates_source));
  value->Set("types", syncable::ModelTypePayloadMapToValue(types));
  return value;
}

SyncerStatus::SyncerStatus()
    : invalid_store(false),
      syncer_stuck(false),
      syncing(false),
      num_successful_commits(0),
      num_successful_bookmark_commits(0),
      num_updates_downloaded_total(0),
      num_tombstone_updates_downloaded_total(0) {
}

SyncerStatus::~SyncerStatus() {
}

DictionaryValue* SyncerStatus::ToValue() const {
  DictionaryValue* value = new DictionaryValue();
  value->SetBoolean("invalidStore", invalid_store);
  value->SetBoolean("syncerStuck", syncer_stuck);
  value->SetBoolean("syncing", syncing);
  value->SetInteger("numSuccessfulCommits", num_successful_commits);
  value->SetInteger("numSuccessfulBookmarkCommits",
                num_successful_bookmark_commits);
  value->SetInteger("numUpdatesDownloadedTotal",
                num_updates_downloaded_total);
  value->SetInteger("numTombstoneUpdatesDownloadedTotal",
                num_tombstone_updates_downloaded_total);
  return value;
}

DictionaryValue* DownloadProgressMarkersToValue(
    const std::string
        (&download_progress_markers)[syncable::MODEL_TYPE_COUNT]) {
  DictionaryValue* value = new DictionaryValue();
  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    // TODO(akalin): Unpack the value into a protobuf.
    std::string base64_marker;
    bool encoded =
        base::Base64Encode(download_progress_markers[i], &base64_marker);
    DCHECK(encoded);
    value->SetString(
        syncable::ModelTypeToString(syncable::ModelTypeFromInt(i)),
        base64_marker);
  }
  return value;
}

ErrorCounters::ErrorCounters()
    : num_conflicting_commits(0),
      consecutive_transient_error_commits(0),
      consecutive_errors(0) {
}

DictionaryValue* ErrorCounters::ToValue() const {
  DictionaryValue* value = new DictionaryValue();
  value->SetInteger("numConflictingCommits", num_conflicting_commits);
  value->SetInteger("consecutiveTransientErrorCommits",
                consecutive_transient_error_commits);
  value->SetInteger("consecutiveErrors", consecutive_errors);
  return value;
}

SyncSessionSnapshot::SyncSessionSnapshot(
    const SyncerStatus& syncer_status,
    const ErrorCounters& errors,
    int64 num_server_changes_remaining,
    bool is_share_usable,
    const syncable::ModelTypeBitSet& initial_sync_ended,
    const std::string
        (&download_progress_markers)[syncable::MODEL_TYPE_COUNT],
    bool more_to_sync,
    bool is_silenced,
    int64 unsynced_count,
    int num_conflicting_updates,
    bool did_commit_items,
    const SyncSourceInfo& source)
    : syncer_status(syncer_status),
      errors(errors),
      num_server_changes_remaining(num_server_changes_remaining),
      is_share_usable(is_share_usable),
      initial_sync_ended(initial_sync_ended),
      download_progress_markers(),
      has_more_to_sync(more_to_sync),
      is_silenced(is_silenced),
      unsynced_count(unsynced_count),
      num_conflicting_updates(num_conflicting_updates),
      did_commit_items(did_commit_items),
      source(source) {
  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
       i < syncable::MODEL_TYPE_COUNT; ++i) {
    const_cast<std::string&>(this->download_progress_markers[i]).assign(
        download_progress_markers[i]);
  }
}

SyncSessionSnapshot::~SyncSessionSnapshot() {}

DictionaryValue* SyncSessionSnapshot::ToValue() const {
  DictionaryValue* value = new DictionaryValue();
  value->Set("syncerStatus", syncer_status.ToValue());
  value->Set("errors", errors.ToValue());
  // We don't care too much if we lose precision here.
  value->SetInteger("numServerChangesRemaining",
                    static_cast<int>(num_server_changes_remaining));
  value->SetBoolean("isShareUsable", is_share_usable);
  value->Set("initialSyncEnded",
             syncable::ModelTypeBitSetToValue(initial_sync_ended));
  value->Set("downloadProgressMarkers",
             DownloadProgressMarkersToValue(download_progress_markers));
  value->SetBoolean("hasMoreToSync", has_more_to_sync);
  value->SetBoolean("isSilenced", is_silenced);
  // We don't care too much if we lose precision here, also.
  value->SetInteger("unsyncedCount",
                    static_cast<int>(unsynced_count));
  value->SetInteger("numConflictingUpdates", num_conflicting_updates);
  value->SetBoolean("didCommitItems", did_commit_items);
  value->Set("source", source.ToValue());
  return value;
}

ConflictProgress::ConflictProgress(bool* dirty_flag) : dirty_(dirty_flag) {}

ConflictProgress::~ConflictProgress() {
  CleanupSets();
}

IdToConflictSetMap::const_iterator ConflictProgress::IdToConflictSetFind(
    const syncable::Id& the_id) const {
  return id_to_conflict_set_.find(the_id);
}

IdToConflictSetMap::const_iterator
ConflictProgress::IdToConflictSetBegin() const {
  return id_to_conflict_set_.begin();
}

IdToConflictSetMap::const_iterator
ConflictProgress::IdToConflictSetEnd() const {
  return id_to_conflict_set_.end();
}

IdToConflictSetMap::size_type ConflictProgress::IdToConflictSetSize() const {
  return id_to_conflict_set_.size();
}

const ConflictSet* ConflictProgress::IdToConflictSetGet(
    const syncable::Id& the_id) {
  return id_to_conflict_set_[the_id];
}

std::set<ConflictSet*>::const_iterator
ConflictProgress::ConflictSetsBegin() const {
  return conflict_sets_.begin();
}

std::set<ConflictSet*>::const_iterator
ConflictProgress::ConflictSetsEnd() const {
  return conflict_sets_.end();
}

std::set<ConflictSet*>::size_type
ConflictProgress::ConflictSetsSize() const {
  return conflict_sets_.size();
}

std::set<syncable::Id>::iterator
ConflictProgress::ConflictingItemsBegin() {
  return conflicting_item_ids_.begin();
}
std::set<syncable::Id>::const_iterator
ConflictProgress::ConflictingItemsBeginConst() const {
  return conflicting_item_ids_.begin();
}
std::set<syncable::Id>::const_iterator
ConflictProgress::ConflictingItemsEnd() const {
  return conflicting_item_ids_.end();
}

void ConflictProgress::AddConflictingItemById(const syncable::Id& the_id) {
  std::pair<std::set<syncable::Id>::iterator, bool> ret =
    conflicting_item_ids_.insert(the_id);
  if (ret.second)
    *dirty_ = true;
}

void ConflictProgress::EraseConflictingItemById(const syncable::Id& the_id) {
  int items_erased = conflicting_item_ids_.erase(the_id);
  if (items_erased != 0)
    *dirty_ = true;
}

void ConflictProgress::MergeSets(const syncable::Id& id1,
                                 const syncable::Id& id2) {
  // There are no single item sets, we just leave those entries == 0
  vector<syncable::Id>* set1 = id_to_conflict_set_[id1];
  vector<syncable::Id>* set2 = id_to_conflict_set_[id2];
  vector<syncable::Id>* rv = 0;
  if (0 == set1 && 0 == set2) {
    // Neither item currently has a set so we build one.
    rv = new vector<syncable::Id>();
    rv->push_back(id1);
    if (id1 != id2) {
      rv->push_back(id2);
    } else {
      LOG(WARNING) << "[BUG] Attempting to merge two identical conflict ids.";
    }
    conflict_sets_.insert(rv);
  } else if (0 == set1) {
    // Add the item to the existing set.
    rv = set2;
    rv->push_back(id1);
  } else if (0 == set2) {
    // Add the item to the existing set.
    rv = set1;
    rv->push_back(id2);
  } else if (set1 == set2) {
    // It's the same set already.
    return;
  } else {
    // Merge the two sets.
    rv = set1;
    // Point all the second sets id's back to the first.
    vector<syncable::Id>::iterator i;
    for (i = set2->begin() ; i != set2->end() ; ++i) {
      id_to_conflict_set_[*i] = rv;
    }
    // Copy the second set to the first.
    rv->insert(rv->end(), set2->begin(), set2->end());
    conflict_sets_.erase(set2);
    delete set2;
  }
  id_to_conflict_set_[id1] = id_to_conflict_set_[id2] = rv;
}

void ConflictProgress::CleanupSets() {
  // Clean up all the sets.
  set<ConflictSet*>::iterator i;
  for (i = conflict_sets_.begin(); i != conflict_sets_.end(); i++) {
    delete *i;
  }
  conflict_sets_.clear();
  id_to_conflict_set_.clear();
}

UpdateProgress::UpdateProgress() {}

UpdateProgress::~UpdateProgress() {}

void UpdateProgress::AddVerifyResult(const VerifyResult& verify_result,
                                     const sync_pb::SyncEntity& entity) {
  verified_updates_.push_back(std::make_pair(verify_result, entity));
}

void UpdateProgress::AddAppliedUpdate(const UpdateAttemptResponse& response,
    const syncable::Id& id) {
  applied_updates_.push_back(std::make_pair(response, id));
}

std::vector<AppliedUpdate>::iterator UpdateProgress::AppliedUpdatesBegin() {
  return applied_updates_.begin();
}

std::vector<VerifiedUpdate>::const_iterator
UpdateProgress::VerifiedUpdatesBegin() const {
  return verified_updates_.begin();
}

std::vector<AppliedUpdate>::const_iterator
UpdateProgress::AppliedUpdatesEnd() const {
  return applied_updates_.end();
}

std::vector<VerifiedUpdate>::const_iterator
UpdateProgress::VerifiedUpdatesEnd() const {
  return verified_updates_.end();
}

int UpdateProgress::SuccessfullyAppliedUpdateCount() const {
  int count = 0;
  for (std::vector<AppliedUpdate>::const_iterator it =
       applied_updates_.begin();
       it != applied_updates_.end();
       ++it) {
    if (it->first == SUCCESS)
      count++;
  }
  return count;
}

// Returns true if at least one update application failed due to a conflict
// during this sync cycle.
bool UpdateProgress::HasConflictingUpdates() const {
  std::vector<AppliedUpdate>::const_iterator it;
  for (it = applied_updates_.begin(); it != applied_updates_.end(); ++it) {
    if (it->first == CONFLICT) {
      return true;
    }
  }
  return false;
}

AllModelTypeState::AllModelTypeState(bool* dirty_flag)
    : unsynced_handles(dirty_flag),
      syncer_status(dirty_flag),
      error_counters(dirty_flag),
      num_server_changes_remaining(dirty_flag, 0),
      commit_set(ModelSafeRoutingInfo()) {
}

AllModelTypeState::~AllModelTypeState() {}

PerModelSafeGroupState::PerModelSafeGroupState(bool* dirty_flag)
    : conflict_progress(dirty_flag) {
}

PerModelSafeGroupState::~PerModelSafeGroupState() {
}

}  // namespace sessions
}  // namespace browser_sync