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

import com.atlassian.bitbucket.mesh.util.ByteSegmentScanner;

import javax.annotation.Nonnull;
import java.nio.ByteBuffer;

/**
 * A base class for creating {@code git cat-file --batch-check} handlers.
 *
 * @param <T> the type of result returned by {@link #getResult()}
 */
public abstract class AbstractBatchCheckCatFileStdioHandler<T> extends BatchedCatFileStdioHandlerSupport<T> {

    private final ByteSegmentScanner segmentScanner;

    protected AbstractBatchCheckCatFileStdioHandler() {
        segmentScanner = new ByteSegmentScanner();
    }

    @Override
    public void onStdout(@Nonnull ByteBuffer buffer, boolean closed) {
        while (buffer.hasRemaining()) {
            ByteBuffer headerBytes = segmentScanner.scan(buffer, closed);
            if (headerBytes == null || !headerBytes.hasRemaining()) {
                //Either there were no bytes to process (probably EOS), or the bytes in the buffer
                //didn't have a full header. Either way, nothing to do for this callback
                //
                //Note: length == 0 is unexpected, since git cat-file --batch-check doesn't output
                //      blank separators like git cat-file --batch does, but we optimistically let
                //      processing continue
                return;
            }

            BatchHeader header = BatchHeader.parse(headerBytes);
            if (header.isMissing()) {
                onMissing(header.getObjectId());
            } else {
                onObject(header);
            }

            if (!closed) {
                //If the stream isn't closed, pump the next object ID
                pumpIfRunning();
            }
        }
    }

    /**
     * Receives an object ID which was requested but not found. If missing objects are unexpected, the
     * implementation can throw an exception to abort further processing (or perhaps set some flag that
     * makes the next {@link #getNextObjectId()} call return {@code null}).
     *
     * @param objectId the ID of the missing object
     */
    protected abstract void onMissing(@Nonnull String objectId);

    /**
     * Receives a {@link BatchHeader header} containing size and type details for a new object.
     * <p>
     * <b>Note</b>: This callback will <i>not</i> be invoked for missing objects, so {@link BatchHeader#isMissing}
     * will always be {@code false}. Missing objects are dispatched to {@link #onMissing} instead.
     *
     * @param header a header describing the object's ID, size and type
     */
    protected abstract void onObject(@Nonnull BatchHeader header);
}
