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

import com.atlassian.bitbucket.mesh.git.GitUtils;
import com.atlassian.bitbucket.mesh.git.content.Change;
import com.atlassian.bitbucket.mesh.git.content.ChangeCallback;

import javax.annotation.Nonnull;

/**
 * Streams {@link Change changes} to a {@link ChangeCallback} until no more changes are available on {@code stdout}
 * or the callback signals completion, whichever happens first.
 * <p>
 * Note: This output handler does <i>not</i> handle merge commit output. It is expected that this handler will not be
 * used with {@code git diff-tree -m}. See {@link #onStdout(String, boolean)} for more details.
 */
public class CallbackChangeDiffTreeStdioHandler extends AbstractDiffTreeStdioHandler<Void> {

    private final ChangeCallback callback;
    private final int maxChanges;

    private int added;

    public CallbackChangeDiffTreeStdioHandler(ChangeCallback callback, int maxChanges) {
        this.callback = callback;
        this.maxChanges = maxChanges;
    }

    /**
     * All output is streamed to the callback, so this handler produces no object output.
     *
     * @return {@code null}
     */
    @Override
    public Void getResult() {
        return null;
    }

    /**
     * This output handler does not accept merge commits, because in a merge commit it is not possible, from the output,
     * to determine which parent the tree is relative to. The presence of a commit hash in the output after any changes
     * is assumed to indicate the presence of a merge commit (in which case the code will bail out).
     */
    @Override
    protected boolean onStdout(@Nonnull String line, boolean truncated) {
        if (line.isEmpty()) {
            //git diff-tree outputs a blank line between the commit hash and the diff tree, which we ignore
            return true;
        }
        if (GitUtils.isHash(line)) {
            if (added > 0) {
                //After streaming any changes, if we find another commit hash it usually means we've received
                //a merge commit, and diff-tree is now outputting the tree relative to another parent. This
                //shouldn't happen, as the command should not be run with merge commits enabled, so bail out now
                return false;
            }

            //First line of output _may_ be a commit hash; diff-tree's output is inconsistent about this
            return true;
        }
        if (isFiltered(line)) {
            return true;
        }

        if (added++ > maxChanges) {
            //We've parsed the maximum amount of changes we're allowed to
            return false;
        }

        callback.onChange(parseChange(line));
        return true;
    }
}
