package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.git.exception.LfsByteRangeNotSatisfiableException;
import com.atlassian.bitbucket.mesh.git.exception.LfsStorageException;
import com.atlassian.bitbucket.mesh.io.IoSupplier;
import org.apache.commons.io.input.BoundedInputStream;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;

import static com.google.common.io.Closeables.closeQuietly;
import static java.util.Objects.requireNonNull;

public class LfsInputStreamSupplier implements IoSupplier<InputStream> {

    private final long end;
    private final Path objectPath;
    private final long start;

    public LfsInputStreamSupplier(@Nonnull Path objectPath) {
        this.objectPath = requireNonNull(objectPath);
        start = -1;
        end = -1;
    }

    public LfsInputStreamSupplier(@Nonnull Path objectPath, long start, long end) {
        this.objectPath = requireNonNull(objectPath);

        try {
            if (start < 0 || end < 0 || start > end || start >= Files.size(objectPath)) {
                throw new LfsByteRangeNotSatisfiableException(start, end);
            }
            this.start = start;
            this.end = end;
        } catch (IOException e) {
            throw new LfsStorageException(e);
        }
    }

    @Nonnull
    public InputStream get() throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = Files.newInputStream(objectPath);
            if (start == -1) {
                return inputStream;
            }

            // Handle range request
            long skipped = inputStream.skip(start);
            if (skipped != start) {
                // The range does not overlap the extent of the file. This should have been checked by the ctor,
                // but its seems wise to double check here rather than return a bad range.
                inputStream.close();
                throw new LfsByteRangeNotSatisfiableException(start, end);
            }
            return new BoundedInputStream(inputStream, end - start + 1);
        } catch (Exception e) {
            closeQuietly(inputStream);
            throw e;
        }
    }
}
