package com.atlassian.bitbucket.internal.branch.model.configuration;

import com.atlassian.bitbucket.internal.branch.model.BranchTypes;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Set;

import static com.atlassian.bitbucket.internal.branch.model.BranchTypes.DEFAULT_ORDER;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;

public class SimpleBranchModelConfiguration implements BranchModelConfiguration {

    private final BranchConfiguration development;
    private final BranchConfiguration production;
    private final Set<BranchTypeConfiguration> types;

    protected SimpleBranchModelConfiguration(@Nonnull BranchConfiguration development,
                                             @Nullable BranchConfiguration production,
                                             @Nonnull Set<BranchTypeConfiguration> types) {
        this.development = checkNotNull(development, "development");
        this.production = production;
        this.types = ImmutableSet.copyOf(DEFAULT_ORDER.immutableSortedCopy(types));
    }

    protected SimpleBranchModelConfiguration(@Nonnull AbstractBuilder<?> builder) {
        this(builder.development, builder.production, ImmutableSet.copyOf(builder.types));
    }

    @Nonnull
    @Override
    public BranchConfiguration getDevelopment() {
        return development;
    }

    @Nullable
    @Override
    public BranchConfiguration getProduction() {
        return production;
    }

    @Nonnull
    @Override
    public Set<BranchTypeConfiguration> getTypes() {
        return types;
    }

    public static class Builder extends AbstractBuilder<Builder> {

        public Builder() {
        }

        public Builder(BranchModelConfiguration original) {
            super(original);
        }

        public SimpleBranchModelConfiguration build() {
            return new SimpleBranchModelConfiguration(this);
        }

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

    protected abstract static class AbstractBuilder<B extends AbstractBuilder<B>> {

        protected BranchConfiguration development;
        protected BranchConfiguration production;
        protected final Set<BranchTypeConfiguration> types = Sets.newTreeSet(DEFAULT_ORDER);

        protected AbstractBuilder() {
        }

        protected AbstractBuilder(BranchModelConfiguration original) {
            this.development = original.getDevelopment();
            this.production = original.getProduction();
            this.types.addAll(original.getTypes());
        }

        @Nonnull
        public B development(@Nonnull BranchConfiguration development) {
            this.development = checkNotNull(development, "development");
            return self();
        }

        @Nonnull
        public B production(@Nullable BranchConfiguration production) {
            this.production = production;
            return self();
        }

        @Nonnull
        public B types(@Nonnull Iterable<BranchTypeConfiguration> values) {
            checkNotNull(values, "value");
            for (BranchTypeConfiguration type : values) {
                type(type);
            }
            return self();
        }

        @Nonnull
        public B types(@Nonnull BranchTypeConfiguration first, @Nonnull BranchTypeConfiguration... rest) {
            checkNotNull(first, "first");
            checkNotNull(rest, "rest");
            types(singleton(first));
            return types(asList(rest));
        }


        @Nonnull
        public B type(@Nonnull BranchTypeConfiguration value) {
            checkNotNull(value, "value");
            if (types.contains(value)) {
                types.remove(value);
            }
            types.add(value);
            return self();
        }

        @Nonnull
        public B type(@Nonnull String id, @Nullable String prefix, boolean enabled) {
            return type(new SimpleBranchTypeConfiguration(BranchTypes.forId(id), enabled, prefix));
        }

        @Nonnull
        public B enabledType(@Nonnull String id, @Nonnull String prefix) {
            return type(id, prefix, true);
        }

        protected abstract B self();
    }
}
