package com.atlassian.bitbucket.internal.scm.git.lfs.rest.storage;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Representation of the Range HTTP header as defined in RFC7233. This is heavily simplified to support a specific
 * usecase and can be replaced with com.springframework.http.HttpRange when plugins can use Spring 4.2 or later.
 */
public class HttpRange {

    private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=(?<start>\\d*)-(?<end>\\d*)");

    private final long end;
    private final long start;

    public HttpRange(long start, long end) {
        this.start = start;
        this.end = end;
    }

    /**
     * Parses a HTTP Range parameter as described in RFC7233. This implementation does not support all of
     * RFC7233. It does not support multiple ranges, nor does it support ranges with units other than bytes.
     * If the range passed is not valid or not supported then {@link Optional#empty()} is returned. The intention
     * being, the caller just treats that situation as if no range was passed and instead of returning a HTTP
     * status 206 partial content response, just returns a HTTP status 200 and streams the entire response;
     * essentially the same behaviour as if HTTP Range is not supported.
     *
     * @return a HttpRange if the provided "range" string could be parsed as a valid range,
     * otherwise {@link Optional#empty()}
     */
    @Nonnull
    public static Optional<HttpRange> parseRange(@Nullable String range) {
        if (range != null) {
            Matcher matcher = RANGE_PATTERN.matcher(range);
            if (matcher.matches() && matcher.groupCount() == 2) {
                try {
                    long start = Long.parseLong(matcher.group("start"));
                    long end = Long.parseLong(matcher.group("end"));
                    return Optional.of(new HttpRange(start, end));
                } catch (NumberFormatException e) {
                    return Optional.empty();
                }
            }
        }
        return Optional.empty();
    }

    public long getRangeStart() {
        return start;
    }

    public long getRangeEnd() {
        return end;
    }

    /**
     * This method is not just used as a convenience for logging, etc; the format returned is used to
     * populate the HTTP Range header entry in the Response.
     *
     * @return a string representation of the range
     */
    @Override
    public String toString() {
        return String.format("%s-%s", start, end);
    }
}
