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

#ifndef CHROME_BROWSER_SYNC_ENGINE_GET_COMMIT_IDS_COMMAND_H_
#define CHROME_BROWSER_SYNC_ENGINE_GET_COMMIT_IDS_COMMAND_H_
#pragma once

#include <utility>
#include <vector>

#include "chrome/browser/sync/engine/syncer_command.h"
#include "chrome/browser/sync/engine/syncer_util.h"
#include "chrome/browser/sync/sessions/ordered_commit_set.h"
#include "chrome/browser/sync/sessions/sync_session.h"

using std::pair;
using std::vector;

namespace browser_sync {

class GetCommitIdsCommand : public SyncerCommand {
  friend class SyncerTest;

 public:
  explicit GetCommitIdsCommand(int commit_batch_size);
  virtual ~GetCommitIdsCommand();

  // SyncerCommand implementation.
  virtual void ExecuteImpl(sessions::SyncSession* session);

  // Builds a vector of IDs that should be committed.
  void BuildCommitIds(const vector<int64>& unsynced_handles,
                      syncable::WriteTransaction* write_transaction,
                      const ModelSafeRoutingInfo& routes);

  // TODO(chron): Remove writes from this iterator. As a warning, this
  // iterator causes writes to entries and so isn't a pure iterator.
  // It will do Put(IS_UNSYNCED). Refactor this out later.
  class CommitMetahandleIterator {
   public:
    // TODO(chron): Cache ValidateCommitEntry responses across iterators to save
    // UTF8 conversion and filename checking
    CommitMetahandleIterator(const vector<int64>& unsynced_handles,
                             syncable::WriteTransaction* write_transaction,
                             sessions::OrderedCommitSet* commit_set)
        : write_transaction_(write_transaction),
          handle_iterator_(unsynced_handles.begin()),
          unsynced_handles_end_(unsynced_handles.end()),
          commit_set_(commit_set) {

      // TODO(chron): Remove writes from this iterator.
      DCHECK(write_transaction_);

      if (Valid() && !ValidateMetahandleForCommit(*handle_iterator_))
        Increment();
    }
    ~CommitMetahandleIterator() {}

    int64 Current() const {
      DCHECK(Valid());
      return *handle_iterator_;
    }

    bool Increment() {
      if (!Valid())
        return false;

      for (++handle_iterator_;
           handle_iterator_ != unsynced_handles_end_;
           ++handle_iterator_) {
        if (ValidateMetahandleForCommit(*handle_iterator_))
          return true;
      }

      return false;
    }

    bool Valid() const {
      return !(handle_iterator_ == unsynced_handles_end_);
    }

    private:
     bool ValidateMetahandleForCommit(int64 metahandle) {
       if (commit_set_->HaveCommitItem(metahandle))
          return false;

       // We should really not WRITE in this iterator, but we can fix that
       // later. We should move that somewhere else later.
       syncable::MutableEntry entry(write_transaction_,
           syncable::GET_BY_HANDLE, metahandle);
       VerifyCommitResult verify_result =
           SyncerUtil::ValidateCommitEntry(&entry);
       if (verify_result == VERIFY_UNSYNCABLE) {
         // Drop unsyncable entries.
         entry.Put(syncable::IS_UNSYNCED, false);
       }
       return verify_result == VERIFY_OK;
     }

     syncable::WriteTransaction* const write_transaction_;
     vector<int64>::const_iterator handle_iterator_;
     vector<int64>::const_iterator unsynced_handles_end_;
     sessions::OrderedCommitSet* commit_set_;

     DISALLOW_COPY_AND_ASSIGN(CommitMetahandleIterator);
  };

 private:
  void AddUncommittedParentsAndTheirPredecessors(
      syncable::BaseTransaction* trans,
      syncable::Id parent_id,
      const ModelSafeRoutingInfo& routes);

  // OrderedCommitSet helpers for adding predecessors in order.
  // TODO(ncarter): Refactor these so that the |result| parameter goes away,
  // and AddItem doesn't need to consider two OrderedCommitSets.
  bool AddItem(syncable::Entry* item, sessions::OrderedCommitSet* result);
  bool AddItemThenPredecessors(syncable::BaseTransaction* trans,
                               syncable::Entry* item,
                               syncable::IndexedBitField inclusion_filter,
                               sessions::OrderedCommitSet* result);
  void AddPredecessorsThenItem(syncable::BaseTransaction* trans,
                               syncable::Entry* item,
                               syncable::IndexedBitField inclusion_filter,
                               const ModelSafeRoutingInfo& routes);

  bool IsCommitBatchFull();

  void AddCreatesAndMoves(const vector<int64>& unsynced_handles,
                          syncable::WriteTransaction* write_transaction,
                          const ModelSafeRoutingInfo& routes);

  void AddDeletes(const vector<int64>& unsynced_handles,
                  syncable::WriteTransaction* write_transaction);

  scoped_ptr<sessions::OrderedCommitSet> ordered_commit_set_;

  int requested_commit_batch_size_;

  DISALLOW_COPY_AND_ASSIGN(GetCommitIdsCommand);
};

}  // namespace browser_sync

#endif  // CHROME_BROWSER_SYNC_ENGINE_GET_COMMIT_IDS_COMMAND_H_