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

import com.atlassian.bitbucket.mesh.annotation.Profiled;
import com.atlassian.bitbucket.mesh.git.GitNodeType;
import com.atlassian.bitbucket.mesh.git.GitObjectInfo;
import com.atlassian.bitbucket.mesh.git.builder.GitProcess;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.builder.catfile.InfoBatchCheckCatFilesStdioHandler;
import com.atlassian.bitbucket.mesh.git.builder.catfile.TypeCatFileStdioHandler;
import com.atlassian.bitbucket.mesh.git.builder.lstree.*;
import com.atlassian.bitbucket.mesh.git.exception.CommitNotFoundException;
import com.atlassian.bitbucket.mesh.git.exception.PathNotFoundException;
import com.atlassian.bitbucket.mesh.git.submodule.SubmoduleManager;
import com.atlassian.bitbucket.mesh.git.submodule.Submodules;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;

import java.util.List;
import java.util.Map;

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.isEmpty;

@Profiled
public class DefaultContentManager implements ContentManager {

    private final GitProcessBuilderFactory builderFactory;
    private final SubmoduleManager submoduleManager;

    public DefaultContentManager(GitProcessBuilderFactory builderFactory, SubmoduleManager submoduleManager) {
        this.builderFactory = builderFactory;
        this.submoduleManager = submoduleManager;
    }

    @Nonnull
    @Override
    public GitNodeType getNodeType(@Nonnull GetNodeTypeParameters parameters) {
        Repository repository = requireNonNull(parameters, "parameters").getRepository();
        String commitId = parameters.getCommitId();
        String path = StringUtils.stripEnd(parameters.getPath(), "/");

        GitProcess<GitNodeType> process;
        if (isEmpty(path) || path.indexOf(':') != -1) {
            //- If there's no path, we _almost_ know what the type is going to be (DIRECTORY), but it's also
            //  possible the commit isn't valid. Forking git cat-file -t ensures that's not the case
            //- If the path, or any subdirectory in the path, starts with a :, since git ls-tree treats its
            //  arguments as _patterns_, not simply paths, even if the path is "correct", the _pattern_ won't
            //  match. Using git cat-file -t will work correctly (unless the path is also a submodule!)
            process = builderFactory.builder(repository).catFile()
                    .type()
                    .object(commitId, path)
                    .build(new TypeCatFileStdioHandler());
        } else {
            //git cat-file -t doesn't support submodules, so if a path is provided we use git ls-tree instead. It
            //requires a slightly different approach to setting up the command, though, because we don't want to
            //try and list the full path; we want to list the containing directory and filter to a specific file
            int split = path.lastIndexOf('/');

            String directory;
            String file;
            if (split == -1) {
                directory = "";
                file = path;
            } else {
                directory = path.substring(0, split);
                file = path.substring(split + 1); //Drop the /
            }

            process = builderFactory.builder(repository).lsTree()
                    .nullTerminated(true)
                    .path(file)
                    .tree(commitId, directory)
                    .build(new TypeLsTreeStdioHandler(commitId, path));
        }

        return parameters.configureAndRun(process);
    }

    @Nonnull
    @Override
    public GitObjectInfo getObjectInfo(@Nonnull GetObjectInfoParameters parameters) {
        Repository repository = requireNonNull(parameters, "parameters").getRepository();
        String commitId = parameters.getCommitId();
        String path = parameters.getPath();
        String commitIdAtPath = commitId + ":" + path;
        List<String> inputs = path == null ?
                ImmutableList.of(commitId) :
                ImmutableList.of(commitId, commitIdAtPath);

        Map<String, GitObjectInfo> objects = parameters.configureAndRun(builderFactory.builder(repository).catFile()
                .batchCheck()
                .build(new InfoBatchCheckCatFilesStdioHandler(inputs)));
        if (objects.isEmpty()) {
            throw new CommitNotFoundException(commitId);
        }
        if (objects.size() == 1 && path != null) {
            throw new PathNotFoundException(commitId, path);
        }

        return objects.get(objects.size() == 1 ? commitId : commitIdAtPath);
    }

    @Override
    public void listDirectory(@Nonnull ListDirectoryParameters parameters, @Nonnull DirectoryCallback callback) {
        requireNonNull(parameters, "parameters");
        requireNonNull(callback, "callback");

        Repository repository = parameters.getRepository();
        Submodules submodules = submoduleManager.get(repository, parameters.getCommitId(), parameters.getPath());

        DirectoryCreator directoryCreator = parameters.isRecursive() ?
                new DefaultDirectoryCreator() : //Recursive ls-tree collapses directories implicitly
                new CollapsingDirectoryCreator(builderFactory, repository);
        parameters.configureAndRun(builderFactory.builder(repository).lsTree()
                .nullTerminated(true)
                .recursive(parameters.isRecursive())
                .sizes(parameters.isWithSizes())
                .tree(parameters.getCommitId(), parameters.getPath())
                .build(new DirectoryLsTreeStdioHandler(callback, directoryCreator,
                        parameters.getPageRange(), submodules)));
    }
}
