package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.io.TypeAwareOutputSupplier;
import com.atlassian.bitbucket.mesh.process.ProcessStdioHandler;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;

import static java.util.Objects.requireNonNull;

public class SupplierStdoutHandler implements ProcessStdioHandler<Void> {

    private final String contentType;
    private final TypeAwareOutputSupplier outputSupplier;

    private WritableByteChannel channel;

    public SupplierStdoutHandler(@Nonnull TypeAwareOutputSupplier outputSupplier) {
        this(outputSupplier, "text/plain; charset=UTF-8");
    }

    public SupplierStdoutHandler(@Nonnull TypeAwareOutputSupplier outputSupplier, @Nonnull String contentType) {
        this.contentType = requireNonNull(contentType, "contentType");
        this.outputSupplier = requireNonNull(outputSupplier, "outputSupplier");
    }

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

    @Override
    public void onExit(int exitCode) {
        WritableByteChannel channel = this.channel;
        if (channel != null && channel.isOpen()) {
            try {
                channel.close();
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to close output channel", e);
            }
        }
    }

    @Override
    public boolean onStdinReady(@Nonnull ByteBuffer buffer) {
        return false;
    }

    @Override
    public void onStdout(@Nonnull ByteBuffer buffer, boolean closed) {
        try {
            if (channel == null) {
                if (closed && !buffer.hasRemaining()) {
                    //If the first onStdout callback has no data and stdout is already closed, it most
                    //likely means the command has failed (probably due to an invalid commit).
                    return;
                }

                channel = Channels.newChannel(outputSupplier.getStream(contentType));
            }

            if (buffer.hasRemaining()) {
                channel.write(buffer);
            }

            if (closed) {
                //Try to eagerly close the output channel as soon as stdout is closed. This doesn't
                //need to be in a finally block because onExit will ensure the channel is closed
                channel.close();
                channel = null;
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Failed to copy output", e);
        }
    }
}
