// Copyright (C) 2014 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.testing;

import static com.google.common.base.MoreObjects.firstNonNull;

import com.google.common.collect.Ordering;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetInfo;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.AbstractChangeNotes;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Injector;
import java.time.ZoneId;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;

/**
 * Utility functions to create and manipulate Change, ChangeUpdate, and ChangeControl objects for
 * testing.
 */
public class TestChanges {
  private static final AtomicInteger nextChangeId = new AtomicInteger(1);

  public static Change newChange(Project.NameKey project, Account.Id userId) {
    return newChange(project, userId, nextChangeId.getAndIncrement());
  }

  public static Change newChange(Project.NameKey project, Account.Id userId, int id) {
    Change.Id changeId = Change.id(id);
    Change c =
        new Change(
            Change.key("Iabcd1234abcd1234abcd1234abcd1234abcd1234"),
            changeId,
            userId,
            BranchNameKey.create(project, "master"),
            TimeUtil.now());
    incrementPatchSet(c);
    return c;
  }

  public static PatchSet newPatchSet(PatchSet.Id id, ObjectId revision, Account.Id userId) {
    return newPatchSet(id, revision.name(), userId);
  }

  public static PatchSet newPatchSet(PatchSet.Id id, String revision, Account.Id userId) {
    return PatchSet.builder()
        .id(id)
        .commitId(ObjectId.fromString(revision))
        .uploader(userId)
        .createdOn(TimeUtil.now())
        .build();
  }

  public static ChangeUpdate newUpdate(
      Injector injector, Change c, CurrentUser user, boolean shouldExist) throws Exception {
    injector =
        injector.createChildInjector(
            new FactoryModule() {
              @Override
              public void configure() {
                bind(CurrentUser.class).toInstance(user);
              }
            });
    ChangeUpdate update =
        injector
            .getInstance(ChangeUpdate.Factory.class)
            .create(
                new ChangeNotes(
                        injector.getInstance(AbstractChangeNotes.Args.class), c, shouldExist, null)
                    .load(),
                user,
                TimeUtil.now(),
                Ordering.natural());

    ChangeNotes notes = update.getNotes();
    boolean hasPatchSets = notes.getPatchSets() != null && !notes.getPatchSets().isEmpty();
    if (hasPatchSets) {
      return update;
    }

    // Change doesn't exist yet. NoteDb requires that there be a commit for the
    // first patch set, so create one.
    GitRepositoryManager repoManager = injector.getInstance(GitRepositoryManager.class);
    try (Repository repo = repoManager.openRepository(c.getProject());
        TestRepository<Repository> tr = new TestRepository<>(repo)) {
      PersonIdent ident =
          user.asIdentifiedUser().newCommitterIdent(update.getWhen(), ZoneId.systemDefault());
      TestRepository<Repository>.CommitBuilder cb =
          tr.commit()
              .author(ident)
              .committer(ident)
              .message(firstNonNull(c.getSubject(), "Test change"));
      Ref parent = repo.exactRef(c.getDest().branch());
      if (parent != null) {
        cb.parent(tr.getRevWalk().parseCommit(parent.getObjectId()));
      }
      update.setBranch(c.getDest().branch());
      update.setChangeId(c.getKey().get());
      update.setCommit(tr.getRevWalk(), cb.create());
      return update;
    }
  }

  public static void incrementPatchSet(Change change) {
    PatchSet.Id curr = change.currentPatchSetId();
    PatchSetInfo ps =
        new PatchSetInfo(PatchSet.id(change.getId(), curr != null ? curr.get() + 1 : 1));
    ps.setSubject("Change subject");
    change.setCurrentPatchSet(ps);
  }
}
