package com.atlassian.bitbucket.repository;

import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.validation.annotation.RequiredString;

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

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Describes a repository within the system.
 */
public interface Repository {

    // Same as slug, but also allows spaces after first character
    String NAME_REGEXP = "[\\p{Alnum}][\\w\\-\\. ]*";

    // Start alphanumeric then 0 or more alphanumeric or _, - or .
    String SLUG_REGEXP = "[\\p{Alnum}][\\w\\-\\.]*";

    int NAME_MAX_SIZE = 128;

    /**
     * Retrieves the unique identifier for the hierarchy of forks to which this repository belongs. Every repository
     * will be a member of a hierarchy, even if the hierarchy only contains that one repository. This allows logical
     * grouping of repositories and their forks, regardless of how many layers of forks there may be.
     *
     * @return this repository's hierarchy identifier
     */
    @Nonnull
    String getHierarchyId();

    /**
     * Retrieves the repository's ID, which represents its primary key.
     *
     * @return the repository's ID
     */
    int getId();

    /**
     * Retrieves the repository's name, which is guaranteed to be unique within its {@link #getProject() project} but
     * <i>not</i> within the system at large.
     *
     * @return the repository's name
     */
    @Nonnull
    @RequiredString(size = NAME_MAX_SIZE, regexp = NAME_REGEXP,
            message = "{com.atlassian.bitbucket.validation.repository.name.pattern.message}")
    String getName();

    /**
     * Retrieves the repository from which this repository was forked, if any. For top-level repositories, this value
     * will be {@code null}.
     * <p>
     * Note: Even when a repository's origin is {@code null}, it will still have a {@link #getHierarchyId() hierarchy
     * ID}. If the repository is ever forked, its forks will inherit the same hierarchy ID.
     *
     * @return the repository's origin, if it is a fork
     */
    @Nullable
    Repository getOrigin();

    /**
     * Retrieves the {@link Project project} to which this repository belongs.
     * <p>
     * Note: If this repository is a fork, it's {@link #getOrigin() origin} repository may belong to a different
     * project.
     *
     * @return the project to which this repository belongs
     */
    @Nonnull
    Project getProject();

    /**
     * Retrieves the unique identifier for the SCM used by this repository.
     *
     * @return the unique identifier of the repository's SCM
     * @see ScmService
     */
    @Nonnull
    @RequiredString
    String getScmId();

    /**
     * Retrieves the "slug" for this repository, which is a URL-friendly variant of its {@link #getName() name}. Each
     * repository's slug is guaranteed to be unique within its {@link #getProject() project}, but <i>not</i> within
     * the system at large.
     *
     * @return the repository's slug
     */
    @Nonnull
    @RequiredString(size = 128, regexp = SLUG_REGEXP)
    String getSlug();

    /**
     * Retrieves the repository's current {@link State state}.
     *
     * @return the repository's state
     */
    @Nonnull
    State getState();

    /**
     * Retrieves the {@link State#getStatusMessage() status message} for the repository's current {@link #getState()
     * state}.
     *
     * @return the repository's status message
     */
    @Nonnull
    String getStatusMessage();

    /**
     * Retrieves a flag indicating whether this repository is a fork of another repository. When {@code true}, the
     * repository will always have an {@link #getOrigin() origin}.
     * <p>
     * Note: Even repositories that are not forks, and have not been used as the origin of a fork, still exist in a
     * {@link #getHierarchyId() hierarchy}; the hierarchy just consists of a single repository.
     *
     * @return {@code true} if this repository is a fork; otherwise, {@code false}
     */
    boolean isFork();

    /**
     * Retrieves a flag indicating whether this repository may be forked. This flag is orthogonal to {@link #isFork()}.
     * It is possible to create a fork from a fork, if the repository is configured so, just as it may not be possible
     * to create forks from some top-level repositories.
     * <p>
     * If {@link RepositoryService#isForkingEnabled() forking is disabled} at the system level, even repositories which
     * are configured to allow forking (where this flag is {@code true}) <i>may not be forked</i>.
     *
     * @return {@code true} if the repository may be forked
     * @see RepositoryService#isForkingEnabled()
     */
    boolean isForkable();

    /**
     * Retrieves a flag indicating whether this repository is public.
     * <p>
     * Note, this flag is taken into account when calculating whether this repository is accessible to
     * unauthenticated users but is <i>not</i> the definitive answer. For a definitive answer, use
     * {@link PermissionService#isPubliclyAccessible(Repository) isPubliclyAccessible}.
     *
     * @return {@code true} if the repository has been marked as public, {@code false} otherwise
     */
    boolean isPublic();

    /**
     * Enumerates the possible states for a given {@link Repository repository}. In general, repositories will always
     * be in the {@link #AVAILABLE available} state.
     */
    enum State {

        /**
         * Indicates the repository has been created both in the database and in the associated SCM and may be pulled
         * from or pushed to normally.
         */
        AVAILABLE(1, "Available"),
        /**
         * Indicates the associated SCM was not able to create the repository's SCM-specific storage, such as a bare
         * clone on disk in {@code git}. Repositories in this state exist within the database, but may not be pulled
         * from or pushed to.
         */
        INITIALISATION_FAILED(2, "Failed to initialise repository"),
        /**
         * Indicates the repository has just been created in the database and the associated SCM is currently in the
         * process of creating its storage. Repositories in this state may not be pulled from or pushed to.
         */
        INITIALISING(0, "Initialising");

        private final int id;
        private final String statusMessage;

        State(int id, String statusMessage) {
            this.id = id;
            this.statusMessage = checkNotNull(statusMessage, "statusMessage");
        }

        /**
         * Retrieves a unique identifier for this state. Unlike the enum's ordinal or name, this value will never
         * change for a given entry.
         *
         * @return the state's unique identifier
         */
        public int getId() {
            return id;
        }

        /**
         * Retrieves the status message describing this state.
         *
         * @return the state's status message
         */
        @Nonnull
        public String getStatusMessage() {
            return statusMessage;
        }

        /**
         * Retrieves the state associated with the specified {@link #getId() ID}.
         *
         * @param id the ID to retrieve a {@code State} for
         * @return the state with the specified ID
         * @throws IllegalArgumentException if no state exists with the specified ID
         */
        public static State fromId(int id) {
            for (State value : values()) {
                if (value.getId() == id) {
                    return value;
                }
            }

            throw new IllegalArgumentException("No Repository.State is associated with ID [" + id + "]");
        }
    }
}
