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

import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.git.ref.Ref;
import com.atlassian.bitbucket.mesh.git.ref.Tag;
import com.atlassian.bitbucket.mesh.process.handler.AbstractLineStdioHandler;
import com.atlassian.bitbucket.mesh.util.SatiableConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.nio.charset.StandardCharsets;

import static com.atlassian.bitbucket.mesh.git.GitAgent.HEAD;
import static com.atlassian.bitbucket.mesh.git.GitRefPattern.TAGS;
import static org.apache.commons.lang3.StringUtils.isBlank;

/**
 * @since 1.3
 */
public class LsRemoteStdioHandler extends AbstractLineStdioHandler<Void> {

    protected static final Logger log = LoggerFactory.getLogger(LsRemoteStdioHandler.class);

    private final String defaultBranchId;
    private final SatiableConsumer<Ref> refConsumer;

    private String objectId;
    private String peeledObjectId;
    private String refId;

    public LsRemoteStdioHandler(SatiableConsumer<Ref> refConsumer, String defaultBranchId) {
        super(StandardCharsets.UTF_8);

        this.defaultBranchId = defaultBranchId;
        this.refConsumer = refConsumer;
    }

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

    /**
     * @param line the line that could not be parsed
     * @return {@code true} if we should continue processing stdout
     */
    protected boolean onInvalidLine(String line) {
        log.warn("Ignoring invalid line: '{}'", line);
        return true;
    }

    @Override
    protected boolean onStdout(@Nonnull String line, boolean truncated) {
        if (line.length() <= 40) {
            return onInvalidLine(line);
        }
        int hashEnd = line.indexOf('\t');
        if (hashEnd == -1) {
            return onInvalidLine(line);
        }
        String lineObjectId = line.substring(0, hashEnd);
        if (line.length() <= hashEnd + 1) {
            return onInvalidLine(line);
        }
        String lineRefId = line.substring(hashEnd + 1);
        boolean peeledLine = lineRefId.endsWith("^{}");
        if (peeledLine) {
            //peeled hash
            peeledObjectId = lineObjectId;
        }
        if (emitRef()) {
            //consumer is done
            return false;
        }
        if (!peeledLine) {
            refId = lineRefId;
            objectId = lineObjectId;
        }
        return true;
    }

    @Override
    protected void onStdoutClosed() {
        //Emit the final ref if needed
        emitRef();
    }

    /**
     * @return {@code true} if the consumer is satiated and no more refs need to be processed
     */
    private boolean emitRef() {
        try {
            if (isBlank(refId) || HEAD.equals(refId)) {
                //Don't emit pseudo-ref HEAD
                return false;
            }

            Ref ref;
            if (refId.startsWith(TAGS.getPath())) {
                Tag.Builder builder = new Tag.Builder()
                        .id(refId);
                if (peeledObjectId != null) {
                    //Annotated tag
                    builder.hash(objectId).latestCommit(peeledObjectId).build();
                } else {
                    //Lightweight tag
                    builder.latestCommit(objectId).build();
                }
                ref = builder.build();
            } else {
                ref = new Branch.Builder()
                        .id(refId)
                        .latestCommit(objectId)
                        .isDefault(refId.equals(defaultBranchId))
                        .build();
            }
            return refConsumer.accept(ref);
        } finally {
            refId = objectId = peeledObjectId = null;
        }
    }
}
