package com.atlassian.bitbucket.mesh.git.builder.mergetree;

import com.atlassian.bitbucket.mesh.git.merge.GitMergeConflict;
import com.atlassian.bitbucket.mesh.process.handler.AbstractLineStdioHandler;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parses {@link GitMergeConflict conflicts} from the output of {@code git merge-tree}.
 */
public class MergeTreeStdioHandler extends AbstractLineStdioHandler<MergeTreeResult> {

    private static final String CONFLICT = "CONFLICT ";
    private static final Pattern PATTERN_BINARY_FILE = Pattern.compile("warning: Cannot merge binary files: (.+) \\(.+\\)$");

    private final Set<String> binaryPaths;
    private final List<GitMergeConflict> conflicts;

    private GitMergeConflict.Builder conflict;
    private int conflictLineCount;

    public MergeTreeStdioHandler() {
        super(StandardCharsets.UTF_8);

        binaryPaths = new HashSet<>();
        conflicts = new ArrayList<>();
    }

    @Nullable
    @Override
    public MergeTreeResult getResult() {
        maybeAddConflict();
        return new MergeTreeResult(conflicts, binaryPaths);
    }

    @Override
    protected boolean onStdout(@Nonnull String line, boolean truncated) {
        // Since Git 2.36+ warnings for binary files are sent to stdout instead of stderr. Given Mesh only supports
        // using merge-tree if Git 2.38+ is used we don't need to parse stderr looking for these lines
        maybeParseBinaryFile(line);

        // Is it the start of a new conflict?
        if (line.startsWith(CONFLICT)) {
            // Add the current conflict.
            maybeAddConflict();
            // Start the new conflict.
            conflict = new GitMergeConflict.Builder(line);
            return true;
        }

        // Not currently processing a conflict.
        if (conflict == null) {
            return true;
        }

        // End of the current conflict.
        if (conflictLineCount >= 2) {
            maybeAddConflict();
            return true;
        }

        // Append line to the conflict.
        conflict.message(line);
        conflictLineCount++;
        return true;
    }

    private void maybeAddConflict() {
        if (conflict == null) {
            return;
        }

        // Add the current conflict to the list.
        conflicts.add(conflict.build());

        // Reset the conflict.
        conflict = null;
        conflictLineCount = 0;
    }

    private void maybeParseBinaryFile(String line) {
        Matcher binaryMatcher = PATTERN_BINARY_FILE.matcher(line);
        if (binaryMatcher.matches()) {
            binaryPaths.add(binaryMatcher.group(1));
        }
    }
}
