package com.atlassian.bitbucket.internal.mirroring.mirror.sync;

import com.atlassian.bitbucket.internal.mirroring.mirror.ExternalProject;
import com.atlassian.bitbucket.internal.mirroring.mirror.ExternalRepository;
import com.atlassian.bitbucket.mirroring.mirror.SyncLevel;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServer;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.util.MoreCollectors;
import com.atlassian.bitbucket.util.TimerUtils;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nonnull;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;

import static com.atlassian.bitbucket.internal.mirroring.mirror.MirrorDescriptionUtils.describe;

/**
 * A context to track the working set of projects and repositories loaded or created during a sync operation
 */
public class SyncContext {
    private final ImmutableSet.Builder<RepositoryFetchTask> fetchTasks;
    private final Map<String, Project> projects;
    private final Map<String, Repository> repositories;
    private final SyncLevel syncLevel;
    private final UpstreamServer upstream;

    public SyncContext(UpstreamServer upstream, SyncLevel syncLevel) {
        this.upstream = upstream;
        this.syncLevel = syncLevel;

        fetchTasks = ImmutableSet.builder();
        projects = new LinkedHashMap<>();
        repositories = new LinkedHashMap<>();
    }

    public void fetch(RepositoryFetchTask fetchTask) {
        fetchTasks.add(fetchTask);
    }

    @Nonnull
    public Iterable<RepositoryFetchTask> getFetchTasks() {
        return fetchTasks.build();
    }

    /**
     * @return the set of projects created or loaded (in creation/load order) for this sync operation
     */
    public Iterable<Project> getProjects() {
        return projects.values().stream()
                .collect(MoreCollectors.toImmutableList());
    }

    /**
     * @return the set of repositories created or loaded (in creation/load order) for this sync operation
     */
    public Iterable<Repository> getRepositories() {
        return repositories.values().stream()
                .collect(MoreCollectors.toImmutableList());
    }

    public SyncLevel getSyncLevel() {
        return syncLevel;
    }

    public UpstreamServer getUpstream() {
        return upstream;
    }

    public Project syncOnce(ExternalProject externalProject, BiFunction<ExternalProject, SyncContext, Project> projectSync) {
        return projects.computeIfAbsent(externalProject.getId(), key ->
                TimerUtils.time("Syncing project " + externalProject.getKey(), () -> projectSync.apply(externalProject, this)));
    }

    public Repository syncOnce(ExternalRepository externalRepository, BiFunction<ExternalRepository, SyncContext, Repository> repoSync) {
        return repositories.computeIfAbsent(externalRepository.getId(), key ->
                TimerUtils.time("Syncing repository " + describe(externalRepository), () -> repoSync.apply(externalRepository, this)));
    }
}
