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

import com.atlassian.bitbucket.mesh.git.GitObjectInfo;
import com.atlassian.bitbucket.mesh.git.GitObjectType;

import javax.annotation.Nonnull;
import java.io.Closeable;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;

public class InteractiveInfoBatchCheckCatFilesStdioHandler
        extends AbstractBatchCheckCatFileStdioHandler<Void> implements Closeable {

    private static final String END_OF_INPUT = "\0\0~~END_OF_INPUT~~\0\0";

    private final BlockingQueue<String> inputs;
    private final BlockingQueue<Optional<GitObjectInfo>> outputs;

    private boolean closed;

    public InteractiveInfoBatchCheckCatFilesStdioHandler() {
        inputs = new SynchronousQueue<>(false);
        outputs = new SynchronousQueue<>(false);
    }

    @Nonnull
    @Override
    public Void getResult() {
        return null;
    }

    @Override
    public void close() {
        if (!closed && process != null && process.isRunning()) {
            try {
                inputs.put(END_OF_INPUT);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("Interrupted while waiting for handler to close", e);
            }
            closed = true;
        }
    }

    @Nonnull
    public Optional<GitObjectInfo> getInfo(@Nonnull String objectId) throws InterruptedException {
        requireNonNull(objectId, "objectId");
        checkState(!closed, "Handler has already been closed");

        inputs.put(objectId);
        return outputs.take();
    }

    @Override
    protected String getNextObjectId() {
        try {
            String next = inputs.take();
            if (END_OF_INPUT.equals(next)) {
                return null;
            }
            return next;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("Interrupted while waiting for next input", e);
            return null;
        }
    }

    @Override
    protected void onMissing(@Nonnull String objectId) {
        onResolved(null);
    }

    @Override
    protected void onObject(@Nonnull BatchHeader header) {
        onResolved(new GitObjectInfo(header.getObjectId(), header.getSize(),
                GitObjectType.fromObjectType(header.getType())));
    }

    private void onResolved(GitObjectInfo objectInfo) {
        try {
            outputs.put(ofNullable(objectInfo));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("Interrupted while waiting to post resolved object ({})",
                    objectInfo == null ? "missing" : objectInfo.getType() + " " + objectInfo.getObjectId(), e);
        }
    }
}
