package com.atlassian.stash.internal.scm.git.binary;

import com.atlassian.fugue.Either;
import com.atlassian.bitbucket.util.Version;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.SystemUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Set;

public interface GitBinaryHelper {

    /**
     * The minimum Git version required for Stash to run.
     */
    Version MINIMUM_VERSION = new Version(1, 8);
    /**
     * The new {@link #MINIMUM_VERSION} that will be applied in the next major release. Instances on older versions
     * of Git should be encouraged to upgrade.
     *
     * @since 3.10
     */
    Version NEXT_MINIMUM_VERSION = new Version(1, 8);
    /**
     * Git 1.8.4.3 has a bug which breaks hosting operations in repositories with more than a handful of symbolic
     * refs. Since pull requests use symbolic refs heavily, this bug breaks repositories which use pull requests.
     * As a result, this version is explicitly blocked.
     */
    Version VERSION_1_8_4_3 = new Version(1, 8, 4, 3);
    /**
     * Git 2.0.2 has a bug which causes {@code git diff-tree} to display incorrect commit details when using
     * {@code --stdin}.
     * <p>
     * <a href="http://git.661346.n2.nabble.com/git-diff-tree-commit-detail-bug-in-2-0-2-and-2-0-3-tp7615867.html">
     * Details in Nabble</a>.
     */
    Version VERSION_2_0_2 = new Version(2, 0, 2);
    /**
     * Git 2.0.3 has a bug which causes {@code git diff-tree} to display incorrect commit details when using
     * {@code --stdin}.
     * <p>
     * <a href="http://git.661346.n2.nabble.com/git-diff-tree-commit-detail-bug-in-2-0-2-and-2-0-3-tp7615867.html">
     * Details in Nabble</a>.
     */
    Version VERSION_2_0_3 = new Version(2, 0, 3);
    /**
     * Versions of Git which are restricted and may not be used due to significant bugs which break core functionality.
     * If any of these versions are detected during startup the system will be locked.
     */
    Set<Version> RESTRICTED_VERSIONS = ImmutableSet.of(VERSION_1_8_4_3, VERSION_2_0_2, VERSION_2_0_3);
    /**
     * Extra locations to check for the Git executable (if it is not in the {@code $PATH}), on Unix-like operating
     * systems such as Linux and MacOS.
     */
    List<String> DEFAULT_LOCATIONS = ImmutableList.of(
            "/usr/bin", "/bin", "/usr/sbin", "/sbin", // standard paths (should already be in the PATH env)
            "/usr/local/bin",     // Homebrew
            "/opt/local/bin",     // MacPort
            "/usr/local/git/bin", // git-osx-installer
            "/opt/bin", SystemUtils.USER_HOME + "/bin");

    /**
     * Extra locations to check for the Git executable (if it is not in the {@code %PATH%}), on Windows.
     */
    List<String> DEFAULT_LOCATIONS_WINDOWS = ImmutableList.of(
            "C:\\Program Files (x86)\\Git\\bin", "C:\\Program Files\\Git\\bin", // msysgit
            "C:\\Cygwin\\bin", // cygwin git
            "C:\\Git\\bin");

    /**
     * If the operating system is Windows, ensures a {@code .exe} extension is present on the provided {@code binary}.
     *
     * @param binary the binary to apply the extension to
     * @return an executable with an extension applied or not, depending on operating system
     * @throws NullPointerException if the provided {@code binary} is {@code null}
     */
    @Nonnull
    String applyExtension(@Nonnull String binary);

    /**
     * Attempts to the {@code git} binary in a set of predefined locations.
     * <p>
     * The following locations are searched, in order:
     * <ul>
     * <li>the configured location, if provided</li>
     * <li>each directory in the {@code PATH}, in order</li>
     * <li>operating system-specific default locations where {@code git} is installed</li>
     * </ul>
     *
     * @param configuredLocation default location for Git
     * @return the first Git executable above the minimum version or {@code configuredLocation} if Git could not be found
     *         in any of those locations
     */
    @Nonnull
    Either<RejectedGitBinary, GitBinary> find(@Nullable String configuredLocation);
}
