package com.atlassian.bitbucket.mesh.repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.util.Objects.requireNonNull;

/**
 * A repository.
 */
public interface Repository {

    String PARAM_QUARANTINE = "q";

    /**
     * @param repositoryId the repository ID
     * @return the canonical ID, which is the provided ID with any query parameters stripped off
     */
    @Nonnull
    static String canonicalId(@Nonnull String repositoryId) {
        requireNonNull(repositoryId, "repositoryId");
        int index = repositoryId.indexOf('?');
        if (index == -1) {
            return repositoryId;
        }
        return repositoryId.substring(0, index);
    }

    /**
     * Accepts the provided {@link RepositoryVisitor} and invokes the appropriate {@code visit} overload for this
     * repository type, returning its result.
     * <p>
     * Note: Nullability for the return value is intentionally not documented one way or another. It is expected that
     * the <i>caller</i> will know the nullability of the visitor implementation they are passing and code accordingly.
     *
     * @param visitor a visitor, which will be called with the <i>concrete</i> repository type
     * @param <T>     the type of object returned by the visitor when it completes
     * @return the visitor's result
     */
    <T> T accept(@Nonnull RepositoryVisitor<T> visitor);

    /**
     * @return {@code true} if the {@link #getPath() repository directory} exists and has not been marked as deleted
     */
    default boolean exists() {
        return Files.isDirectory(getPath());
    }

    /**
     * @return the {@link Path path} on disk where the repository's hooks are located
     */
    @Nonnull
    default Path getHooksDir() {
        //This default implementation assumes the repository is bare
        return getPath().resolve("hooks");
    }

    /**
     * @return the repository ID
     */
    @Nonnull
    String getId();

    /**
     * @return the {@link Path path} on disk where LFS data should be stored
     */
    @Nonnull
    Path getLfsDir();

    /**
     * @param name the parameter name
     * @return the parameter value, if one is specified on the {@link #getId() ID}
     */
    @Nullable
    String getParameter(@Nonnull String name);

    /**
     * @return the repository {@link Path path} on disk
     */
    @Nonnull
    Path getPath();

    /**
     * @param relativeTo the repository relative to which a relative {@link Path path} should be created
     * @return the repository {@link Path path} relative to {@code relativeTo}
     */
    @Nonnull
    default Path getRelativePath(@Nonnull Repository relativeTo) {
        Path repositoryDir = getPath();
        Path relativeDir = requireNonNull(relativeTo, "relativeTo").getPath();

        Path relativePath;
        try {
            relativePath = relativeDir.relativize(repositoryDir);
        } catch (IllegalArgumentException e) {
            //Building relative paths between filesystems (for example) is not possible. Handle that by
            //returning the absolute path
            return repositoryDir;
        }

        //Return whichever path is _shorter_. Depending on how many parent directories the two paths have
        //in common, it's possible the relative path is _longer_ than the absolute one
        return relativePath.toString().length() < repositoryDir.toString().length() ? relativePath : repositoryDir;
    }

    /**
     * @return a representation of the {@link #getId()} which uniquely identifies the repository and is safe
     *         to use in URLs.
     */
    @Nonnull
    String getSlug();

    /**
     * Return the <em>root</em> directory for where to create work trees.
     *
     * @return the path under which new work trees can be created
     */
    @Nonnull
    Path getWorkTreesDir();

    /**
     * Retrieves the read-only mode status for this repository.
     *
     * @return {@code true} if the repository is in read-only mode, otherwise {@code false}
     */
    boolean isReadOnly();

    /**
     * Enable/disable read-only mode for the given repository.
     *
     * @param value {@code true} to enable read-only mode, or {@code false} to disable read-only mode
     */
    void setReadOnly(boolean value);
}
