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

import com.atlassian.bitbucket.mesh.repository.Repository;

import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class PeelTagCatFileBatchStdioHandler extends AbstractBatchCatFileStdioHandler<Map<String, String>> {

    private final Repository repository;
    private final Map<String, String> result;
    private final Iterator<String> tagIds;

    private String tagId;

    public PeelTagCatFileBatchStdioHandler(Repository repository, Set<String> tagObjectIds) {
        this.repository = repository;

        result = new HashMap<>();
        tagIds = tagObjectIds.iterator();
    }

    @Override
    public Map<String, String> getResult() {
        return result;
    }

    @Override
    protected String getNextObjectId() {
        return tagIds.hasNext() ? tagIds.next() : null;
    }

    @Override
    protected boolean onContent(@Nonnull ByteBuffer content, boolean closed) {
        // the first line of content should be: object <sha>
        // based on that, we should be able to extract the ID in less than 50 bytes, so we use
        // that as an upper bound on our scanning
        int limit = Math.min(content.remaining(), 50);
        for (int i = 0; i < limit; ++i) {
            // use non-consuming get(int) calls to search for a line feed (this call doesn't
            // advance the buffer's position)
            if (content.get(i) == (byte) '\n') {
                byte[] bytes = new byte[i];
                content.get(bytes);

                String line = new String(bytes, StandardCharsets.UTF_8);
                if (line.startsWith("object ")) {
                    result.put(tagId, line.substring(7));
                } else {
                    log.warn("{} Unexpected content for tag object {}: '{}'", repository, tagId, line);
                }

                return false;
            }
        }

        // if we make it here, we haven't found a line feed
        if (limit < 50) {
            // if the first content buffer was "short" (less than 50 characters), request
            // more content; the next callback will have all the same object content as
            // this one (hopefully) plus more
            return true;
        }

        // if we've checked 50 bytes and didn't find a line feed, assume the content is
        // malformed and give up
        log.warn("{} Missing \"object\" header for tag object {}", repository, tagId);
        return false;
    }

    @Override
    protected void onMissing(@Nonnull String objectId) {
        // treat missing objects as lightweight tags (this feels "wrong", but matches the blocking
        // I/O handling that existed when this was written)
        result.put(objectId, objectId);
    }

    @Override
    protected boolean onObject(@Nonnull BatchHeader header) {
        String objectId = header.getObjectId();
        if ("tag".equals(header.getType())) {
            tagId = objectId;

            // for tag objects, we need to stream the content to find the tagged object's ID
            return true;
        }

        // for any other type, treat it as a lightweight tag
        result.put(objectId, objectId);
        return false;
    }
}
