public class BZLib {

    public static int decompress(byte out[], int outSize, byte in[], int inSize, int offset) {
        BZState block = new BZState();
        block.input = in;
        block.nextIn = offset;
        block.output = out;
        block.availOut = 0;
        block.availIn = inSize;
        block.decompressedSize = outSize;
        block.bsLive = 0;
        block.bsBuff = 0;
        block.totalInLo32 = 0;
        block.totalInHi32 = 0;
        block.totalOutLo32 = 0;
        block.totalOutHi32 = 0;
        block.blockNo = 0;
        decompress(block);
        outSize -= block.decompressedSize;
        return outSize;
    }

    private static void nextHeader(BZState state) {
        byte cStateOutCh = state.stateOutCh;
        int cStateOutLen = state.stateOutLen;
        int cNblockUsed = state.nblockUsed;
        int cK0 = state.k0;
        int cTt[] = state.tt;
        int cTpos = state.tpos;
        byte output[] = state.output;
        int csNextOut = state.availOut;
        int csAvailOut = state.decompressedSize;
        int asdasdasd = csAvailOut;
        int sSaveNblockPP = state.saveNblock + 1;
        returnNotr:
        do {
            if (cStateOutLen > 0) {
                do {
                    if (csAvailOut == 0)
                        break returnNotr;
                    if (cStateOutLen == 1)
                        break;
                    output[csNextOut] = cStateOutCh;
                    cStateOutLen--;
                    csNextOut++;
                    csAvailOut--;
                } while (true);
                if (csAvailOut == 0) {
                    cStateOutLen = 1;
                    break;
                }
                output[csNextOut] = cStateOutCh;
                csNextOut++;
                csAvailOut--;
            }
            boolean flag = true;
            while (flag) {
                flag = false;
                if (cNblockUsed == sSaveNblockPP) {
                    cStateOutLen = 0;
                    break returnNotr;
                }
                cStateOutCh = (byte) cK0;
                cTpos = cTt[cTpos];
                byte k1 = (byte) (cTpos & 0xff);
                cTpos >>= 8;
                cNblockUsed++;
                if (k1 != cK0) {
                    cK0 = k1;
                    if (csAvailOut == 0) {
                        cStateOutLen = 1;
                    } else {
                        output[csNextOut] = cStateOutCh;
                        csNextOut++;
                        csAvailOut--;
                        flag = true;
                        continue;
                    }
                    break returnNotr;
                }
                if (cNblockUsed != sSaveNblockPP)
                    continue;
                if (csAvailOut == 0) {
                    cStateOutLen = 1;
                    break returnNotr;
                }
                output[csNextOut] = cStateOutCh;
                csNextOut++;
                csAvailOut--;
                flag = true;
            }
            cStateOutLen = 2;
            cTpos = cTt[cTpos];
            byte k2 = (byte) (cTpos & 0xff);
            cTpos >>= 8;
            if (++cNblockUsed != sSaveNblockPP)
                if (k2 != cK0) {
                    cK0 = k2;
                } else {
                    cStateOutLen = 3;
                    cTpos = cTt[cTpos];
                    byte k3 = (byte) (cTpos & 0xff);
                    cTpos >>= 8;
                    if (++cNblockUsed != sSaveNblockPP)
                        if (k3 != cK0) {
                            cK0 = k3;
                        } else {
                            cTpos = cTt[cTpos];
                            byte byte3 = (byte) (cTpos & 0xff);
                            cTpos >>= 8;
                            cNblockUsed++;
                            cStateOutLen = (byte3 & 0xff) + 4;
                            cTpos = cTt[cTpos];
                            cK0 = (byte) (cTpos & 0xff);
                            cTpos >>= 8;
                            cNblockUsed++;
                        }
                }
        } while (true);
        int i2 = state.totalOutLo32;
        state.totalOutLo32 += asdasdasd - csAvailOut;
        if (state.totalOutLo32 < i2)
            state.totalOutHi32++;
        state.stateOutCh = cStateOutCh;
        state.stateOutLen = cStateOutLen;
        state.nblockUsed = cNblockUsed;
        state.k0 = cK0;
        state.tt = cTt;
        state.tpos = cTpos;
        state.output = output;
        state.availOut = csNextOut;
        state.decompressedSize = csAvailOut;
    }

    private static void decompress(BZState state) {
        /*boolean flag = false;
        boolean flag1 = false;
        boolean flag2 = false;
        boolean flag3 = false;
        boolean flag4 = false;
        boolean flag5 = false;
        boolean flag6 = false;
        boolean flag7 = false;
        boolean flag8 = false;
        boolean flag9 = false;
        boolean flag10 = false;
        boolean flag11 = false;
        boolean flag12 = false;
        boolean flag13 = false;
        boolean flag14 = false;
        boolean flag15 = false;
        boolean flag16 = false;
        boolean flag17 = false;
        boolean flag18 = false;*/
        int gMinLen = 0;
        int gLimit[] = null;
        int gBase[] = null;
        int gPerm[] = null;
        state.blocksize100k = 1;
        if (state.tt == null)
            state.tt = new int[state.blocksize100k * 100000];
        boolean goingandshit = true;
        while (goingandshit) {
            byte uc = getUchar(state);
            if (uc == 23)
                return;
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getUchar(state);
            state.blockNo++;
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getUchar(state);
            uc = getBit(state);
            state.blockRandomised = uc != 0;
            if (state.blockRandomised)
                System.out.println("PANIC! RANDOMISED BLOCK!");
            state.origPtr = 0;
            uc = getUchar(state);
            state.origPtr = state.origPtr << 8 | uc & 0xff;
            uc = getUchar(state);
            state.origPtr = state.origPtr << 8 | uc & 0xff;
            uc = getUchar(state);
            state.origPtr = state.origPtr << 8 | uc & 0xff;
            for (int i = 0; i < 16; i++) {
                uc = getBit(state);
                state.inUse_16[i] = uc == 1;
            }

            for (int i = 0; i < 256; i++)
                state.inUse[i] = false;

            for (int i = 0; i < 16; i++)
                if (state.inUse_16[i]) {
                    for (int j = 0; j < 16; j++) {
                        uc = getBit(state);
                        if (uc == 1)
                            state.inUse[i * 16 + j] = true;
                    }

                }

            makeMaps(state);
            int alphaSize = state.nInUse + 2;
            int nGroups = getBits(3, state);
            int nSelectors = getBits(15, state);
            for (int i = 0; i < nSelectors; i++) {
                int j = 0;
                do {
                    uc = getBit(state);
                    if (uc == 0)
                        break;
                    j++;
                } while (true);
                state.selectorMtf[i] = (byte) j;
            }

            byte pos[] = new byte[6];
            for (byte v = 0; v < nGroups; v++)
                pos[v] = v;

            for (int i = 0; i < nSelectors; i++) {
                byte v = state.selectorMtf[i];
                byte tmp = pos[v];
                for (; v > 0; v--)
                    pos[v] = pos[v - 1];

                pos[0] = tmp;
                state.selector[i] = tmp;
            }

            for (int t = 0; t < nGroups; t++) {
                int curr = getBits(5, state);
                for (int i = 0; i < alphaSize; i++) {
                    do {
                        uc = getBit(state);
                        if (uc == 0)
                            break;
                        uc = getBit(state);
                        if (uc == 0)
                            curr++;
                        else
                            curr--;
                    } while (true);
                    state.len[t][i] = (byte) curr;
                }

            }

            for (int t = 0; t < nGroups; t++) {
                byte minLen = 32;
                int maxLen = 0;
                for (int l1 = 0; l1 < alphaSize; l1++) {
                    if (state.len[t][l1] > maxLen)
                        maxLen = state.len[t][l1];
                    if (state.len[t][l1] < minLen)
                        minLen = state.len[t][l1];
                }

                createDecodeTables(state.limit[t], state.base[t], state.perm[t], state.len[t], minLen, maxLen, alphaSize);
                state.minLens[t] = minLen;
            }

            int eob = state.nInUse + 1;
            int nblockMax = 100000 * state.blocksize100k;
            int groupNo = -1;
            int groupPos = 0;
            for (int i = 0; i <= 255; i++)
                state.unzftab[i] = 0;

            int kk = 4095; // MTFASIZE-1;
            for (int ii = 15; ii >= 0; ii--) {
                for (int jj = 15; jj >= 0; jj--) {
                    state.mtfa[kk] = (byte) (ii * 16 + jj);
                    kk--;
                }

                state.mtfbase[ii] = kk + 1;
            }

            int nblock = 0;
            // GETMTFVAL
            if (groupPos == 0) {
                groupNo++;
                groupPos = 50; // BZGSIZE
                byte gSel = state.selector[groupNo];
                gMinLen = state.minLens[gSel];
                gLimit = state.limit[gSel];
                gPerm = state.perm[gSel];
                gBase = state.base[gSel];
            }
            groupPos--;
            int zn = gMinLen;
            int zvec;
            byte zj;
            for (zvec = getBits(zn, state); zvec > gLimit[zn]; zvec = zvec << 1 | zj) {
                zn++;
                zj = getBit(state);
            }

            for (int nextSym = gPerm[zvec - gBase[zn]]; nextSym != eob; )
                if (nextSym == 0 || nextSym == 1) { // BZRUNA, BZRUNB
                    int es = -1;
                    int N = 1;
                    do {
                        if (nextSym == 0)
                            es += N;
                        else if (nextSym == 1)
                            es += 2 * N;
                        N *= 2;
                        // GETMTFVAL, y da fuk did they not subroutine this
                        if (groupPos == 0) {
                            groupNo++;
                            groupPos = 50;
                            byte gSel = state.selector[groupNo];
                            gMinLen = state.minLens[gSel];
                            gLimit = state.limit[gSel];
                            gPerm = state.perm[gSel];
                            gBase = state.base[gSel];
                        }
                        groupPos--;
                        int zn_2 = gMinLen;
                        int zvec_2;
                        byte zj_2;
                        for (zvec_2 = getBits(zn_2, state); zvec_2 > gLimit[zn_2]; zvec_2 = zvec_2 << 1 | zj_2) {
                            zn_2++;
                            zj_2 = getBit(state);
                        }

                        nextSym = gPerm[zvec_2 - gBase[zn_2]];
                    } while (nextSym == 0 || nextSym == 1);
                    es++;
                    uc = state.setToUnseq[state.mtfa[state.mtfbase[0]] & 0xff];
                    state.unzftab[uc & 0xff] += es;
                    for (; es > 0; es--) {
                        state.tt[nblock] = uc & 0xff;
                        nblock++;
                    }

                } else {
                    int nn = nextSym - 1;
                    if (nn < 16) { // MTFLSIZE
                        int pp = state.mtfbase[0];
                        uc = state.mtfa[pp + nn];
                        for (; nn > 3; nn -= 4) {
                            int z = pp + nn;
                            state.mtfa[z] = state.mtfa[z - 1];
                            state.mtfa[z - 1] = state.mtfa[z - 2];
                            state.mtfa[z - 2] = state.mtfa[z - 3];
                            state.mtfa[z - 3] = state.mtfa[z - 4];
                        }

                        for (; nn > 0; nn--)
                            state.mtfa[pp + nn] = state.mtfa[(pp + nn) - 1];

                        state.mtfa[pp] = uc;
                    } else {
                        int lno = nn / 16;
                        int off = nn % 16;
                        int pp = state.mtfbase[lno] + off;
                        uc = state.mtfa[pp];
                        for (; pp > state.mtfbase[lno]; pp--)
                            state.mtfa[pp] = state.mtfa[pp - 1];

                        state.mtfbase[lno]++;
                        for (; lno > 0; lno--) {
                            state.mtfbase[lno]--;
                            state.mtfa[state.mtfbase[lno]] = state.mtfa[(state.mtfbase[lno - 1] + 16) - 1];
                        }

                        state.mtfbase[0]--;
                        state.mtfa[state.mtfbase[0]] = uc;
                        if (state.mtfbase[0] == 0) {
                            kk = 4095; // MTFASIZE - 1
                            for (int ii = 15; ii >= 0; ii--) {
                                for (int jj = 15; jj >= 0; jj--) {
                                    state.mtfa[kk] = state.mtfa[state.mtfbase[ii] + jj];
                                    kk--;
                                }

                                state.mtfbase[ii] = kk + 1;
                            }

                        }
                    }
                    state.unzftab[state.setToUnseq[uc & 0xff] & 0xff]++;
                    state.tt[nblock] = state.setToUnseq[uc & 0xff] & 0xff;
                    nblock++;
                    // GETMTFVAL here we go AGAIN
                    if (groupPos == 0) {
                        groupNo++;
                        groupPos = 50;
                        byte gSel = state.selector[groupNo];
                        gMinLen = state.minLens[gSel];
                        gLimit = state.limit[gSel];
                        gPerm = state.perm[gSel];
                        gBase = state.base[gSel];
                    }
                    groupPos--;
                    int zn_2 = gMinLen;
                    int zvec_2;
                    byte zj_2;
                    for (zvec_2 = getBits(zn_2, state); zvec_2 > gLimit[zn_2]; zvec_2 = zvec_2 << 1 | zj_2) {
                        zn_2++;
                        zj_2 = getBit(state);
                    }

                    nextSym = gPerm[zvec_2 - gBase[zn_2]];
                }

            state.stateOutLen = 0;
            state.stateOutCh = 0;
            state.cftab[0] = 0;
            for (int i = 1; i <= 256; i++)
                state.cftab[i] = state.unzftab[i - 1];

            for (int i = 1; i <= 256; i++)
                state.cftab[i] += state.cftab[i - 1];

            for (int i = 0; i < nblock; i++) {
                uc = (byte) (state.tt[i] & 0xff);
                state.tt[state.cftab[uc & 0xff]] |= i << 8;
                state.cftab[uc & 0xff]++;
            }

            state.tpos = state.tt[state.origPtr] >> 8;
            state.nblockUsed = 0;
            state.tpos = state.tt[state.tpos];
            state.k0 = (byte) (state.tpos & 0xff);
            state.tpos >>= 8;
            state.nblockUsed++;
            state.saveNblock = nblock;
            nextHeader(state);
            goingandshit = state.nblockUsed == state.saveNblock + 1 && state.stateOutLen == 0;
        }
    }

    private static byte getUchar(BZState state) {
        return (byte) getBits(8, state);
    }

    private static byte getBit(BZState state) {
        return (byte) getBits(1, state);
    }

    private static int getBits(int i, BZState state) {
        int vvv;
        do {
            if (state.bsLive >= i) {
                int v = state.bsBuff >> state.bsLive - i & (1 << i) - 1;
                state.bsLive -= i;
                vvv = v;
                break;
            }
            state.bsBuff = state.bsBuff << 8 | state.input[state.nextIn] & 0xff;
            state.bsLive += 8;
            state.nextIn++;
            state.availIn--;
            state.totalInLo32++;
            if (state.totalInLo32 == 0)
                state.totalInHi32++;
        } while (true);
        return vvv;
    }

    private static void makeMaps(BZState state) {
        state.nInUse = 0;
        for (int i = 0; i < 256; i++)
            if (state.inUse[i]) {
                state.setToUnseq[state.nInUse] = (byte) i;
                state.nInUse++;
            }

    }

    private static void createDecodeTables(int limit[], int base[], int perm[], byte length[], int minLen, int maxLen, int alphaSize) {
        int pp = 0;
        for (int i = minLen; i <= maxLen; i++) {
            for (int j = 0; j < alphaSize; j++)
                if (length[j] == i) {
                    perm[pp] = j;
                    pp++;
                }

        }

        for (int i = 0; i < 23; i++)
            base[i] = 0;

        for (int i = 0; i < alphaSize; i++)
            base[length[i] + 1]++;

        for (int i = 1; i < 23; i++)
            base[i] += base[i - 1];

        for (int i = 0; i < 23; i++)
            limit[i] = 0;

        int vec = 0;
        for (int i = minLen; i <= maxLen; i++) {
            vec += base[i + 1] - base[i];
            limit[i] = vec - 1;
            vec <<= 1;
        }

        for (int i = minLen + 1; i <= maxLen; i++)
            base[i] = (limit[i - 1] + 1 << 1) - base[i];

    }
}
