package com.atlassian.bitbucket.mesh.transaction;

import com.atlassian.bitbucket.mesh.execution.ExecutionLocal;
import com.atlassian.bitbucket.mesh.execution.ExecutionManager;
import com.atlassian.bitbucket.mesh.git.ref.RefUpdate;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Function;

import static com.atlassian.bitbucket.mesh.util.MoreCollectors.toImmutableMap;

public class DefaultUpdateRefsContextHolder implements UpdateRefsContextHolder {

    private final ExecutionLocal<SimpleUpdateRefsContext> current;

    public DefaultUpdateRefsContextHolder(ExecutionManager executionManager) {
        current = executionManager.newExecutionLocal(SimpleUpdateRefsContext::close);
    }

    @Override
    public void clear() {
        current.remove();
    }

    @Override
    public UpdateRefsContext get() {
        return current.get();
    }

    @Override
    public void set(@Nonnull Repository repository, @Nonnull Collection<RefUpdate> refUpdates) {
        UpdateRefsContext prev = current.remove();
        current.set(new SimpleUpdateRefsContext(repository, refUpdates));
        if (prev != null) {
            prev.close();
        }
    }

    private class SimpleUpdateRefsContext implements UpdateRefsContext {

        private final Set<String> committedRefUpdates;
        private final Map<String, RefUpdate> refUpdates;
        private final Repository repository;

        SimpleUpdateRefsContext(Repository repository, Collection<RefUpdate> refUpdates) {
            this.repository = repository;
            this.refUpdates = refUpdates.stream()
                    .collect(toImmutableMap(RefUpdate::getRefId, Function.identity()));
            this.committedRefUpdates = new CopyOnWriteArraySet<>();
        }

        @Override
        public void clearCommitted(@Nonnull Collection<String> refIds) {
            committedRefUpdates.removeAll(refIds);
        }

        @Override
        public void close() {
            clear();
        }

        @Nonnull
        @Override
        public Set<String> getCommittedRefUpdates() {
            return ImmutableSet.copyOf(committedRefUpdates);
        }

        @Nonnull
        @Override
        public Map<String, RefUpdate> getRefUpdates() {
            return refUpdates;
        }

        @Nonnull
        @Override
        public Repository getRepository() {
            return repository;
        }

        @Override
        public void markCommitted(@Nonnull Collection<String> refIds) {
            committedRefUpdates.addAll(refIds);
        }
    }
}
