package com.atlassian.bitbucket.commit;

import com.google.common.collect.Lists;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;

/**
 * Utility {@link CommitCallback} for batching calls to {@link #onCommit(Commit)}, which may be useful for enriching
 * {@link Commit commits}, or performing other operations which may be done more efficiently in bulk.
 */
public class BatchingCommitCallback implements CommitCallback {

    private final int batchSize;
    private final List<Commit> batch;
    private final CommitCallback delegate;

    public BatchingCommitCallback(CommitCallback delegate, int batchSize) {
        this.batchSize = batchSize;
        this.delegate = delegate;

        batch = Lists.newArrayListWithCapacity(batchSize);
    }

    /**
     * Adds the provided {@link Commit commit} to the pending batch, which is flushed to {@link #onCommits} if it
     * has reached or exceeded the size provided during construction.
     *
     * @param commit the commit to add to the batch
     * @throws IOException propagated if thrown by the delegate
     */
    @Override
    public boolean onCommit(@Nonnull Commit commit) throws IOException {
        batch.add(commit);

        return flush(batch.size() >= batchSize);
    }

    /**
     * Flushes any batched {@link Commit commits} to {@link #onCommits}.
     *
     * @param summary a summary describing the commits that have been streamed
     * @throws IOException propagated if thrown by the delegate
     */
    @Override
    public void onEnd(@Nonnull CommitSummary summary) throws IOException {
        flush(true);

        delegate.onEnd(summary);
    }

    /**
     * @param context a context describing the commits requested
     * @throws IOException propagated if thrown by the delegate
     */
    @Override
    public void onStart(@Nonnull CommitContext context) throws IOException {
        delegate.onStart(context);
    }

    /**
     * Passes each batched {@link Commit commit} to the delegate's {@link #onCommit} method.
     *
     * @param commits to be passed back to the delegate callback
     * @return {@code true} if more commits should be provided; otherwise, {@code false} to end streaming
     * @throws IOException propagated if thrown by the delegate
     */
    protected boolean onCommits(@Nonnull Iterable<Commit> commits) throws IOException {
        for (Commit commit : commits) {
            if (!delegate.onCommit(commit)) {
                return false;
            }
        }
        return true;
    }

    private boolean flush(boolean now) throws IOException {
        if (now) {
            try {
                return onCommits(batch);
            } finally {
                batch.clear();
            }
        }

        return true;
    }
}
