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

import com.atlassian.stash.internal.scm.git.command.AbstractGitCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.diff.GitDiffColor;
import com.atlassian.bitbucket.scm.git.command.diff.GitDiffCoreBuilder;
import com.atlassian.bitbucket.scm.git.command.diff.GitDiffRenames;
import com.google.common.collect.Sets;

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

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

public abstract class AbstractGitDiffCoreBuilder<B extends GitDiffCoreBuilder<B>>
        extends AbstractGitCommandBuilder<B>
        implements GitDiffCoreBuilder<B> {

    protected final Set<String> paths;

    protected String ancestor;
    protected String rev;

    private GitDiffColor color;
    private boolean fullIndex;
    private GitDiffRenames renames;
    private Integer threshold;

    public AbstractGitDiffCoreBuilder(GitScmCommandBuilder builder) {
        super(builder);

        color = GitDiffColor.DEFAULT;
        paths = Sets.newHashSet();
        renames = GitDiffRenames.OFF;
    }

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

        return self();
    }

    @Nonnull
    @Override
    public B clearPaths() {
        paths.clear();

        return self();
    }

    @Nonnull
    @Override
    public B color(@Nonnull GitDiffColor value) {
        color = checkNotNull(value, "color");

        return self();
    }

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

        return self();
    }

    @Nonnull
    @Override
    public B path(String value) {
        addIfNotBlank(paths, value);

        return self();
    }

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

        return self();
    }

    @Nonnull
    @Override
    public B paths(Iterable<String> values) {
        if (values != null) {
            addIfNotBlank(paths, values);
        }

        return self();
    }

    @Nonnull
    @Override
    public B renames(@Nonnull GitDiffRenames value) {
        return renames(value, null);
    }

    @Nonnull
    @Override
    public B renames(@Nonnull GitDiffRenames renames, Integer threshold) {
        this.renames = checkNotNull(renames, "renames");
        this.threshold = renames.supportsThreshold() ? checkThreshold(threshold) : null;

        return self();
    }

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

        return self();
    }

    @Override
    protected void applyArguments() {
        //This can't use a simple "isFlagged()" approach because FIND_COPIES_HARDER requires two flags
        if (renames == GitDiffRenames.FIND_COPIES_HARDER) {
            builder.argument(applyThreshold("-C")); //Detect copies
            builder.argument("--find-copies-harder");
        } else if (renames == GitDiffRenames.FIND_COPIES) {
            builder.argument(applyThreshold("-C")); //Detect copies
        } else if (renames == GitDiffRenames.FIND_RENAMES) {
            //Warning: Using -C implies -M. Using both does whichever happens second. So -C -M actually
            //         disables copy detection and only performs rename detection.
            builder.argument(applyThreshold("-M")); //Detect renames
        } else if (renames == GitDiffRenames.OFF) {
            builder.argument("--no-renames");
        }

        if (color.isFlagged()) {
            builder.argument(color.getFlag());
        }
        if (fullIndex) {
            builder.argument("--full-index");
        }
    }

    protected void applyPaths() {
        builder.argument("--");
        for (String path : paths) {
            builder.argument(path);
        }
    }

    private String applyThreshold(String base) {
        return threshold == null ? base : base + threshold;
    }

    private static GitDiffColor checkColor(String color) {
        if (color == null) {
            return GitDiffColor.DEFAULT;
        }
        return GitDiffColor.valueOf(color.toUpperCase(Locale.US));
    }

    private static Integer checkThreshold(Integer threshold) {
        if (threshold == null) {
            return null;
        }

        int value = threshold; //Just unbox once
        //Oddly enough, git appears to be willing to process all the way down to 1. It's hard to be sure; the lowest
        //I've seen it report was a copy on 11% similarity
        checkArgument(value > 0, "The threshold must be a positive integer between 1 and 99");
        //Setting the similarity index to 100 is ignored by git and it applies the default threshold instead, so we
        //limit the similarity here to 99, which git _will_ apply, to prevent unexpected behaviour
        checkArgument(value < 100, "The threshold must be a positive integer between 1 and 99");

        return threshold;
    }
}
