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

import com.atlassian.bitbucket.mesh.git.exception.InvalidEditFileException;
import com.atlassian.bitbucket.mesh.git.file.EditFileMetadata;
import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.io.EncodingUtils;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcInvalidEditFile;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.util.Iterator;

/**
 * Parses {@link EditFileMetadata} for a specified file and, if the branch has been updated, verifies that the file
 * to edit has not changed between the current tip commit and the previous commit.
 */
public class EditFileMetadataBatchCatFileStdioHandler extends AbstractBatchCatFileStdioHandler<EditFileMetadata> {

    private final int bufferSize;
    private final Iterator<String> nextObjectId;

    private String latestObjectId;
    private EditFileMetadata metadata;

    public EditFileMetadataBatchCatFileStdioHandler(int bufferSizeBinary, int bufferSizeEncoding,
                                                    @Nonnull String previousCommit, @Nonnull Branch branch,
                                                    @Nonnull String path) {
        //Buffer up content until we hit either the binary or encoding detection threshold, whichever
        //is larger. (This is intended to reduce false positives from the detection)
        bufferSize = Math.max(bufferSizeBinary, bufferSizeEncoding);
        //If the branch's tip commit hasn't moved, this set will contain exactly 1 object
        nextObjectId = ImmutableSet.of(branch.getLatestCommit() + ':' + path, previousCommit + ":" + path).iterator();
    }

    @Override
    public EditFileMetadata getResult() {
        return metadata;
    }

    @Override
    protected boolean onContent(@Nonnull ByteBuffer content, boolean closed) {
        //If this is the full content for the object, or if we have enough content to check, build
        //the metadata
        if (closed || content.remaining() >= bufferSize) {
            EditFileMetadata.Builder metadataBuilder = new EditFileMetadata.Builder();
            if (EncodingUtils.isBinary(content)) {
                metadataBuilder.binary(true);
            } else {
                metadataBuilder.encoding(EncodingUtils.detectEncoding(content));
            }
            metadata = metadataBuilder.build();

            //No need to stream any more bytes from this object
            return false;
        }

        //Otherwise, wait for more data
        return true;
    }

    @Override
    protected void onMissing(@Nonnull String objectId) {
        if (latestObjectId == null) {
            //This means the path doesn't exist in the branch's tip commit
            throw new InvalidEditFileException(RpcInvalidEditFile.Reason.EDIT_FILE_DELETED);
        }

        //This means the path didn't exist in the previous commit
        throw new InvalidEditFileException(RpcInvalidEditFile.Reason.EDIT_FILE_NONEXISTENT);
    }

    @Override
    protected boolean onObject(@Nonnull BatchHeader header) {
        if (latestObjectId == null) {
            if (nextObjectId.hasNext()) {
                //If the branch's tip commit has changed, record the tip commit's content ID and then
                //request the same file at the previous commit
                latestObjectId = header.getObjectId();

                //Ignore the current file's content; we only care about it if the file hasn't changed
                return false;
            }

            //If the branch's tip commit hasn't changed there's no reason to request it again; stream
            //the file's content and extract metadata
            return true;
        }

        if (latestObjectId.equals(header.getObjectId())) {
            //The file's content is the same between the two commits, so stream it and extract metadata
            return true;
        }

        //Otherwise, the file has changed between the commit we're editing it from and the commit
        //at the tip of the branch
        throw new InvalidEditFileException(RpcInvalidEditFile.Reason.EDIT_FILE_STALE);
    }

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