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

import com.atlassian.bitbucket.mesh.git.exception.CommandFailedException;
import com.atlassian.bitbucket.mesh.git.exception.CommandTimeoutException;
import com.atlassian.bitbucket.mesh.git.exception.InvalidPackException;
import com.atlassian.bitbucket.mesh.grpc.StdinObserver;
import com.atlassian.bitbucket.mesh.process.OsProcess;
import com.atlassian.bitbucket.mesh.process.ProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.process.ProcessSummary;
import com.atlassian.bitbucket.mesh.process.handler.AbstractStringStdioHandler;
import com.google.common.base.Throwables;
import com.google.protobuf.ByteString;

import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * {@link com.atlassian.bitbucket.mesh.process.ProcessStdioHandler} that can be used with
 * {@code git index-pack --stdin} where pack content is streamed to stdin through
 * {@link #writeStdin(ByteString, boolean)}.
 */
public class IndexPackStdioHandler extends AbstractStringStdioHandler<Path> implements ProcessCompletionHandler {

    private static final Pattern INDEX_PACK_STDOUT = Pattern.compile("(keep|pack)\\s+([0-9a-f]+)");

    private final AtomicLong bytesReceived;
    private final Path objectDir;
    private final StdinObserver stdinObserver;

    public IndexPackStdioHandler(Path objectDir) {
        this.objectDir = objectDir;

        bytesReceived = new AtomicLong();
        stdinObserver = new StdinObserver();
    }

    @Override
    public boolean closeStdin() {
        return stdinObserver.closeStdin();
    }

    public long getBytesReceived() {
        return bytesReceived.get();
    }

    /**
     * @return the pack file if one exists
     */
    @Override
    public Path getResult() {
        String stdout = getStdout();
        Matcher matcher = INDEX_PACK_STDOUT.matcher(stdout);
        if (matcher.matches()) {
            return objectDir.resolve("pack").resolve("pack-" + matcher.group(2) + ".pack");
        }
        return null;
    }

    @Override
    public void onComplete(@Nonnull ProcessSummary summary) {
        if (summary.isCanceled()) {
            return;
        }

        Throwable thrown = summary.getThrown();
        if (thrown == null) {
            if (summary.isTimedOut()) {
                thrown = new CommandTimeoutException(summary);
            } else if (summary.getExitCode() != 0 || getResult() == null) {
                //index-pack failed, or did not create a pack file in the expected location
                thrown = InvalidPackException.forStderr(summary.getStderr());
            }
        }
        if (thrown != null) {
            Throwables.throwIfUnchecked(thrown);
            throw new CommandFailedException("index-pack failed", thrown, summary);
        }
    }

    @Override
    public void onStart(@Nonnull OsProcess process) {
        super.onStart(process);

        stdinObserver.onStart(process);
    }

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

    public void writeStdin(@Nonnull ByteString stdin, boolean closed) {
        stdinObserver.writeStdin(stdin, closed);
        bytesReceived.addAndGet(stdin.size());
    }
}
