package com.github.blindpirate.gogradle.vcs.git;

import com.github.blindpirate.gogradle.core.cache.GlobalCacheManager;
import com.github.blindpirate.gogradle.core.dependency.GolangDependencySet;
import com.github.blindpirate.gogradle.core.dependency.NotationDependency;
import com.github.blindpirate.gogradle.core.dependency.ResolvedDependency;
import com.github.blindpirate.gogradle.core.dependency.produce.DependencyVisitor;
import com.github.blindpirate.gogradle.core.dependency.resolve.AbstractVcsDependencyManager;
import com.github.blindpirate.gogradle.core.exceptions.DependencyResolutionException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.github.blindpirate.gogradle.util.Cast.cast;
import static com.github.blindpirate.gogradle.util.DateUtils.toMilliseconds;
import static com.github.blindpirate.gogradle.vcs.git.GitNotationDependency.NEWEST_COMMIT;

@Singleton
public class GitDependencyManager extends AbstractVcsDependencyManager<Repository, RevCommit> {

    public static final String DEFAULT_BRANCH = "master";
    private static final Logger LOGGER = Logging.getLogger(GitDependencyManager.class);

    private final GitAccessor gitAccessor;

    private final DependencyVisitor visitor;

    @Inject
    public GitDependencyManager(GlobalCacheManager cacheManager, GitAccessor gitAccessor, DependencyVisitor visitor) {
        super(cacheManager);
        this.gitAccessor = gitAccessor;
        this.visitor = visitor;
    }

    @Override
    protected void doReset(ResolvedDependency dependency, Path globalCachePath) {
        GitResolvedDependency gitResolvedDependency = (GitResolvedDependency) dependency;
        Repository repository = gitAccessor.getRepository(globalCachePath.toFile());

        resetToSpecificCommit(repository, gitResolvedDependency.getVersion());
    }

    @Override
    protected GitResolvedDependency createResolvedDependency(NotationDependency dependency,
                                                             File directory,
                                                             Repository repository,
                                                             RevCommit commit) {

        GitResolvedDependency ret = GitResolvedDependency.builder()
                .withNotationDependency(dependency)
                .withName(dependency.getName())
                .withCommitId(commit.getName())
                .withTag(cast(GitNotationDependency.class, dependency).getTag())
                .withRepoUrl(gitAccessor.getRemoteUrl(repository))
                .withCommitTime(toMilliseconds(commit.getCommitTime()))
                .build();
        GolangDependencySet dependencies = dependency.getStrategy().produce(ret, directory, visitor);
        ret.setDependencies(dependencies);
        return ret;
    }

    @Override
    protected void resetToSpecificVersion(Repository repository, RevCommit commit) {
        resetToSpecificCommit(repository, commit.getName());
    }

    private void resetToSpecificCommit(Repository repository, String commitId) {
        gitAccessor.resetToCommit(repository, commitId);
    }

    @Override
    protected RevCommit determineVersion(Repository repository, NotationDependency dependency) {
        GitNotationDependency gitNotationDependency = (GitNotationDependency) dependency;
        if (gitNotationDependency.getTag() != null) {
            Optional<RevCommit> commit = gitAccessor.findCommitByTag(repository, gitNotationDependency.getTag());
            if (commit.isPresent()) {
                return commit.get();
            }

            commit = gitAccessor.findCommitBySemVersion(repository, gitNotationDependency.getTag());
            if (commit.isPresent()) {
                return commit.get();
            }
        }

        if (isConcreteCommit(gitNotationDependency.getCommit())) {
            Optional<RevCommit> commit = gitAccessor.findCommit(repository, gitNotationDependency.getCommit());
            if (commit.isPresent()) {
                return commit.get();
            } else {
                throw DependencyResolutionException.cannotFindGitCommit(gitNotationDependency);
            }
        }

        // use HEAD of master branch
        // TODO the default branch may not be master
        return gitAccessor.headCommitOfBranch(repository, DEFAULT_BRANCH).get();
    }

    private boolean isConcreteCommit(String commit) {
        return commit != null && !NEWEST_COMMIT.equals(commit);
    }

    @Override
    protected Repository updateRepository(Repository repository, File directory) {
        return gitAccessor.hardResetAndUpdate(repository);
    }

    @Override
    protected Repository initRepository(NotationDependency dependency, File directory) {
        List<String> urls = determineUrls(dependency);
        tryCloneWithUrls(dependency, urls, directory);
        return gitAccessor.getRepository(directory);
    }

    private List<String> determineUrls(NotationDependency dependency) {
        String urlSpecifiedByUser = cast(GitNotationDependency.class, dependency).getUrl();
        if (urlSpecifiedByUser != null) {
            return Arrays.asList(urlSpecifiedByUser);
        } else {
            return Arrays.asList(cast(GitNotationDependency.class, dependency).getUrl());
        }
    }

    private void tryCloneWithUrls(NotationDependency dependency, List<String> urls, File directory) {
        for (int i = 0; i < urls.size(); ++i) {
            String url = urls.get(i);
            try {
                gitAccessor.cloneWithUrl(url, directory);
                return;
            } catch (Throwable e) {
                LOGGER.warn("Clone {} with url {} failed", dependency.getName(), url, e);
                if (i == urls.size() - 1) {
                    throw DependencyResolutionException.cannotCloneRepository(dependency, e);
                }
            }
        }
    }

    @Override
    protected Optional<Repository> repositoryMatch(File repoRootDir, NotationDependency dependency) {
        Repository repository = gitAccessor.getRepository(repoRootDir);
        List<String> urls = determineUrls(dependency);
        Set<String> remoteUrls = gitAccessor.getRemoteUrls(repository);

        if (Collections.disjoint(urls, remoteUrls)) {
            return Optional.empty();
        } else {
            return Optional.of(repository);
        }
    }
}

@@ -331,32 +331,113 @@
 lvedDependency;%0A
+import com.github.blindpirate.gogradle.core.dependency.VendorResolvedDependency;%0A
 import com.githu
@@ -2755,28 +2755,45 @@
 pendency.get
-Name
+Package().getRootPath
 ())%0A        
@@ -3227,26 +3227,663 @@
 s);%0A
-        return ret
+%0A        setVendorUpdateTimeIfNecessary(repository, dependencies);%0A        return ret;%0A    %7D%0A%0A    private void setVendorUpdateTimeIfNecessary(Repository repository, GolangDependencySet dependencies) %7B%0A        dependencies.flatten().stream()%0A                .filter(dependency -%3E dependency instanceof VendorResolvedDependency)%0A                .map(dependency -%3E (VendorResolvedDependency) dependency)%0A                .forEach(dependency -%3E %7B%0A                    String relativePath = dependency.getRelativePathToHost().toString();%0A                    dependency.setUpdateTime(gitAccessor.lastCommitTimeOfPath(repository, relativePath));%0A                %7D)
 ;%0A  
@@ -6342,32 +6342,45 @@
 ss, dependency).
+getPackage().
 getUrl());%0A     

Set vendor dependency commit time after resolving a vcs dependency