package com.atlassian.bitbucket.scm.git;

import com.atlassian.bitbucket.repository.NoDefaultBranchException;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;

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

/**
 * Describes an interface for performing operations against a {@code git} repository. The exact techniques used by
 * implementations are not mandated here beyond stating that they should not invoke {@code git} processes. All tasks
 * which rely on the {@code git} executable should be implemented by adding a type-safe builder interface which is
 * exposed by {@link GitScmCommandBuilder} for use by {@link GitCommandFactory}.
 */
public interface GitAgent {

    /**
     * The name of the symbolic reference which contains the head of the git repository, also known as the default
     * branch. By default, this is "refs/heads/master". However, it may be updated by either directly updating the
     * file to a new reference, or by executing {@code git symbolic-ref HEAD &lt;new-branch&gt;}.
     */
    String HEAD = "HEAD";
    /**
     * The name of the default remote added when a repository is cloned.
     */
    String ORIGIN = "origin";

    /**
     * Creates a reference with the specified {@code refName} pointing to the specified {@code hash}.
     * <p>
     * Reference names are assumed to be fully-qualified, in git parlance. This allows the creation of references that
     * are not in the standard git locations, but it also means trying to create a reference in a normal location, like
     * refs/heads, requires a qualified name like "refs/heads/new-branch" rather than simply specifying "new-branch".
     * <p>
     * The value provided <i>must</i> be a full 40-byte hash. It cannot be a short hash, nor can it be the name of a
     * reference, such as "master".
     *
     * @param repository the repository in which the new ref should be created
     * @param refName    the fully-qualified ref name
     * @param hash       the hash for the created ref
     */
    void createRef(@Nonnull Repository repository, @Nonnull String refName, @Nonnull String hash);

    /**
     * Creates a symbolic reference from the specified {@code refName} to the specified {@code targetRef}. Both names
     * must be <i>fully-qualified</i>.
     *
     * @param repository the repository in which the new symbolic ref should be created
     * @param refName    the fully-qualified ref name
     * @param targetRef  the fully-qualified name of the target ref
     */
    void createSymbolicRef(Repository repository, String refName, String targetRef);

    /**
     * Enables {@code git reflog} for the specified {@code refName}, if it has not already been enabled. If it has,
     * nothing is done.
     * <p>
     * In a non-bare clone, git enables logging for all refs by default. In a bare clone, it's possible to enable
     * logging the same way, but that is not always desirable. However, git internally automatically checks for the
     * existence of the reflog file and it uses that to determine whether to update it (it uses the enablement flag
     * to tell it to create that file when it creates a new ref, not whether to update the log). That allows logging
     * to be enabled for selective refs simply by "touching" their target log.
     *
     * @param repository the repository in which logging should be enabled for the specified ref
     * @param refName    the fully-qualified ref name
     */
    void enableReflog(@Nonnull Repository repository, @Nonnull String refName);

    /**
     * Retrieves the HEAD reference for the specified repository.
     * <p>
     * Note: The HEAD file is a standard part of git and, even for an empty repository, will never be empty. However,
     * for empty repositories it is possible that the reference pointed to by HEAD may not actually exist. This method
     * <i>does not</i> actually resolve the HEAD reference--it simply returns what it is. If the reference should be
     * resolved, thereby ensuring it actually exists, use {@link #resolveHead(Repository)} instead.
     *
     * @param repository the repository from which the HEAD should be retrieved
     * @return the HEAD for the specified repository
     * @throws GitDetachedHeadException Thrown if the specified repository has a HEAD which is not a reference.
     * @throws NoDefaultBranchException Throws if the specified repository has no HEAD.
     */
    @Nonnull
    String getHead(@Nonnull Repository repository) throws GitDetachedHeadException, NoDefaultBranchException;

    /**
     * Examines the specified repository to determine if it is empty. A repository is considered to be empty if it has
     * no branches or tags, with the assumption being that any objects pushed to a repository must be have a reference
     * pointing to them.
     *
     * @return {@code true} if the provided repository has no branches or tags; otherwise, {@code false}
     */
    boolean isEmpty(@Nonnull Repository repository);

    /**
     * Qualifies the provided {@code refName} as a branch, returning the qualified path to the reference.
     *
     * @param refName the reference to qualify as a branch
     * @return the qualified branch name
     */
    @Nonnull
    String qualifyBranch(@Nonnull String refName);

    /**
     * Qualifies the provided {@code refName} as a tag, returning the qualified path to the reference.
     *
     * @param refName the reference to qualify as a tag
     * @return the qualified tag name
     */
    @Nonnull
    String qualifyTag(@Nonnull String refName);

    /**
     * Resolves the {@link Branch} within the specified repository identified by the provided name. It is assumed that
     * the provided name is <i>not</i> absolute.
     * 
     * @param repository the repository to resolve the branch in
     * @param refName    the name of the branch to resolve
     * @return the resolved branch, or {@code null} if no such branch exists
     * @see #resolveBranch(Repository, String, boolean)
     */
    @Nullable
    Branch resolveBranch(@Nonnull Repository repository, @Nonnull String refName);

    /**
     * Resolves the {@link Branch} within the specified repository identified by the provided name, optionally applying
     * git precedence rules if the provided name is not absolute.
     * <p>
     * An example of an absolute name is "refs/heads/master". Absolute names must be matched exactly how they are; the
     * rules of ref precedence in git will not be applied. When dealing with non-absolute names, such as "master", git
     * applies a standard precedence in searching for the matching reference. Those same rules will be applied here.
     *
     * @param repository the repository to resolve the branch in
     * @param refName    the name of the branch to resolve
     * @param absolute   {@code true} if the ref name is already absolute, meaning there is no need to apply precedence
     *                   rules; otherwise, {@code false} to apply standard precedence rules
     * @return the resolved branch, or {@code null} if no such branch exists
     */
    @Nullable
    Branch resolveBranch(@Nonnull Repository repository, @Nonnull String refName, boolean absolute);

    /**
     * Resolves the {@link Branch} denoted by the {@link #getHead(Repository) HEAD} of the specified repository.
     * <p>
     * Note: For a repository which has had no commits, or which has had only commits targeting branches other than the
     * HEAD branch, attempting to resolve the HEAD will throw a {@link NoDefaultBranchException}.
     *
     * @param repository the repository to resolve the HEAD reference for
     * @return the resolved branch
     * @throws GitDetachedHeadException    Thrown if the specified repository has a HEAD which is not a reference.
     * @throws NoDefaultBranchException Thrown if the configured default branch for the specified repository does not
     *                                  exist. This may happen in {@link #isEmpty(Repository) empty} repositories, or
     *                                  repositories which do not use standard git layout (meaning they have no master
     *                                  branch).
     */
    @Nonnull
    Branch resolveHead(@Nonnull Repository repository) throws GitDetachedHeadException, NoDefaultBranchException;
}
