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

import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Header in the output of a {@code git cat-file --batch} or {@code git cat-file --batch-check} command.
 */
public class BatchHeader {

    public static final Pattern FOUND_PATTERN = Pattern.compile("([0-9a-f]{40,}) (.*) (\\d+)$");
    public static final int MAX_SIZE_IN_BYTES = 1024;
    public static final Pattern MISSING_PATTERN = Pattern.compile("(.+) missing$");

    private final String objectId;
    private final String type;
    private final long size;

    private BatchHeader(String objectId, String type, long size) {
        this.objectId = objectId;
        this.type = type;
        this.size = size;
    }

    /**
     * Parses a batch header from the provided buffer. The <i>entire buffer</i> is consumed to produce the header,
     * so if there are bytes that should not be included the caller should use a {@link ByteBuffer#slice slice} of
     * the buffer that is limited to the desired subset of bytes.
     *
     * @param buffer the buffer to parse to build the batch header
     * @return the parsed batch header
     * @throws IllegalStateException if the buffer doesn't contain a valid batch header
     */
    @Nonnull
    public static BatchHeader parse(@Nonnull ByteBuffer buffer) {
        return parse(StandardCharsets.UTF_8.decode(buffer).toString());
    }

    /**
     * Parses a batch header from the provided string. The <i>entire string</i> is used to produce the header, so
     * if there are bytes that should not be included the caller should use a {@link String#substring substring}
     * that is limited to the desired subset of bytes.
     *
     * @param value the value to parse to build the batch header
     * @return the parsed batch header
     * @throws IllegalStateException if the string doesn't contain a valid batch header
     */
    @Nonnull
    public static BatchHeader parse(@Nonnull String value) {
        return parseOutputLine(value);
    }

    /**
     * Parses a batch header from the provided array. The <i>entire array</i> is used to produce the header, so
     * if there are bytes that should not be included the caller should either create a separate, smaller array
     * and copy over only the desired subset of bytes, or wrap the array in a {@link ByteBuffer} with the desired
     * length and parse that instead.
     *
     * @param bytes the array to parse to build the batch header
     * @return the parsed batch header
     * @throws IllegalStateException if the array doesn't contain a valid batch header
     */
    @Nonnull
    public static BatchHeader parse(byte[] bytes) {
        return parse(new String(bytes, StandardCharsets.UTF_8));
    }

    @Nonnull
    public String getObjectId() {
        return objectId;
    }

    @Nonnull
    public String getType() {
        return type;
    }

    public long getSize() {
        return size;
    }

    public boolean isMissing() {
        return "missing".equals(type);
    }

    // Output for found objects looks like this:
    //   <objectname> SP <objecttype> SP <objectsize> LF
    // For missing objects:
    //   <objectname> SP missing LF
    private static BatchHeader parseOutputLine(String value) {
        Matcher matcher = FOUND_PATTERN.matcher(value);
        if (matcher.matches()) {
            String object = matcher.group(1);
            String type = matcher.group(2);
            String sizeString = matcher.group(3);

            try {
                long size = Long.parseLong(sizeString);
                if (size < 0) {
                    throw new IllegalStateException("The size cannot be negative");
                }
                return new BatchHeader(object, type, size);
            } catch (NumberFormatException e) {
                throw new IllegalStateException("The size field did not contain a valid number");
            }
        }
        matcher = MISSING_PATTERN.matcher(value);
        if (matcher.matches()) {
            String object = matcher.group(1);
            return new BatchHeader(object, "missing", 0);
        }
        throw new IllegalStateException("Invalid format: " + value);
    }
}