package com.atlassian.stash.internal.scm.git.command.revlist;

import com.atlassian.bitbucket.scm.CommandInputHandler;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevCoreBuilder;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevListMerges;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevListOrder;
import com.atlassian.bitbucket.scm.git.command.revlist.GitRevListWalk;
import com.atlassian.stash.internal.scm.git.command.AbstractGitCommandBuilder;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;

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

/**
 * @since 4.5
 */
public abstract class AbstractGitRevCoreBuilder<B extends GitRevCoreBuilder<B>>
        extends AbstractGitCommandBuilder<B>
        implements GitRevCoreBuilder<B> {

    protected final List<String> revs;

    protected boolean all;
    protected boolean boundary;
    protected boolean branches;
    protected boolean ignoreMissing;
    protected boolean stdIn;
    protected boolean tags;

    private final List<String> files;

    private String format;
    private LeftOrRight leftOrRight;
    private boolean leftRight;
    private int limit;
    private GitRevListOrder order;
    private GitRevListMerges merges;
    private boolean parents;
    private GitRevListWalk walk;

    protected AbstractGitRevCoreBuilder(GitScmCommandBuilder builder) {
        super(builder);

        files = new ArrayList<>();
        leftOrRight = LeftOrRight.DEFAULT;
        order = GitRevListOrder.DEFAULT;
        merges = GitRevListMerges.DEFAULT;
        revs = new ArrayList<>();
        walk = GitRevListWalk.DEFAULT;
    }

    @Nonnull
    @Override
    public B all(boolean value) {
        all = value;

        return self();
    }

    @Nonnull
    @Override
    public B boundary(boolean value) {
        boundary = value;

        return self();
    }

    @Nonnull
    @Override
    public B branches(boolean value) {
        branches = value;

        return self();
    }

    @Nonnull
    @Override
    public B clearFiles() {
        files.clear();

        return self();
    }

    @Nonnull
    @Override
    public B clearRevs() {
        revs.clear();

        return self();
    }

    @Nonnull
    @Override
    public B file(String value) {
        addIfNotBlank(files, value);

        return self();
    }

    @Nonnull
    @Override
    public B files(Iterable<String> values) {
        addIfNotBlank(files, values);

        return self();
    }

    @Nonnull
    @Override
    public B files(String value, String... values) {
        addIfNotBlank(files, value, values);

        return self();
    }

    @Nonnull
    @Override
    public B format(String value) {
        format = value;

        return self();
    }

    @Nonnull
    @Override
    public B ignoreMissing(boolean value) {
        ignoreMissing = value;

        return self();
    }

    @Nonnull
    @Override
    public B inputHandler(CommandInputHandler inputHandler) {
        if (inputHandler == null) {
            stdIn = false;
            builder.clearInputHandler();
        } else {
            stdIn = true;
            builder.inputHandler(inputHandler);
        }

        return self();
    }

    @Nonnull
    @Override
    public B leftOnly(boolean value) {
        leftOrRight = value ? LeftOrRight.LEFT_ONLY : LeftOrRight.DEFAULT;

        return self();
    }

    @Nonnull
    @Override
    public B leftRight(boolean value) {
        leftRight = value;

        return self();
    }

    @Nonnull
    @Override
    public B limit(int value) {
        limit = value;

        return self();
    }

    @Nonnull
    @Override
    public B merges(@Nonnull GitRevListMerges value) {
        merges = checkNotNull(value, "merges");

        return self();
    }

    @Nonnull
    @Override
    public B order(@Nonnull GitRevListOrder value) {
        order = checkNotNull(value, "order");

        return self();
    }

    @Nonnull
    @Override
    public B parents(boolean value) {
        parents = value;

        return self();
    }

    @Nonnull
    @Override
    public B rev(String value) {
        addIfNotBlank(revs, value);

        return self();
    }

    @Nonnull
    @Override
    public B revs(Iterable<String> values) {
        addIfNotBlank(revs, values);

        return self();
    }

    @Nonnull
    @Override
    public B revs(String value, String... values) {
        addIfNotBlank(revs, value, values);

        return self();
    }

    @Nonnull
    @Override
    public B rightOnly(boolean value) {
        leftOrRight = value ? LeftOrRight.RIGHT_ONLY : LeftOrRight.DEFAULT;

        return self();
    }

    @Nonnull
    @Override
    public B tags(boolean value) {
        tags = value;

        return self();
    }

    @Nonnull
    @Override
    public B walk(@Nonnull GitRevListWalk value) {
        walk = checkNotNull(value, "walk");

        return self();
    }

    @Override
    protected void applyArguments() {
        applyFlags();
        applyRevs();
        applyFiles();
    }

    protected void applyFiles() {
        //Anecdotal testing seems to indicate always passing the hyphens is safe. To simplify the handling for the
        //commit ID and file, we always include them. Even if we pass neither commit ID nor path, git handles
        //the presence of the double-hyphen appropriately.
        builder.argument("--");
        files.forEach(builder::argument);
    }

    protected void applyFlags() {
        if (all) {
            builder.argument("--all");
        }
        if (boundary) {
            builder.argument("--boundary");
        }
        if (branches) {
            builder.argument("--branches");
        }
        if (StringUtils.isNotBlank(format)) {
            builder.argument("--format=" + format);
        }
        if (ignoreMissing) {
            builder.argument("--ignore-missing");
        }
        if (leftRight) {
            builder.argument("--left-right");
        }
        if (limit > 0) {
            builder.argument("-" + limit);
        }
        if (leftOrRight.isFlagged()) {
            builder.argument(leftOrRight.getFlag());
        }
        if (order.isFlagged()) {
            builder.argument(order.getFlag());
        }
        if (merges.isFlagged()) {
            builder.argument(merges.getFlag());
        }
        if (parents) {
            builder.argument("--parents");
        }
        if (stdIn) {
            builder.argument("--stdin");
        }
        if (tags) {
            builder.argument("--tags");
        }
        if (walk.isFlagged()) {
            builder.argument(walk.getFlag());
        }
    }

    protected void applyRevs() {
        revs.forEach(builder::argument);
    }

    private enum LeftOrRight {

        DEFAULT,
        LEFT_ONLY("--left-only"),
        RIGHT_ONLY("--right-only");

        private final String flag;

        LeftOrRight() {
            flag = null;
        }

        LeftOrRight(String flag) {
            this.flag = flag;
        }

        public String getFlag() {
            return flag;
        }

        public boolean isFlagged() {
            return flag != null;
        }
    }
}
