package com.atlassian.bitbucket.internal.branch.automerge;

import com.atlassian.bitbucket.internal.branch.automerge.result.MergeContinue;
import com.atlassian.bitbucket.internal.branch.automerge.result.MergeResult;
import com.atlassian.bitbucket.internal.branch.automerge.result.MergeStopped;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.fugue.Pair;
import com.google.common.collect.ImmutableList;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * Implementation of the auto merge using the configured {@link BranchProcessor}s.
 */
public class AutoMergeProcessor {

    private final List<BranchProcessor> processors;

    /**
     * @param processors handler which takes each merge instruction and returns the appropriate result
     */
    public AutoMergeProcessor(List<BranchProcessor> processors) {
        checkArgument(!(processors == null || processors.isEmpty()), "must have at least one processor");

        // Create a copy of the processors list so we can sort our own (guaranteed mutable) copy
        processors = new ArrayList<>(processors);
        // Sort the processors, to ensure their order is always stable. Spring <list/> _should_ have stable
        // semantics, but there's no harm in being sure
        AnnotationAwareOrderComparator.sort(processors);

        this.processors = ImmutableList.copyOf(processors);
    }

    public List<Pair<MergeInstruction, MergeResult>> autoMerge(Repository repository, List<Branch> path) {
        if (path.size() < 2) {
            // We need to have at least two branches to merge
            return Collections.emptyList();
        }

        List<Pair<MergeInstruction, MergeResult>> results = new ArrayList<>(path.size());

        // `- 1` is intentional here, we want to iterate over adjacent pairs
        for (int i = 0; i < path.size() - 1; i++) {
            Branch source = path.get(i);
            Branch destination = path.get(i + 1);

            MergeInstruction mergeInstruction = new MergeInstruction(repository, source, destination);
            MergeResult result = process(mergeInstruction);
            results.add(Pair.pair(mergeInstruction, result));
            if (result instanceof MergeStopped) {
                // Stop merging now; the last result we return includes the MergeStopped
                break;
            }
        }

        return results;
    }

    private MergeResult process(MergeInstruction mergeInstruction) {
        MergeResult result = null;
        for (BranchProcessor processor : processors) {
            result = processor.process(mergeInstruction);
            // Stop on everything except continue
            if (!(result instanceof MergeContinue)) {
                return result;
            }
        }
        return result;
    }
}
