package com.atlassian.bitbucket.mesh.process.handler;

import com.atlassian.bitbucket.mesh.process.OsProcess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
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.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;

public abstract class AbstractLoggingStdioHandler<T> extends AbstractProcessStdioHandler<T> {

    private final WritableByteChannel channel;
    private final ByteArrayOutputStream output;

    private Logger log;

    public AbstractLoggingStdioHandler() {
        output = new ByteArrayOutputStream();
        channel = Channels.newChannel(output);
    }

    public AbstractLoggingStdioHandler(@Nonnull Logger log) {
        this();

        this.log = requireNonNull(log, "log");
    }

    /**
     * Prepares the output buffer.
     * <p>
     * <b>Note</b>: This override allows output logging to be reused across multiple <i>sequential</i> commands
     * (use across <i>concurrent</i> commands is not possible). Subclasses may add their own state on top of the
     * output logging, however, and it is up to such subclasses to manage their own state to allow for reuse.
     *
     * @param process the process about to be started
     */
    @Override
    public void onPreStart(@Nonnull OsProcess process) {
        super.onPreStart(process);

        output.reset();
        if (log == null) {
            log = LoggerFactory.getLogger(process.getClass());
        }
    }

    @Override
    public void onStdout(@Nonnull ByteBuffer buffer, boolean closed) {
        try {
            channel.write(buffer);
        } catch (IOException e) {
            //The WritableByteChannel we're using can't actually throw this, but the compiler doesn't know that
            throw new UncheckedIOException(e);
        }

        //When stdout is closed, log the output. This way we can get all the output together
        if (closed) {
            logOutput();
        }
    }

    private void logOutput() {
        if (output.size() == 0) {
            log.trace("{}: Completed with no output", process);
        } else if (log.isDebugEnabled()) {
            log.debug("{}: Completed with the following output:\n{}",
                    process, new String(output.toByteArray(), UTF_8));
        }
    }
}
