//

package edu.cmu.cs.vbc.prog.zipme;

class InflaterDynHeader {

    private static final int LNUM = 0;

    private static final int DNUM = 1;

    private static final int BLNUM = 2;

    private static final int BLLENS = 3;

    private static final int LENS = 4;

    private static final int REPS = 5;

    private static final int repMin[] = {3, 3, 11};

    private static final int repBits[] = {2, 3, 7};
    private static final int[] BL_ORDER = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
    private byte[] blLens;
    private byte[] litdistLens;
    private InflaterHuffmanTree blTree;
    private int mode;
    private int lnum, dnum, blnum, num;
    private int repSymbol;
    private byte lastLen;
    private int ptr;

    public InflaterDynHeader() {
    }

    public boolean decode(StreamManipulator input) throws DataFormatException {
        decode_loop:
        for (; ; ) {
            switch (mode) {
                case LNUM:
                    lnum = input.peekBits(5);
                    if (lnum < 0)
                        return false;
                    lnum += 257;
                    input.dropBits(5);
                    mode = DNUM;
                case DNUM:
                    dnum = input.peekBits(5);
                    if (dnum < 0)
                        return false;
                    dnum++;
                    input.dropBits(5);
                    num = lnum + dnum;
                    litdistLens = new byte[num];
                    mode = BLNUM;
                case BLNUM:
                    blnum = input.peekBits(4);
                    if (blnum < 0)
                        return false;
                    blnum += 4;
                    input.dropBits(4);
                    blLens = new byte[19];
                    ptr = 0;
                    mode = BLLENS;
                case BLLENS:
                    while (ptr < blnum) {
                        int len = input.peekBits(3);
                        if (len < 0)
                            return false;
                        input.dropBits(3);
                        blLens[BL_ORDER[ptr]] = (byte) len;
                        ptr++;
                    }
                    blTree = new InflaterHuffmanTree(blLens);
                    blLens = null;
                    ptr = 0;
                    mode = LENS;
                case LENS: {
                    int symbol;
                    while (((symbol = blTree.getSymbol(input)) & ~15) == 0) {
                        litdistLens[ptr++] = lastLen = (byte) symbol;
                        if (ptr == num) {
                            return true;
                        }
                    }
                    if (symbol < 0)
                        return false;
                    if (symbol >= 17) {
                        lastLen = 0;
                    } else {
                        if (ptr == 0)
                            throw new DataFormatException();
                    }
                    repSymbol = symbol - 16;
                    mode = REPS;
                }
                case REPS: {
                    int bits = repBits[repSymbol];
                    int count = input.peekBits(bits);
                    if (count < 0)
                        return false;
                    input.dropBits(bits);
                    count += repMin[repSymbol];
                    if (ptr + count > num)
                        throw new DataFormatException();
                    while (count-- > 0)
                        litdistLens[ptr++] = lastLen;
                    if (ptr == num) {
                        return true;
                    }
                }
                mode = LENS;
                continue decode_loop;
            }
        }
    }

    public InflaterHuffmanTree buildLitLenTree() throws DataFormatException {
        byte[] litlenLens = new byte[lnum];
        System.arraycopy(litdistLens, 0, litlenLens, 0, lnum);
        return new InflaterHuffmanTree(litlenLens);
    }

    public InflaterHuffmanTree buildDistTree() throws DataFormatException {
        byte[] distLens = new byte[dnum];
        System.arraycopy(litdistLens, lnum, distLens, 0, dnum);
        return new InflaterHuffmanTree(distLens);
    }

}
