package com.atlassian.bitbucket.mesh.util;

import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.StringUtils.repeat;

/**
 * Provides utility methods for working with commit hashes.
 */
public class ShaUtils {

    public static final String NULL_SHA1 = repeat("0", 40);
    public static final Pattern PATTERN_HASH = Pattern.compile("[a-fA-F0-9]{40}");

    private ShaUtils() {
        throw new UnsupportedOperationException(getClass().getName() +
                " is a utility class and should not be instantiated");
    }

    /**
     * Compares two hashes, returning {@code 0} if they are equal, {@code < 0} if {@code hash1 < hash2} and
     * {@code > 0} if {@code hash1 > hash2}. Hashes are defined to be equal if they are identical, or one hash is a
     * prefix of the other hash. For instance {@code abcde123} is equal to {@code abcde}.
     *
     * @param hash1 the first hash
     * @param hash2 the second hash
     * @return {@code 0} if the hashes are equal {@code < 0} if the {@code hash1 < hash2} and
     *         {@code > 0} if {@code hash1 > hash2}
     * @throws IllegalArgumentException if either hash is {@code null} or empty
     */
    public static int compare(@Nonnull String hash1, @Nonnull String hash2) {
        checkArgument(StringUtils.isNotEmpty(hash1), "hash1 is empty");
        checkArgument(StringUtils.isNotEmpty(hash2), "hash2 is empty");

        int length1 = hash1.length();
        int length2 = hash2.length();

        // If the two hashes are not the same length, trim the longer one to the shorter one's length
        if (length1 < length2) {
            hash2 = hash2.substring(0, length1);
        } else if (length2 < length1) {
            hash1 = hash1.substring(0, length2);
        }

        // Now that the hashes are the same length they can be compared directly
        return hash1.compareTo(hash2);
    }

    /**
     * Checks whether two hashes are equal. Hashes are defined to be equal if they are identical, or one hash is a
     * prefix of the other hash. For instance {@code abcde123} is equal to {@code abcde}.
     *
     * @param hash1 the first hash
     * @param hash2 the second hash
     * @return {@code true} if the hashes are equal, otherwise {@code false}
     */
    public static boolean hashesMatch(@Nullable String hash1, @Nullable String hash2) {
        if (hash1 == null || hash2 == null) {
            return Objects.equals(hash1, hash2);
        }
        return compare(hash1, hash2) == 0;
    }

    /**
     * @param value the string to test
     * @return {@code true} if {@code value} is a SHA1 hash
     */
    public static boolean isHash(String value) {
        return value != null && PATTERN_HASH.matcher(value).matches();
    }

    /**
     * @param value the string to test
     * @return {@code true} if {@code value} {@link #hashesMatch matches} the {@link #NULL_SHA1};
     *         otherwise, {@code false}
     */
    public static boolean isNull(String value) {
        return hashesMatch(value, NULL_SHA1);
    }
}