// Copyright (C) 2009 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.submit;

import static java.util.stream.Collectors.toSet;

import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.inject.Provider;
import java.util.List;
import java.util.Optional;

/**
 * Status codes set on {@link com.google.gerrit.server.git.CodeReviewCommit}s by {@link
 * SubmitStrategy} implementations.
 */
public enum CommitMergeStatus {
  CLEAN_MERGE("Change has been successfully merged"),

  CLEAN_PICK("Change has been successfully cherry-picked"),

  CLEAN_REBASE("Change has been successfully rebased and submitted"),

  ALREADY_MERGED(""),

  PATH_CONFLICT(
      "Change could not be merged due to a path conflict.\n"
          + "\n"
          + "Please rebase the change locally and upload the rebased commit for review."),

  REBASE_MERGE_CONFLICT(
      "Change could not be merged due to a conflict.\n"
          + "\n"
          + "Please rebase the change locally and upload the rebased commit for review."),

  SKIPPED_IDENTICAL_TREE(
      "Marking change merged without cherry-picking to branch, as the resulting commit would be"
          + " empty."),

  MISSING_DEPENDENCY("Depends on change that was not submitted."),

  MANUAL_RECURSIVE_MERGE(
      "The change requires a local merge to resolve.\n"
          + "\n"
          + "Please merge (or rebase) the change locally and upload the resolution for review."),

  CANNOT_CHERRY_PICK_ROOT(
      "Cannot cherry-pick an initial commit onto an existing branch.\n"
          + "\n"
          + "Please merge the change locally and upload the merge commit for review."),

  CANNOT_REBASE_ROOT(
      "Cannot rebase an initial commit onto an existing branch.\n"
          + "\n"
          + "Please merge the change locally and upload the merge commit for review."),

  NOT_FAST_FORWARD(
      "Project policy requires all submissions to be a fast-forward.\n"
          + "\n"
          + "Please rebase the change locally and upload again for review."),

  EMPTY_COMMIT(
      "Change could not be merged because the commit is empty.\n"
          + "\n"
          + "Project policy requires all commits to contain modifications to at least one file.");

  private final String description;

  CommitMergeStatus(String description) {
    this.description = description;
  }

  public String getDescription() {
    return description;
  }

  public static String createMissingDependencyMessage(
      @Nullable CurrentUser caller,
      Provider<InternalChangeQuery> queryProvider,
      String commit,
      String otherCommit) {
    List<ChangeData> changes = queryProvider.get().enforceVisibility(true).byCommit(otherCommit);

    if (changes.isEmpty()) {
      return String.format(
          "Commit %s depends on commit %s which cannot be merged."
              + " Is the change of this commit not visible to '%s' or was it deleted?",
          commit, otherCommit, caller != null ? caller.getLoggableName() : "<user-not-available>");
    } else if (changes.size() == 1) {
      ChangeData cd = changes.get(0);
      if (cd.currentPatchSet().commitId().name().equals(otherCommit)) {
        return String.format(
            "Commit %s depends on commit %s of change %d which cannot be merged.",
            commit, otherCommit, cd.getId().get());
      }
      Optional<PatchSet> patchSet =
          cd.patchSets().stream().filter(ps -> ps.commitId().name().equals(otherCommit)).findAny();
      if (patchSet.isPresent()) {
        return String.format(
            "Commit %s depends on commit %s, which is outdated patch set %d of change %d."
                + " The latest patch set is %d.",
            commit,
            otherCommit,
            patchSet.get().id().get(),
            cd.getId().get(),
            cd.currentPatchSet().id().get());
      }
      // should not happen, fall-back to default message
      return String.format(
          "Commit %s depends on commit %s of change %d which cannot be merged.",
          commit, otherCommit, cd.getId().get());
    } else {
      return String.format(
          "Commit %s depends on commit %s of changes %s which cannot be merged.",
          commit, otherCommit, changes.stream().map(cd -> cd.getId().get()).collect(toSet()));
    }
  }
}
