package org.jf.dexlib2.writer.io;

import android.support.annotation.NonNull;

import com.google.common.collect.Lists;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
 * A deferred output stream that is stored in memory
 */
public class MemoryDeferredOutputStream extends DeferredOutputStream {
    private static final int DEFAULT_BUFFER_SIZE = 16 * 1024;

    private final List<byte[]> buffers = Lists.newArrayList();
    private byte[] currentBuffer;
    private int currentPosition;

    public MemoryDeferredOutputStream() {
        this(DEFAULT_BUFFER_SIZE);
    }

    public MemoryDeferredOutputStream(int bufferSize) {
        currentBuffer = new byte[bufferSize];
    }

    @Override
    public void writeTo(OutputStream output) throws IOException {
        for (byte[] buffer : buffers) {
            output.write(buffer);
        }
        if (currentPosition > 0) {
            output.write(currentBuffer, 0, currentPosition);
        }
        buffers.clear();
        currentPosition = 0;
    }

    @Override
    public void write(int i) throws IOException {
        if (remaining() == 0) {
            buffers.add(currentBuffer);
            currentBuffer = new byte[currentBuffer.length];
            currentPosition = 0;
        }
        currentBuffer[currentPosition++] = (byte) i;
    }

    @Override
    public void write(byte[] bytes) throws IOException {
        write(bytes, 0, bytes.length);
    }

    @Override
    public void write(byte[] bytes, int offset, int length) throws IOException {
        int remaining = remaining();
        int written = 0;
        while (length - written > 0) {
            int toWrite = Math.min(remaining, (length - written));
            System.arraycopy(bytes, offset + written, currentBuffer, currentPosition, toWrite);
            written += toWrite;
            currentPosition += toWrite;

            remaining = remaining();
            if (remaining == 0) {
                buffers.add(currentBuffer);
                currentBuffer = new byte[currentBuffer.length];
                currentPosition = 0;
                remaining = currentBuffer.length;
            }
        }
    }

    private int remaining() {
        return currentBuffer.length - currentPosition;
    }

    @NonNull
    public static DeferredOutputStreamFactory getFactory() {
        return getFactory(DEFAULT_BUFFER_SIZE);
    }

    @NonNull
    public static DeferredOutputStreamFactory getFactory(final int bufferSize) {
        return new DeferredOutputStreamFactory() {
            @Override
            public DeferredOutputStream makeDeferredOutputStream() {
                return new MemoryDeferredOutputStream(bufferSize);
            }
        };
    }
}
