package com.atlassian.bitbucket.repository;

import com.atlassian.bitbucket.project.Project;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;

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

/**
 * Defines the properties that can be set when creating a new {@link Repository}. To reduce backwards compatibility
 * issues as new properties are added over time, instances of this class may only be created using its {@link Builder}.
 * <p>
 * The following properties are <i>required</i>:
 * <ul>
 *     <li>{@link #getName()}: The name for the new repository</li>
 *     <li>{@link #getProject()}: The project to contain the repository</li>
 *     <li>{@link #getScmId()}: The SCM type for the new repository</li>
 * </ul>
 * Unless {@link #isForkable() otherwise specified}, the new repository will be {@link Repository#isForkable() forkable}
 * by default.
 * Unless {@link #isPublic() otherwise specified}, the new repository will not be {@link Repository#isPublic() public}
 * by default.
 * <p>
 * The repository's {@link Repository#getSlug() slug}, which is used in URLs (both for the browser and when cloning),
 * will be generated from the provided {@link #getName()}. Both the name and the generated slug must be unique within
 * the {@link #getProject() project} or the repository cannot be created.
 *
 * @see RepositoryService#create(RepositoryCreateRequest)
 */
public class RepositoryCreateRequest extends AbstractRepositoryRequest {

    private final Project project;
    private final String scmId;

    private RepositoryCreateRequest(String name, boolean forkable, Project project, String scmId, boolean publiclyAccessible) {
        super(name, forkable, publiclyAccessible);

        this.project = project;
        this.scmId = scmId;
    }

    /**
     * Retrieves the project in which the new repository will be created.
     *
     * @return the destination project
     */
    @Nonnull
    public Project getProject() {
        return project;
    }

    /**
     * Retrieves the SCM ID, defining the SCM to be used for the new repository. Once created, the SCM for a given
     * repository <i>cannot be changed</i>.
     *
     * @return the SCM for the new repository
     */
    @Nonnull
    public String getScmId() {
        return scmId;
    }

    /**
     * Constructs {@link RepositoryCreateRequest requests} for creating new repositories.
     */
    public static class Builder extends AbstractBuilder<Builder> {

        private Project project;
        private String scmId;

        public Builder() {
        }

        public Builder(@Nonnull Repository repository) {
            super(repository);

            project = repository.getProject();
            scmId = repository.getScmId();
        }

        public Builder(@Nonnull RepositoryCreateRequest request) {
            super(request);

            project = request.getProject();
            scmId = request.getScmId();
        }

        /**
         * Builds a {@link RepositoryCreateRequest request} from the assembled values. Before the request is built,
         * it is first verified that a {@link #name(String) name}, {@link #project(Project) project} and
         * {@link #scmId(String) SCM ID} were specified, and an exception is thrown if any are missing.
         *
         * @return the built request
         * @throws IllegalStateException if the {@link #name(String) name} or {@link #scmId(String) SCM ID} are blank
         *                               (empty, or containing only whitespace), or the {@link #project(Project)} is
         *                               {@code null}
         */
        @Nonnull
        public RepositoryCreateRequest build() {
            checkState(StringUtils.isNotBlank(name), "The name for the new repository is required");
            checkState(project != null, "The project in which to create the repository is required");
            checkState(StringUtils.isNotBlank(scmId), "The SCM type for the new repository is required");

            return new RepositoryCreateRequest(name, forkable, project, scmId, publiclyAccessible);
        }

        /**
         * Sets the project in which the repository will be created.
         * <p>
         * Note: This value is <i>required</i>. If this method is not called prior to {@link #build() building} the
         * request, an exception will be thrown.
         *
         * @param value the destination project
         * @return {@code this}
         * @throws NullPointerException if the provided {@code value} is {@code null}
         */
        @Nonnull
        public Builder project(@Nonnull Project value) {
            project = checkNotNull(value, "project");

            return self();
        }

        /**
         * Sets the SCM to be used by the new repository.
         * <p>
         * Note: This value is <i>required</i>. If this method is not called prior to {@link #build() building} the
         * request, an exception will be thrown.
         *
         * @param value the SCM used to handle the repository
         * @return {@code this}
         * @throws IllegalArgumentException if the provided {@code value} is empty or contains only whitespace
         * @throws NullPointerException if the provided {@code value} is {@code null}
         */
        @Nonnull
        public Builder scmId(@Nonnull String value) {
            scmId = checkNotBlank(value, "scmId");

            return self();
        }

        /**
         * @return {@code this}
         */
        @Override
        protected Builder self() {
            return this;
        }
    }
}
