// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.git;

import static com.google.gerrit.server.git.GitRepositoryManager.REFS_NOTES_REVIEW;

import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;

import java.io.IOException;
import java.util.List;

import javax.annotation.Nullable;

/**
 * This class create code review notes for given {@link CodeReviewCommit}s.
 * <p>
 * After the {@link #create(List, PersonIdent)} method is invoked once this
 * instance must not be reused. Create a new instance of this class if needed.
 */
public class CreateCodeReviewNotes {
  public interface Factory {
    CreateCodeReviewNotes create(ReviewDb reviewDb, Repository db);
  }

  private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");

  private final AccountCache accountCache;
  private final ApprovalTypes approvalTypes;
  private final String canonicalWebUrl;
  private final String anonymousCowardName;
  private final ReviewDb schema;
  private final Repository db;

  private PersonIdent author;

  private RevWalk revWalk;
  private ObjectInserter inserter;

  private final NotesBranchUtil.Factory notesBranchUtilFactory;

  @Inject
  CreateCodeReviewNotes(
      @GerritPersonIdent final PersonIdent gerritIdent,
      final AccountCache accountCache,
      final ApprovalTypes approvalTypes,
      final @Nullable @CanonicalWebUrl String canonicalWebUrl,
      final @AnonymousCowardName String anonymousCowardName,
      final NotesBranchUtil.Factory notesBranchUtilFactory,
      final @Assisted  ReviewDb reviewDb,
      final @Assisted  Repository db) {
    this.author = gerritIdent;
    this.accountCache = accountCache;
    this.approvalTypes = approvalTypes;
    this.canonicalWebUrl = canonicalWebUrl;
    this.anonymousCowardName = anonymousCowardName;
    this.notesBranchUtilFactory = notesBranchUtilFactory;
    schema = reviewDb;
    this.db = db;
  }

  public void create(List<CodeReviewCommit> commits, PersonIdent author)
      throws CodeReviewNoteCreationException {
    try {
      revWalk = new RevWalk(db);
      inserter = db.newObjectInserter();
      if (author != null) {
        this.author = author;
      }

      NoteMap notes = NoteMap.newEmptyMap();
      StringBuilder message =
          new StringBuilder("Update notes for submitted changes\n\n");
      for (CodeReviewCommit c : commits) {
        notes.set(c, createNoteContent(c.change, c));
        message.append("* ").append(c.getShortMessage()).append("\n");
      }

      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
      notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
          message.toString());
    } catch (IOException e) {
      throw new CodeReviewNoteCreationException(e);
    } catch (ConcurrentRefUpdateException e) {
      throw new CodeReviewNoteCreationException(e);
    } finally {
      revWalk.release();
      inserter.release();
    }
  }

  public void create(List<Change> changes, PersonIdent author,
      String commitMessage, ProgressMonitor monitor) throws OrmException,
      IOException, CodeReviewNoteCreationException {
    try {
      revWalk = new RevWalk(db);
      inserter = db.newObjectInserter();
      if (author != null) {
        this.author = author;
      }
      if (monitor == null) {
        monitor = NullProgressMonitor.INSTANCE;
      }

      NoteMap notes = NoteMap.newEmptyMap();
      for (Change c : changes) {
        monitor.update(1);
        PatchSet ps = schema.patchSets().get(c.currentPatchSetId());
        ObjectId commitId = ObjectId.fromString(ps.getRevision().get());
        notes.set(commitId, createNoteContent(c, commitId));
      }

      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
      notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
          commitMessage);
    } catch (ConcurrentRefUpdateException e) {
      throw new CodeReviewNoteCreationException(e);
    } finally {
      revWalk.release();
      inserter.release();
    }
  }

  private ObjectId createNoteContent(Change change, ObjectId commit)
      throws CodeReviewNoteCreationException, IOException  {
    if (!(commit instanceof RevCommit)) {
      commit = revWalk.parseCommit(commit);
    }
    return createNoteContent(change, (RevCommit) commit);
  }

  private ObjectId createNoteContent(Change change, RevCommit commit)
      throws CodeReviewNoteCreationException, IOException {
    try {
      ReviewNoteHeaderFormatter formatter =
          new ReviewNoteHeaderFormatter(author.getTimeZone(),
              anonymousCowardName);
      final List<String> idList = commit.getFooterLines(CHANGE_ID);
      if (idList.isEmpty())
        formatter.appendChangeId(change.getKey());
      ResultSet<PatchSetApproval> approvals =
        schema.patchSetApprovals().byPatchSet(change.currentPatchSetId());
      PatchSetApproval submit = null;
      for (PatchSetApproval a : approvals) {
        if (a.getValue() == 0) {
          // Ignore 0 values.
        } else if (ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
          submit = a;
        } else {
          ApprovalType type = approvalTypes.byId(a.getCategoryId());
          if (type != null) {
            formatter.appendApproval(
                type.getCategory(),
                a.getValue(),
                accountCache.get(a.getAccountId()).getAccount());
          }
        }
      }

      if (submit != null) {
        formatter.appendSubmittedBy(accountCache.get(submit.getAccountId()).getAccount());
        formatter.appendSubmittedAt(submit.getGranted());
      }
      if (canonicalWebUrl != null) {
        formatter.appendReviewedOn(canonicalWebUrl, change.getId());
      }
      formatter.appendProject(change.getProject().get());
      formatter.appendBranch(change.getDest());
      return inserter.insert(Constants.OBJ_BLOB, formatter.toString().getBytes("UTF-8"));
    } catch (OrmException e) {
      throw new CodeReviewNoteCreationException(commit, e);
    }
  }
}

@@ -4474,24 +4474,48 @@
 oString());%0A
+      inserter.flush();%0A
     %7D catch 
@@ -5672,24 +5672,48 @@
 itMessage);%0A
+      inserter.flush();%0A
     %7D catch 

Call inserter.flush() when creating code review notes