/* FJBG -- Fast Java Bytecode Generator
 * Copyright 2002-2013 LAMP/EPFL
 * @author  Michel Schinz
 */

package ch.epfl.lamp.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Array of bytes.
 *
 * @author Michel Schinz
 * @version 1.0
 */

public class ByteArray {
    protected final static int BYTE_BLOCK_BITS = 8;
    protected final static int BYTE_BLOCK_SIZE = 1 << BYTE_BLOCK_BITS;
    protected final static int BYTE_BLOCK_MASK = BYTE_BLOCK_SIZE - 1;

    protected byte[][] data = new byte[][] { new byte[BYTE_BLOCK_SIZE] };
    protected int pos = 0;  // The next free position.

    protected boolean frozen = false;

    public ByteArray() { }

    public ByteArray(InputStream stream, int size) throws IOException {
        pos = size;
        for (int i = 0; size > 0; ++i) {
            int sizeToRead = Math.min(BYTE_BLOCK_SIZE, size);
            stream.read(data[i], 0, sizeToRead);

            size -= sizeToRead;
            if (size > 0) addNewBlock();
        }
    }

    public void freeze() { frozen = true; }

    public int nextBytePosition() {
        return pos;
    }

    public int getSize() {
        return pos;
    }

    protected void addNewBlock() {
        int nextBlockPos = pos >>> BYTE_BLOCK_BITS;
        if (nextBlockPos == data.length) {
            byte[][] newData = new byte[data.length * 2][];
            System.arraycopy(data, 0, newData, 0, data.length);
            data = newData;
        }
        assert data[nextBlockPos] == null : pos + " " + nextBlockPos;
        data[nextBlockPos] = new byte[BYTE_BLOCK_SIZE];
    }

    protected void addByte(int b) {
        assert !frozen;

        if ((pos & BYTE_BLOCK_MASK) == 0 && pos > 0)
            addNewBlock();
        int currPos = pos++;
        data[currPos >>> BYTE_BLOCK_BITS][currPos & BYTE_BLOCK_MASK] = (byte)b;
    }

    public void addU1(int i) {
        assert i <= 0xFF : i;
        addByte(i);
    }

    public void addU2(int i) {
        assert i <= 0xFFFF : i;

        addByte(i >>> 8);
        addByte(i & 0xFF);
    }

    public void addU4(int i) {
        addByte(i >>> 24);
        addByte((i >>> 16) & 0xFF);
        addByte((i >>>  8) & 0xFF);
        addByte(i & 0xFF);
    }

    public void putByte(int targetPos, int b) {
        assert !frozen;
        assert targetPos < pos : targetPos + " >= " + pos;

        data[targetPos >>> BYTE_BLOCK_BITS][targetPos & BYTE_BLOCK_MASK] = (byte)b;
    }

    public void putU2(int targetPos, int i) {
        assert i < 0xFFFF : i;
        putByte(targetPos, i >>> 8);
        putByte(targetPos + 1, i & 0xFF);
    }

    public void putU4(int targetPos, int i) {
        putByte(targetPos, i >>> 24);
        putByte(targetPos + 1, (i >>> 16) & 0xFF);
        putByte(targetPos + 2, (i >>>  8) & 0xFF);
        putByte(targetPos + 3, i & 0xFF);
    }

    public int getU1(int sourcePos) {
        assert sourcePos < pos : sourcePos + " >= " + pos;
        return data[sourcePos >>> BYTE_BLOCK_BITS][sourcePos & BYTE_BLOCK_MASK] & 0xFF;
    }

    public int getU2(int sourcePos) {
        return (getU1(sourcePos) << 8) | getU1(sourcePos + 1);
    }

    public int getU4(int sourcePos) {
        return (getU2(sourcePos) << 16) | getU2(sourcePos + 2);
    }

    public int getS1(int sourcePos) {
        assert sourcePos < pos : sourcePos + " >= " + pos;
        return data[sourcePos >>> BYTE_BLOCK_BITS][sourcePos & BYTE_BLOCK_MASK];
    }

    public int getS2(int sourcePos) {
        return (getS1(sourcePos) << 8) | getU1(sourcePos + 1);
    }

    public int getS4(int sourcePos) {
        return (getS2(sourcePos) << 16) | getU2(sourcePos + 2);
    }

    public void writeTo(OutputStream stream) throws IOException {
        if (!frozen) freeze();

        for (int i = 0; i < data.length && data[i] != null; ++i) {
            int len = Math.min(BYTE_BLOCK_SIZE, pos - (i << BYTE_BLOCK_BITS));
            stream.write(data[i], 0, len);
        }
    }
}
