package com.atlassian.bitbucket.mesh.repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.Normalizer;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;

import static java.util.Objects.requireNonNull;

/**
 * A {@link Repository} implementation which is simply a wrapper for the repository's path, used when Mesh is running
 * as a sidecar.
 */
public class DefaultPathRepository extends AbstractRepository implements PathRepository {

    private static final Pattern PATTERN_NON_LATIN_CHARS = Pattern.compile("[^\\p{InBasicLatin}]+");
    private static final Pattern PATTERN_SLUG_UNSAFE = Pattern.compile("[^a-zA-Z\\-_0-9.]+");

    private final String id;
    private final Path path;
    private final String slug;

    private Path hooksDir;

    public DefaultPathRepository(@Nonnull Path path) {
        this(PathRepository.formatId(path));
    }

    private DefaultPathRepository(String id) {
        this.id = id;

        path = Paths.get(id.substring(5)).toAbsolutePath();
        //Default the hooksDir to /path/to/repository/hooks
        hooksDir = path.resolve("hooks");
        //This is built around an assumption that the final name on the path uniquely identifies the repository.
        //This is always true with both Bitbucket Server (for both the shared home and data stores) and Bitbucket
        //Cloud.
        slug = slugify(path.getFileName().toString());
    }

    @Nullable
    public static DefaultPathRepository parseId(@Nonnull String repositoryId) {
        if (requireNonNull(repositoryId, "repositoryId").startsWith("path:")) {
            return new DefaultPathRepository(repositoryId);
        }

        return null;
    }

    @Override
    public <T> T accept(@Nonnull RepositoryVisitor<T> visitor) {
        return requireNonNull(visitor, "visitor").visit(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof DefaultPathRepository) {
            DefaultPathRepository that = (DefaultPathRepository) o;

            return Objects.equals(getPath(), that.getPath());
        }

        return false;
    }

    @Nonnull
    @Override
    public Path getHooksDir() {
        return hooksDir;
    }

    @Nonnull
    @Override
    public String getId() {
        return id;
    }

    @Nonnull
    @Override
    public Path getLfsDir() {
        throw new UnsupportedOperationException("PathRepositories do not support LFS");
    }

    @Nonnull
    @Override
    public Path getPath() {
        return path;
    }

    @Nonnull
    @Override
    public String getSlug() {
        return slug;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getPath());
    }

    @Override
    public String toString() {
        return slug;
    }

    @Nonnull
    @Override
    Path getTemplateDir() {
        throw new UnsupportedOperationException("PathRepositories do not have a template");
    }

    void setHooksDir(@Nonnull Path hooksDir) {
        this.hooksDir = requireNonNull(hooksDir, "hooksDir");
    }

    @Nonnull
    @Override
    public Path getWorkTreesDir() {
        throw new UnsupportedOperationException("PathRepositories do not support work trees");
    }

    @Nonnull
    private static String slugify(@Nonnull String value) {
        //Normalize the value
        String result = Normalizer.normalize(value, Normalizer.Form.NFKD);
        //Strip non-latin characters
        result = PATTERN_NON_LATIN_CHARS.matcher(result).replaceAll("");
        //Replace unsafe characters with a - and lowercase it
        return PATTERN_SLUG_UNSAFE.matcher(result).replaceAll("-").toLowerCase(Locale.US);
    }
}
