package com.atlassian.bitbucket.mesh.git.commit;

import com.atlassian.bitbucket.mesh.git.AbstractParameters;
import com.atlassian.bitbucket.mesh.git.builder.revlist.GitRevListMerges;
import com.atlassian.bitbucket.mesh.git.builder.revlist.GitRevListOrder;
import com.atlassian.bitbucket.mesh.git.builder.revlist.GitRevListWalk;
import com.atlassian.bitbucket.mesh.git.pull.PullRequest;
import com.atlassian.bitbucket.mesh.git.pull.PullRequestRef;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Instant;
import java.util.Objects;
import java.util.Set;

import static java.lang.Math.max;
import static java.util.Objects.requireNonNull;

public class StreamCommitsParameters extends AbstractParameters {

    public static final int UNLIMITED_MESSAGE_LENGTH = -1;

    private final boolean all;
    private final Set<Repository> alternates;
    private final boolean branches;
    private final Set<String> excludes;
    private final boolean followRenames;
    private final boolean ignoringMissing;
    private final Set<String> includes;
    private final int limit;
    private final int maxMessageLength;
    private final GitRevListMerges merges;
    private final GitRevListOrder order;
    private final Set<String> paths;
    private final Repository repository;
    private final Instant since;
    private final int skip;
    private final boolean tags;
    private final GitRevListWalk walk;
    private final boolean withMessages;

    protected StreamCommitsParameters(Builder builder) {
        super(builder);

        all = builder.all;
        alternates = builder.alternates.build();
        branches = builder.branches;
        excludes = builder.excludes.build();
        followRenames = builder.followRenames;
        ignoringMissing = builder.ignoringMissing;
        includes = builder.includes.build();
        limit = builder.limit;
        //If builder.withMessages is true, normalize the requested length. Otherwise, ignore it. This means the
        //length can either be 0 because it was set that way explicitly, or because withMessages was false
        maxMessageLength = builder.withMessages ? max(UNLIMITED_MESSAGE_LENGTH, builder.maxMessageLength) : 0;
        merges = builder.merges;
        order = builder.order;
        paths = builder.paths.build();
        repository = builder.repository;
        since = builder.since;
        skip = builder.skip;
        tags = builder.tags;
        walk = builder.walk;
        //Only set withMessages to true if builder.withMessages was true _and_ the max length isn't 0
        withMessages = builder.withMessages && maxMessageLength != 0;

        if (followRenames) {
            if (paths.isEmpty()) {
                throw new IllegalStateException("A path must be supplied when attempting to follow renames");
            }
            if (paths.size() > 1) {
                throw new IllegalStateException("Following renames is only possible with a single path");
            }
        }
    }

    public Set<Repository> getAlternates() {
        return alternates;
    }

    public Set<String> getExcludes() {
        return excludes;
    }

    public Set<String> getIncludes() {
        return includes;
    }

    public int getLimit() {
        return limit;
    }

    public int getMaxMessageLength() {
        return maxMessageLength;
    }

    public GitRevListMerges getMerges() {
        return merges;
    }

    public GitRevListOrder getOrder() {
        return order;
    }

    public Set<String> getPaths() {
        return paths;
    }

    public Repository getRepository() {
        return repository;
    }

    public Instant getSince() {
        return since;
    }

    public int getSkip() {
        return skip;
    }

    public GitRevListWalk getWalk() {
        return walk;
    }

    public boolean isAll() {
        return all;
    }

    public boolean isBranches() {
        return branches;
    }

    public boolean isFollowingRenames() {
        return followRenames;
    }

    public boolean isIgnoringMissing() {
        return ignoringMissing;
    }

    public boolean isTags() {
        return tags;
    }

    public boolean isWithMessages() {
        return withMessages;
    }

    public static class Builder extends AbstractBuilder<Builder, StreamCommitsParameters> {

        private final ImmutableSet.Builder<Repository> alternates;
        private final ImmutableSet.Builder<String> excludes;
        private final ImmutableSet.Builder<String> includes;
        private final ImmutableSet.Builder<String> paths;
        private final Repository repository;

        private boolean all;
        private boolean branches;
        private boolean followRenames;
        private boolean ignoringMissing;
        private int limit;
        private int maxMessageLength;
        private GitRevListMerges merges;
        private GitRevListOrder order;
        private Instant since;
        private int skip;
        private boolean tags;
        private GitRevListWalk walk;
        private boolean withMessages;

        public Builder(@Nonnull PullRequest pullRequest) {
            this(requireNonNull(pullRequest, "pullRequest").getToRef().getRepository());

            PullRequestRef fromRef = pullRequest.getFromRef();
            PullRequestRef toRef = pullRequest.getToRef();

            if (pullRequest.isCrossRepository()) {
                alternates.add(fromRef.getRepository());
            }
            excludes.add(toRef.getLatestCommit());
            includes.add(fromRef.getLatestCommit());
        }

        public Builder(@Nonnull Repository repository) {
            this.repository = requireNonNull(repository, "repository");

            alternates = ImmutableSet.builder();
            excludes = ImmutableSet.builder();
            includes = ImmutableSet.builder();
            maxMessageLength = UNLIMITED_MESSAGE_LENGTH;
            merges = GitRevListMerges.INCLUDE;
            order = GitRevListOrder.DEFAULT;
            paths = ImmutableSet.builder();
            walk = GitRevListWalk.DO_WALK;
            withMessages = true;
        }

        @Nonnull
        public Builder all(boolean value) {
            all = value;

            return self();
        }

        @Nonnull
        public Builder alternate(@Nullable Repository value) {
            addIf(Objects::nonNull, alternates, value);

            return self();
        }

        @Nonnull
        public Builder alternates(@Nullable Iterable<Repository> value) {
            addIf(Objects::nonNull, alternates, value);

            return self();
        }

        @Nonnull
        public Builder branches(boolean value) {
            branches = value;

            return self();
        }

        @Nonnull
        @Override
        public StreamCommitsParameters build() {
            return new StreamCommitsParameters(this);
        }

        @Nonnull
        public Builder exclude(@Nullable String value) {
            addIf(NOT_BLANK, excludes, value);

            return self();
        }

        @Nonnull
        public Builder excludes(@Nullable Iterable<String> values) {
            addIf(NOT_BLANK, excludes, values);

            return self();
        }

        @Nonnull
        public Builder followRenames(boolean value) {
            followRenames = value;

            return self();
        }

        @Nonnull
        public Builder ignoreMissing(boolean value) {
            ignoringMissing = value;

            return self();
        }

        @Nonnull
        public Builder include(@Nullable String value) {
            addIf(NOT_BLANK, includes, value);

            return self();
        }

        @Nonnull
        public Builder includes(@Nullable Iterable<String> values) {
            addIf(NOT_BLANK, includes, values);

            return self();
        }

        @Nonnull
        public Builder limit(int value) {
            limit = value;

            return self();
        }

        @Nonnull
        public Builder maxMessageLength(int value) {
            maxMessageLength = value;

            return self();
        }

        @Nonnull
        public Builder merges(@Nonnull GitRevListMerges value) {
            merges = requireNonNull(value, "merges");

            return self();
        }

        @Nonnull
        public Builder order(@Nonnull GitRevListOrder value) {
            order = requireNonNull(value, "order");

            return self();
        }

        @Nonnull
        public Builder path(@Nullable String value) {
            addIf(NOT_BLANK, paths, value);

            return self();
        }

        @Nonnull
        public Builder paths(@Nullable Iterable<String> values) {
            addIf(NOT_BLANK, paths, values);

            return self();
        }

        @Nonnull
        public Builder since(@Nullable Instant value) {
            since = value;

            return self();
        }

        @Nonnull
        public Builder skip(int value) {
            skip = value;

            return self();
        }

        @Nonnull
        public Builder tags(boolean value) {
            tags = value;

            return self();
        }

        @Nonnull
        public Builder walk(@Nonnull GitRevListWalk value) {
            walk = requireNonNull(value, "walk");

            return self();
        }

        @Nonnull
        public Builder withMessages(boolean value) {
            withMessages = value;

            return self();
        }

        @Nonnull
        @Override
        protected Builder self() {
            return this;
        }
    }
}
