package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.git.commit.Commit;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcChangeType;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.atlassian.bitbucket.mesh.rpc.v1.RpcChangeType.*;

/**
 * Defines the standard change types which may occur in a given {@link Commit commit}.
 * <p>
 * All of the names used here are strongly influenced by {@code git}'s documentation, and status codes are linked to
 * each type. These names and codes are drawn from documentation from {@code git help diff-tree} (which is shared with
 * several other commands).
 */
public enum GitChangeType {

    /**
     * A: addition of a file
     */
    ADD(CHANGE_TYPE_ADD, "A"),
    /**
     * C: copy of a file into a new one
     */
    COPY(CHANGE_TYPE_COPY, "C", true),
    /**
     * D: deletion of a file
     */
    DELETE(CHANGE_TYPE_DELETE, "D"),
    /**
     * M: modification of the contents or mode of a file
     */
    MODIFY(CHANGE_TYPE_MODIFY, "M"),
    /**
     * R: renaming of a file
     * <p>
     * Note: {@code MOVE} is a generic term for this; {@code RENAME} is {@code git}'s term.
     */
    RENAME(CHANGE_TYPE_RENAME, "R", true),
    /**
     * T: change in the type of the file
     */
    TYPE(CHANGE_TYPE_TYPE, "T"),
    /**
     * U: file is unmerged (you must complete the merge before it can be committed)
     * <p>
     * Note: This status should not be possible in a bare clone, such as what is used to serve a repository.
     */
    UNMERGED(CHANGE_TYPE_UNMERGED, "U"),
    /**
     * X: "unknown" change type
     * <p>
     * Note: Per the {@code git} documentation, unknown almost always indicates a bug in {@code git}.
     */
    UNKNOWN(CHANGE_TYPE_UNKNOWN, "X");

    private final RpcChangeType rpc;
    private final boolean scored;
    private final String status;

    GitChangeType(RpcChangeType rpc, String status) {
        this(rpc, status, false);
    }

    GitChangeType(RpcChangeType rpc, String status, boolean scored) {
        this.rpc = rpc;
        this.scored = scored;
        this.status = status;
    }

    /**
     * Evaluates all change types to determine if one exists with the provided {@link #getStatus() status code}. If
     * no match is found, {@link #UNKNOWN} is returned.
     *
     * @param status the status code to evaluate
     * @return the matching change type, or {@link #UNKNOWN} if there is no match
     */
    @Nonnull
    public static GitChangeType fromStatus(@Nullable String status) {
        for (GitChangeType value : values()) {
            if (StringUtils.equals(status, value.getStatus())) {
                return value;
            }
        }
        return UNKNOWN;
    }

    /**
     * Retrieves the {@code git} status code associated with this change type.
     *
     * @return the status code
     */
    @Nonnull
    public String getStatus() {
        return status;
    }

    /**
     * Flag indicating whether {@code git} provides a score with changes of this type. "score" is {@code git}'s term
     * for the similarity percentage between two files.
     *
     * @return {@code true} if this change is scored; otherwise {@code false}
     */
    public boolean isScored() {
        return scored;
    }

    @Nonnull
    public RpcChangeType toRpc() {
        return rpc;
    }
}
