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

import com.atlassian.stash.internal.scm.git.command.status.CollectingStatusCallback;
import com.atlassian.stash.internal.scm.git.command.status.StatusOutputHandler;
import com.atlassian.bitbucket.io.SingleLineOutputHandler;
import com.atlassian.bitbucket.scm.CommandErrorHandler;
import com.atlassian.bitbucket.scm.CommandInputHandler;
import com.atlassian.bitbucket.scm.CommandOutputHandler;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import com.atlassian.bitbucket.util.PageRequestImpl;
import com.atlassian.utils.process.LineOutputHandler;
import com.atlassian.utils.process.ProcessException;
import com.atlassian.utils.process.Watchdog;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Helper to test Git command builder classes such as {@link GitCommandBuilderFactory}.
 * <p>
 * <i>Do not add methods to this class which duplicates functionality in {@link GitCommandFactory} or
 * {@link GitCommandBuilderFactory}.</i>
 */
public final class GitFactoriesTestHelper {

    public static File createEmptyRepository(File directory, GitCommandBuilderFactory factory) throws IOException {
        factory.builder().init().directory(directory.getAbsolutePath()).bare(false).build().call();
        return directory;
    }

    public static List<GitStatus> getStatus(File directory, Iterable<String> paths, GitCommandBuilderFactory factory) {
        CollectingStatusCallback callback = new CollectingStatusCallback();
        StatusOutputHandler handler = new StatusOutputHandler(new PageRequestImpl(0, 100), callback);
        factory.builder().workingDirectory(directory).status().porcelain(true).untracked("all").paths(paths).build(handler).call();
        return callback.getStatuses();
    }

    public static Queue<String> getReflog(File directory, int depth, String ref, GitCommandBuilderFactory factory) {
        QueueHandler handler = new QueueHandler();
        return factory.builder().workingDirectory(directory).command("reflog").argument("-" + depth).argument(ref).build(handler).call();
    }

    public static String getTrackedRemote(File directory, String branchName, GitCommandBuilderFactory factory) {
        SingleLineOutputHandler handler = new SingleLineOutputHandler();
        return factory.builder().workingDirectory(directory).command("config").argument("--local").argument("--get").argument("branch." + branchName + ".remote").build(handler).call();
    }

    public static void addRemote(File directory, File remoteDirectory, String remoteName, GitCommandBuilderFactory factory) {
        factory.builder().workingDirectory(directory).remote().add(remoteName, remoteDirectory).build().call();
        NoOpHandler handler = new NoOpHandler();
        factory.builder().workingDirectory(directory).command("fetch").argument(remoteName).build(handler).call();
    }

    public static String getOrigin(File directory, GitCommandBuilderFactory factory) {
        SingleLineOutputHandler handler = new SingleLineOutputHandler();
        return factory.builder().workingDirectory(directory).command("config").argument("remote.origin.url").build(handler).call();
    }

    public static int countLooseObjects(File directory, GitCommandBuilderFactory factory) {
        String output = factory.builder()
                .workingDirectory(directory)
                .command("count-objects")
                .build(new SingleLineOutputHandler())
                .call();
        if (output == null) {
            return 0;
        }

        Matcher matcher = Pattern.compile("(\\d+) objects").matcher(output);
        if (matcher.find()) {
            return Integer.parseInt(matcher.group(1));
        }
        throw new IllegalStateException("Unrecognised output: " + output);
    }

    public static int countPackFiles(File directory) {
        File packDir = isBareRepository(directory) ?
                new File(directory, "objects/pack") :
                new File(directory, ".git/objects/pack");
        File[] packs = packDir.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File parentDir, String name) {
                //Only count pack files. Dividing by 2 is not a safe mechanism here because newer versions of
                //Git can also write bitmaps for their packs (for example), not just indexes. The presence of
                //such extra files can't be guaranteed and would skew a lazy count
                return name.endsWith(".pack");
            }
        });

        return packs == null ? 0 : packs.length;
    }

    // gitAgent doesn't support non-bare repositories
    public static String getHead(File directory) {
        try {
            return StringUtils.removeStart(
                    isBareRepository(directory) ?
                            Files.readFirstLine(new File(directory, "HEAD"), Charsets.UTF_8) :
                            Files.readFirstLine(new File(directory, ".git/HEAD"), Charsets.UTF_8),
                    "ref: ");
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    public static boolean isBareRepository(File directory) {
        return !new File(directory, ".git").isDirectory();
    }

    public static boolean isSharedRepository(File directory) {
        return (isBareRepository(directory) ?
                new File(directory, "objects/info/alternates") :
                new File(directory, ".git/objects/info/alternates")).isFile();
    }

    public static boolean hasCheckout(File directory) {
        File[] files = directory.listFiles();
        return !isBareRepository(directory) && files != null && files.length > 1;
    }

    public static File createTempFile(String prefix, File directory, String content) throws IOException {
        File f = File.createTempFile(prefix, null, directory);
        Files.write(content, f, Charsets.UTF_8);
        f.deleteOnExit();
        return f;
    }

    //TODO: Remove this; SingleLineOutputHandler is the same thing
    public static class StringHandler extends SingleLineOutputHandler implements CommandErrorHandler {
    }

    public static class QueueHandler extends LineOutputHandler implements CommandOutputHandler<Queue<String>> {

        private final Queue<String> queue = new LinkedList<>();

        public QueueHandler() {
            super(Charsets.UTF_8.name());
        }

        @Override
        public Queue<String> getOutput() {
            return queue;
        }

        @Override
        protected void processLine(int lineNum, String line) {
            queue.add(line);
        }
    }

    public static class NoOpHandler implements CommandOutputHandler<Void> {

        @Override
        public Void getOutput() {
            return null;
        }

        @Override
        public void process(InputStream output) throws ProcessException {
        }

        @Override
        public void complete() throws ProcessException {
        }

        @Override
        public void setWatchdog(Watchdog watchdog) {
        }
    }

    public static class StringInputHandler extends com.atlassian.utils.process.StringInputHandler implements CommandInputHandler {

        public StringInputHandler(String content) {
            super(Charsets.UTF_8.name(), content);
        }
    }
}
