package com.atlassian.bitbucket.scm.cache.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Helper class that buffers an {@link InputStream} in-memory. The difference between this version and the standard
 * {@link java.io.BufferedInputStream} is that this version buffers unbounded.
 *
 * TODO: implement upper limit and overflow to disk
 */
public class ResettableInputStream extends InputStream {

    private volatile ByteArrayInputStream bis;
    private volatile ByteArrayOutputStream bos;
    private volatile boolean closed;
    private final InputStream delegate;

    public ResettableInputStream(InputStream delegate) {
        this.delegate = delegate;
    }

    @Override
    public void close() throws IOException {
        closed = true;
        bis = null;
        bos = null;

        delegate.close();
    }

    @Override
    public int available() throws IOException {
        return (bis != null ? bis.available() : 0) + delegate.available();
    }

    public void clearMark() {
        bos = null;
    }

    public void mark() {
        mark(0);
    }

    public synchronized void mark(int ignored) {
        bos = new ByteArrayOutputStream(2048);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public int read() throws IOException {
        int result = -1;
        if (bis != null) {
            result = bis.read();
            if (result == -1) {
                // depleted the buffer
                bis = null;
            }
        }

        if (result == -1) {
            result = delegate.read();
        }
        if (result != -1 && bos != null) {
            bos.write(result);
        }
        return result;
    }

    @Override
    public synchronized void reset() throws IOException {
        if (bos == null) {
            throw new IOException("Cannot call reset without calling mark");
        }
        if (closed) {
            throw new IOException("Cannot call reset on an InputStream after it's been closed");
        }
        bis = new ByteArrayInputStream(bos.toByteArray());
        bos = null;
    }
}
