@Override
  public void deleteDocument(String docId, String prevRevId) throws ConflictException {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(docId), "Input document id can not be empty");
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(prevRevId), "Input previous revision id can not be empty");

    CouchUtils.validateRevisionId(prevRevId);

    DocumentDeleted documentDeleted = null;
    this.sqlDb.beginTransaction();
    try {
      BasicDocumentRevision preRevision = this.getDocument(docId, prevRevId);
      if (preRevision == null) {
        throw new IllegalArgumentException("The document trying to update does not exist.");
      }

      DocumentRevisionTree revisionTree = this.getAllRevisionsOfDocument(docId);
      if (revisionTree == null) {
        throw new IllegalArgumentException("Document does not exist for id: " + docId);
      } else if (!revisionTree.leafRevisionIds().contains(prevRevId)) {
        throw new ConflictException("Revision to be deleted is not a leaf node:" + prevRevId);
      }

      if (!preRevision.isDeleted()) {
        this.checkOffPreviousWinnerRevisionStatus(preRevision);
        String newRevisionId = CouchUtils.generateNextRevisionId(preRevision.getRevision());
        // Previous revision to be deleted could be winner revision ("current" == true),
        // or a non-winner leaf revision ("current" == false), the new inserted
        // revision must have the same flag as it previous revision.
        // Deletion of non-winner leaf revision is mainly used when resolving
        // conflicts.
        this.insertRevision(
            preRevision.getInternalNumericId(),
            newRevisionId,
            preRevision.getSequence(),
            true,
            preRevision.isCurrent(),
            JSONUtils.EMPTY_JSON,
            false);
        BasicDocumentRevision newRevision = this.getDocument(preRevision.getId(), newRevisionId);
        documentDeleted = new DocumentDeleted(preRevision, newRevision);
      }

      // Very tricky! Must call setTransactionSuccessful() even no change
      // to the db within this method. This is to allow this method to be
      // nested to other outer transaction, otherwise, the outer transaction
      // will rollback.
      this.sqlDb.setTransactionSuccessful();
    } finally {
      this.sqlDb.endTransaction();
      if (documentDeleted != null) {
        eventBus.post(documentDeleted);
      }
    }
  }
  @Override
  public BasicDocumentRevision updateDocument(
      String docId, String prevRevId, final DocumentBody body) throws ConflictException {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(docId), "Input document id can not be empty");
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(prevRevId), "Input previous revision id can not be empty");
    Preconditions.checkNotNull(body, "Input document body can not be null");
    this.validateDBBody(body);

    CouchUtils.validateRevisionId(prevRevId);

    DocumentUpdated documentUpdated = null;
    this.sqlDb.beginTransaction();
    try {
      BasicDocumentRevision preRevision = this.getDocument(docId, prevRevId);
      if (preRevision == null) {
        throw new IllegalArgumentException("The document trying to update does not exist.");
      }

      if (!preRevision.isCurrent()) {
        throw new ConflictException("Revision to be updated is not current revision.");
      }

      this.checkOffPreviousWinnerRevisionStatus(preRevision);
      String newRevisionId = this.insertNewWinnerRevision(body, preRevision);
      BasicDocumentRevision newRevision = this.getDocument(preRevision.getId(), newRevisionId);

      this.sqlDb.setTransactionSuccessful();
      documentUpdated = new DocumentUpdated(preRevision, newRevision);
      return newRevision;
    } finally {
      this.sqlDb.endTransaction();
      if (documentUpdated != null) {
        eventBus.post(documentUpdated);
      }
    }
  }
  @Override
  public BasicDocumentRevision createDocument(String docId, final DocumentBody body) {
    Preconditions.checkState(this.isOpen(), "Database is closed");
    CouchUtils.validateDocumentId(docId);
    Preconditions.checkNotNull(body, "Input document body can not be null");
    this.validateDBBody(body);

    DocumentCreated documentCreated = null;
    this.sqlDb.beginTransaction();
    try {
      long docNumericID = insertDocumentID(docId);
      if (docNumericID < 0) {
        throw new IllegalArgumentException(
            "Can not insert new doc, likely the docId exists already: " + docId);
      }

      String revisionId = CouchUtils.getFirstRevisionId();
      long newSequence =
          insertRevision(docNumericID, revisionId, -1l, false, true, body.asBytes(), true);
      if (newSequence < 0) {
        throw new IllegalStateException("Error inserting data, please checking data.");
      }

      BasicDocumentRevision doc = getDocument(docId, revisionId);
      documentCreated = new DocumentCreated(doc);

      Log.d(LOG_TAG, "New document created: " + doc.toString());

      this.sqlDb.setTransactionSuccessful();
      return doc;
    } finally {
      this.sqlDb.endTransaction();
      if (documentCreated != null) {
        eventBus.post(documentCreated);
      }
    }
  }
  private long insertDocumentHistoryIntoExistingTree(
      DocumentRevision newRevision,
      List<String> revisions,
      Long docNumericID,
      DocumentRevisionTree localRevs) {
    DocumentRevision parent = localRevs.lookup(newRevision.getId(), revisions.get(0));
    Preconditions.checkNotNull(parent, "Parent must not be null");
    BasicDocumentRevision previousLeaf = (BasicDocumentRevision) localRevs.getCurrentRevision();

    // Walk through the remote history in chronological order, matching each revision ID to
    // a local revision. When the list diverges, start creating blank local revisions to fill
    // in the local history
    int i;
    for (i = 1; i < revisions.size(); i++) {
      DocumentRevision nextNode = localRevs.lookupChildByRevId(parent, revisions.get(i));
      if (nextNode == null) {
        break;
      } else {
        parent = nextNode;
      }
    }

    if (i >= revisions.size()) {
      Log.v(LOG_TAG, "All revision are in local sqlDatabase already, no new revision inserted.");
      return -1;
    }

    // Insert the new stub revisions
    for (; i < revisions.size() - 1; i++) {
      Log.v(
          LOG_TAG,
          "Inserting new stub revision, id: " + docNumericID + ", rev: " + revisions.get(i));
      this.changeDocumentToBeNotCurrent(parent.getSequence());
      insertStubRevision(docNumericID, revisions.get(i), parent.getSequence());
      parent = getDocument(newRevision.getId(), revisions.get(i));
      localRevs.add(parent);
    }

    // Insert the new leaf revision
    Log.v(LOG_TAG, "Inserting new revision, id: " + docNumericID + ", rev: " + revisions.get(i));
    String newRevisionId = revisions.get(revisions.size() - 1);
    this.changeDocumentToBeNotCurrent(parent.getSequence());
    long sequence =
        insertRevision(
            docNumericID,
            newRevisionId,
            parent.getSequence(),
            newRevision.isDeleted(),
            true,
            newRevision.asBytes(),
            true);
    BasicDocumentRevision newLeaf = getDocument(newRevision.getId(), newRevisionId);
    localRevs.add(newLeaf);

    // Refresh previous leaf in case it is changed in sqlDb but not in memory
    previousLeaf = getDocument(previousLeaf.getId(), previousLeaf.getRevision());

    if (previousLeaf.isCurrent()) {
      // we have a conflicts, and we need to resolve it.
      pickWinnerOfConflicts(localRevs, newLeaf, previousLeaf);
    }

    return sequence;
  }
 private void pickWinnerOfConflicts(
     DocumentRevisionTree objectTree,
     BasicDocumentRevision newLeaf,
     BasicDocumentRevision previousLeaf) {
   // We are having a conflict, and we are resolving it
   if (newLeaf.isDeleted() == previousLeaf.isDeleted()) {
     // If both leafs are deleted or not
     int previousLeafDepth = objectTree.depth(previousLeaf.getSequence());
     int newLeafDepth = objectTree.depth(newLeaf.getSequence());
     if (previousLeafDepth > newLeafDepth) {
       this.changeDocumentToBeNotCurrent(newLeaf.getSequence());
     } else if (previousLeafDepth < newLeafDepth) {
       this.changeDocumentToBeNotCurrent(previousLeaf.getSequence());
     } else {
       // Compare revision hash if both leafs has same depth
       String previousRevisionHash = previousLeaf.getRevision().substring(2);
       String newRevisionHash = newLeaf.getRevision().substring(2);
       if (previousRevisionHash.compareTo(newRevisionHash) > 0) {
         this.changeDocumentToBeNotCurrent(newLeaf.getSequence());
       } else {
         this.changeDocumentToBeNotCurrent(previousLeaf.getSequence());
       }
     }
   } else {
     // If only one of the leaf is not deleted, that is the winner
     if (newLeaf.isDeleted()) {
       this.changeDocumentToBeNotCurrent(newLeaf.getSequence());
     } else {
       this.changeDocumentToBeNotCurrent(previousLeaf.getSequence());
     }
   }
 }