// CHECKSTYLE:OFF
package com.jscape.inet.ssh.protocol.v2.marshaling.algorithms.zlib;

public class Inflate {

    static final private int MAX_WBITS = 15; // 32K LZ77 window

    // preset dictionary flag in zlib header
    static final private int PRESET_DICT = 0x20;

    static final int Z_NO_FLUSH = 0;
    static final int Z_PARTIAL_FLUSH = 1;
    static final int Z_SYNC_FLUSH = 2;
    static final int Z_FULL_FLUSH = 3;
    static final int Z_FINISH = 4;

    static final private int Z_DEFLATED = 8;

    static final private int Z_OK = 0;
    static final private int Z_STREAM_END = 1;
    static final private int Z_NEED_DICT = 2;
    static final private int Z_ERRNO = -1;
    static final private int Z_STREAM_ERROR = -2;
    static final private int Z_DATA_ERROR = -3;
    static final private int Z_MEM_ERROR = -4;
    static final private int Z_BUF_ERROR = -5;
    static final private int Z_VERSION_ERROR = -6;

    static final private int METHOD = 0;   // waiting for method byte
    static final private int FLAG = 1;     // waiting for flag byte
    static final private int DICT4 = 2;    // four dictionary check bytes to go
    static final private int DICT3 = 3;    // three dictionary check bytes to go
    static final private int DICT2 = 4;    // two dictionary check bytes to go
    static final private int DICT1 = 5;    // one dictionary check byte to go
    static final private int DICT0 = 6;    // waiting for inflateSetDictionary
    static final private int BLOCKS = 7;   // decompressing blocks
    static final private int CHECK4 = 8;   // four check bytes to go
    static final private int CHECK3 = 9;   // three check bytes to go
    static final private int CHECK2 = 10;  // two check bytes to go
    static final private int CHECK1 = 11;  // one check byte to go
    static final private int DONE = 12;    // finished check, done
    static final private int BAD = 13;     // got an error--stay here

    static final private int HEAD = 14;
    static final private int LENGTH = 15;
    static final private int TIME = 16;
    static final private int OS = 17;
    static final private int EXLEN = 18;
    static final private int EXTRA = 19;
    static final private int NAME = 20;
    static final private int COMMENT = 21;
    static final private int HCRC = 22;
    static final private int FLAGS = 23;

    static final int INFLATE_ANY = 0x40000000;

    int mode;                            // current inflate mode

    // mode dependent information
    int method;        // if FLAGS, method byte

    // if CHECK, check values to compare
    long was = -1;           // computed check value
    long need;               // stream check value

    // if BAD, inflateSync's marker bytes count
    int marker;

    // mode independent information
    int wrap;          // flag for no wrapper
    // 0: no wrapper
    // 1: zlib header
    // 2: gzip header
    // 4: auto detection

    int wbits;            // log2(window size)  (8..15, defaults to 15)

    InfBlocks blocks;     // current inflate_blocks state

    private final ZStream z;

    private int flags;

    private int need_bytes = -1;
    private byte[] crcbuf = new byte[4];

    GZIPHeader gheader = null;

    int inflateReset() {
        if (z == null) return Z_STREAM_ERROR;

        z.totalIn = z.totalOut = 0;
        z.msg = null;
        this.mode = HEAD;
        this.need_bytes = -1;
        this.blocks.reset();
        return Z_OK;
    }

    int inflateEnd() {
        if (blocks != null) {
            blocks.free();
        }
        return Z_OK;
    }

    Inflate(ZStream z) {
        this.z = z;
    }

    int inflateInit(int w) {
        z.msg = null;
        blocks = null;

        // handle undocumented wrap option (no zlib header or check)
        wrap = 0;
        if (w < 0) {
            w = -w;
        } else if ((w & INFLATE_ANY) != 0) {
            wrap = 4;
            w &= ~INFLATE_ANY;
            if (w < 48)
                w &= 15;
        } else if ((w & ~31) != 0) { // for example, DEF_WBITS + 32
            wrap = 4;               // zlib and gzip wrapped data should be accepted.
            w &= 15;
        } else {
            wrap = (w >> 4) + 1;
            if (w < 48)
                w &= 15;
        }

        if (w < 8 || w > 15) {
            inflateEnd();
            return Z_STREAM_ERROR;
        }
        if (blocks != null && wbits != w) {
            blocks.free();
            blocks = null;
        }

        // set window size
        wbits = w;

        this.blocks = new InfBlocks(z, 1 << w);

        // reset state
        inflateReset();

        return Z_OK;
    }

    int inflate(int f) {
        int hold = 0;

        int r;
        int b;

        if (z == null || z.nextIn == null) {
            if (f == Z_FINISH && this.mode == HEAD)
                return Z_OK;
            return Z_STREAM_ERROR;
        }

        f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK;
        r = Z_BUF_ERROR;
        while (true) {

            switch (this.mode) {
                case HEAD:
                    if (wrap == 0) {
                        this.mode = BLOCKS;
                        break;
                    }

                    try {
                        r = readBytes(2, r, f);
                    } catch (Return e) {
                        return e.r;
                    }

                    if ((wrap == 4 || (wrap & 2) != 0) &&
                            this.need == 0x8b1fL) {   // gzip header
                        if (wrap == 4) {
                            wrap = 2;
                        }
                        z.adler = new CRC32();
                        checksum(2, this.need);

                        if (gheader == null)
                            gheader = new GZIPHeader();

                        this.mode = FLAGS;
                        break;
                    }

                    if ((wrap & 2) != 0) {
                        this.mode = BAD;
                        z.msg = "incorrect header check";
                        break;
                    }

                    flags = 0;

                    this.method = ((int) this.need) & 0xff;
                    b = ((int) (this.need >> 8)) & 0xff;

                    if (((wrap & 1) == 0 ||  // check if zlib header allowed
                            (((this.method << 8) + b) % 31) != 0) &&
                            (this.method & 0xf) != Z_DEFLATED) {
                        if (wrap == 4) {
                            z.nextInIndex -= 2;
                            z.availIn += 2;
                            z.totalIn -= 2;
                            wrap = 0;
                            this.mode = BLOCKS;
                            break;
                        }
                        this.mode = BAD;
                        z.msg = "incorrect header check";
                        // since zlib 1.2, it is allowted to inflateSync for this case.
          /*
          this.marker = 5;       // can't try inflateSync
          */
                        break;
                    }

                    if ((this.method & 0xf) != Z_DEFLATED) {
                        this.mode = BAD;
                        z.msg = "unknown compression method";
                        // since zlib 1.2, it is allowted to inflateSync for this case.
      /*
          this.marker = 5;       // can't try inflateSync
	  */
                        break;
                    }

                    if (wrap == 4) {
                        wrap = 1;
                    }

                    if ((this.method >> 4) + 8 > this.wbits) {
                        this.mode = BAD;
                        z.msg = "invalid window size";
                        // since zlib 1.2, it is allowted to inflateSync for this case.
      /*
          this.marker = 5;       // can't try inflateSync
	  */
                        break;
                    }

                    z.adler = new Adler32();

                    if ((b & PRESET_DICT) == 0) {
                        this.mode = BLOCKS;
                        break;
                    }
                    this.mode = DICT4;
                case DICT4:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need = ((z.nextIn[z.nextInIndex++] & 0xff) << 24) & 0xff000000L;
                    this.mode = DICT3;
                case DICT3:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += ((z.nextIn[z.nextInIndex++] & 0xff) << 16) & 0xff0000L;
                    this.mode = DICT2;
                case DICT2:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += ((z.nextIn[z.nextInIndex++] & 0xff) << 8) & 0xff00L;
                    this.mode = DICT1;
                case DICT1:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += (z.nextIn[z.nextInIndex++] & 0xffL);
                    z.adler.reset(this.need);
                    this.mode = DICT0;
                    return Z_NEED_DICT;
                case DICT0:
                    this.mode = BAD;
                    z.msg = "need dictionary";
                    this.marker = 0;       // can try inflateSync
                    return Z_STREAM_ERROR;
                case BLOCKS:
                    r = this.blocks.proc(r);
                    if (r == Z_DATA_ERROR) {
                        this.mode = BAD;
                        this.marker = 0;     // can try inflateSync
                        break;
                    }
                    if (r == Z_OK) {
                        r = f;
                    }
                    if (r != Z_STREAM_END) {
                        return r;
                    }
                    r = f;
                    this.was = z.adler.getValue();
                    this.blocks.reset();
                    if (this.wrap == 0) {
                        this.mode = DONE;
                        break;
                    }
                    this.mode = CHECK4;
                case CHECK4:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need = ((z.nextIn[z.nextInIndex++] & 0xff) << 24) & 0xff000000L;
                    this.mode = CHECK3;
                case CHECK3:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += ((z.nextIn[z.nextInIndex++] & 0xff) << 16) & 0xff0000L;
                    this.mode = CHECK2;
                case CHECK2:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += ((z.nextIn[z.nextInIndex++] & 0xff) << 8) & 0xff00L;
                    this.mode = CHECK1;
                case CHECK1:

                    if (z.availIn == 0) return r;
                    r = f;

                    z.availIn--;
                    z.totalIn++;
                    this.need += (z.nextIn[z.nextInIndex++] & 0xffL);

                    if (flags != 0) {  // gzip
                        this.need = ((this.need & 0xff000000) >> 24 |
                                (this.need & 0x00ff0000) >> 8 |
                                (this.need & 0x0000ff00) << 8 |
                                (this.need & 0x0000ffff) << 24) & 0xffffffffL;
                    }

                    if (((int) (this.was)) != ((int) (this.need))) {
                        z.msg = "incorrect data check";
                        // chack is delayed
          /*
          this.mode = BAD;
          this.marker = 5;       // can't try inflateSync
          break;
	  */
                    } else if (flags != 0 && gheader != null) {
                        gheader.crc = this.need;
                    }

                    this.mode = LENGTH;
                case LENGTH:
                    if (wrap != 0 && flags != 0) {

                        try {
                            r = readBytes(4, r, f);
                        } catch (Return e) {
                            return e.r;
                        }

                        if (z.msg != null && z.msg.equals("incorrect data check")) {
                            this.mode = BAD;
                            this.marker = 5;       // can't try inflateSync
                            break;
                        }

                        if (this.need != (z.totalOut & 0xffffffffL)) {
                            z.msg = "incorrect length check";
                            this.mode = BAD;
                            break;
                        }
                        z.msg = null;
                    } else {
                        if (z.msg != null && z.msg.equals("incorrect data check")) {
                            this.mode = BAD;
                            this.marker = 5;       // can't try inflateSync
                            break;
                        }
                    }

                    this.mode = DONE;
                case DONE:
                    return Z_STREAM_END;
                case BAD:
                    return Z_DATA_ERROR;

                case FLAGS:

                    try {
                        r = readBytes(2, r, f);
                    } catch (Return e) {
                        return e.r;
                    }

                    flags = ((int) this.need) & 0xffff;

                    if ((flags & 0xff) != Z_DEFLATED) {
                        z.msg = "unknown compression method";
                        this.mode = BAD;
                        break;
                    }
                    if ((flags & 0xe000) != 0) {
                        z.msg = "unknown header flags set";
                        this.mode = BAD;
                        break;
                    }

                    if ((flags & 0x0200) != 0) {
                        checksum(2, this.need);
                    }

                    this.mode = TIME;

                case TIME:
                    try {
                        r = readBytes(4, r, f);
                    } catch (Return e) {
                        return e.r;
                    }
                    if (gheader != null)
                        gheader.time = this.need;
                    if ((flags & 0x0200) != 0) {
                        checksum(4, this.need);
                    }
                    this.mode = OS;
                case OS:
                    try {
                        r = readBytes(2, r, f);
                    } catch (Return e) {
                        return e.r;
                    }
                    if (gheader != null) {
                        gheader.xflags = ((int) this.need) & 0xff;
                        gheader.os = (((int) this.need) >> 8) & 0xff;
                    }
                    if ((flags & 0x0200) != 0) {
                        checksum(2, this.need);
                    }
                    this.mode = EXLEN;
                case EXLEN:
                    if ((flags & 0x0400) != 0) {
                        try {
                            r = readBytes(2, r, f);
                        } catch (Return e) {
                            return e.r;
                        }
                        if (gheader != null) {
                            gheader.extra = new byte[((int) this.need) & 0xffff];
                        }
                        if ((flags & 0x0200) != 0) {
                            checksum(2, this.need);
                        }
                    } else if (gheader != null) {
                        gheader.extra = null;
                    }
                    this.mode = EXTRA;

                case EXTRA:
                    if ((flags & 0x0400) != 0) {
                        try {
                            r = readBytes(r, f);
                            if (gheader != null) {
                                byte[] foo = tmp_string.toByteArray();
                                tmp_string = null;
                                if (foo.length == gheader.extra.length) {
                                    System.arraycopy(foo, 0, gheader.extra, 0, foo.length);
                                } else {
                                    z.msg = "bad extra field length";
                                    this.mode = BAD;
                                    break;
                                }
                            }
                        } catch (Return e) {
                            return e.r;
                        }
                    } else if (gheader != null) {
                        gheader.extra = null;
                    }
                    this.mode = NAME;
                case NAME:
                    if ((flags & 0x0800) != 0) {
                        try {
                            r = readString(r, f);
                            if (gheader != null) {
                                gheader.name = tmp_string.toByteArray();
                            }
                            tmp_string = null;
                        } catch (Return e) {
                            return e.r;
                        }
                    } else if (gheader != null) {
                        gheader.name = null;
                    }
                    this.mode = COMMENT;
                case COMMENT:
                    if ((flags & 0x1000) != 0) {
                        try {
                            r = readString(r, f);
                            if (gheader != null) {
                                gheader.comment = tmp_string.toByteArray();
                            }
                            tmp_string = null;
                        } catch (Return e) {
                            return e.r;
                        }
                    } else if (gheader != null) {
                        gheader.comment = null;
                    }
                    this.mode = HCRC;
                case HCRC:
                    if ((flags & 0x0200) != 0) {
                        try {
                            r = readBytes(2, r, f);
                        } catch (Return e) {
                            return e.r;
                        }
                        if (gheader != null) {
                            gheader.hcrc = (int) (this.need & 0xffff);
                        }
                        if (this.need != (z.adler.getValue() & 0xffffL)) {
                            this.mode = BAD;
                            z.msg = "header crc mismatch";
                            this.marker = 5;       // can't try inflateSync
                            break;
                        }
                    }
                    z.adler = new CRC32();

                    this.mode = BLOCKS;
                    break;
                default:
                    return Z_STREAM_ERROR;
            }
        }
    }

    int inflateSetDictionary(byte[] dictionary, int dictLength) {
        if (z == null || (this.mode != DICT0 && this.wrap != 0)) {
            return Z_STREAM_ERROR;
        }

        int index = 0;
        int length = dictLength;

        if (this.mode == DICT0) {
            long adler_need = z.adler.getValue();
            z.adler.reset();
            z.adler.update(dictionary, 0, dictLength);
            if (z.adler.getValue() != adler_need) {
                return Z_DATA_ERROR;
            }
        }

        z.adler.reset();

        if (length >= (1 << this.wbits)) {
            length = (1 << this.wbits) - 1;
            index = dictLength - length;
        }
        this.blocks.set_dictionary(dictionary, index, length);
        this.mode = BLOCKS;
        return Z_OK;
    }

    static private byte[] mark = { (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff };

    int inflateSync() {
        int n;       // number of bytes to look at
        int p;       // pointer to bytes
        int m;       // number of marker bytes found in a row
        long r, w;   // temporaries to save total_in and total_out

        // set up
        if (z == null)
            return Z_STREAM_ERROR;
        if (this.mode != BAD) {
            this.mode = BAD;
            this.marker = 0;
        }
        if ((n = z.availIn) == 0)
            return Z_BUF_ERROR;

        p = z.nextInIndex;
        m = this.marker;
        // search
        while (n != 0 && m < 4) {
            if (z.nextIn[p] == mark[m]) {
                m++;
            } else if (z.nextIn[p] != 0) {
                m = 0;
            } else {
                m = 4 - m;
            }
            p++;
            n--;
        }

        // restore
        z.totalIn += p - z.nextInIndex;
        z.nextInIndex = p;
        z.availIn = n;
        this.marker = m;

        // return no joy or set up to restart on a new block
        if (m != 4) {
            return Z_DATA_ERROR;
        }
        r = z.totalIn;
        w = z.totalOut;
        inflateReset();
        z.totalIn = r;
        z.totalOut = w;
        this.mode = BLOCKS;

        return Z_OK;
    }

    // Returns true if inflate is currently at the end of a block generated
    // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
    // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
    // but removes the length bytes of the resulting empty stored block. When
    // decompressing, PPP checks that at the end of input packet, inflate is
    // waiting for these length bytes.
    int inflateSyncPoint() {
        if (z == null || this.blocks == null)
            return Z_STREAM_ERROR;
        return this.blocks.sync_point();
    }

    private int readBytes(int n, int r, int f)
            throws Return {
        if (need_bytes == -1) {
            need_bytes = n;
            this.need = 0;
        }
        while (need_bytes > 0) {
            if (z.availIn == 0) {
                throw new Return(r);
            }
            ;
            r = f;
            z.availIn--;
            z.totalIn++;
            this.need = this.need |
                    ((z.nextIn[z.nextInIndex++] & 0xff) << ((n - need_bytes) * 8));
            need_bytes--;
        }
        if (n == 2) {
            this.need &= 0xffffL;
        } else if (n == 4) {
            this.need &= 0xffffffffL;
        }
        need_bytes = -1;
        return r;
    }

    static class Return
            extends Exception {
        int r;

        Return(int r) {
            this.r = r;
        }
    }

    private java.io.ByteArrayOutputStream tmp_string = null;

    private int readString(int r, int f)
            throws Return {
        if (tmp_string == null) {
            tmp_string = new java.io.ByteArrayOutputStream();
        }
        int b = 0;
        do {
            if (z.availIn == 0) {
                throw new Return(r);
            }
            ;
            r = f;
            z.availIn--;
            z.totalIn++;
            b = z.nextIn[z.nextInIndex];
            if (b != 0) tmp_string.write(z.nextIn, z.nextInIndex, 1);
            z.adler.update(z.nextIn, z.nextInIndex, 1);
            z.nextInIndex++;
        } while (b != 0);
        return r;
    }

    private int readBytes(int r, int f)
            throws Return {
        if (tmp_string == null) {
            tmp_string = new java.io.ByteArrayOutputStream();
        }
        int b = 0;
        while (this.need > 0) {
            if (z.availIn == 0) {
                throw new Return(r);
            }
            ;
            r = f;
            z.availIn--;
            z.totalIn++;
            b = z.nextIn[z.nextInIndex];
            tmp_string.write(z.nextIn, z.nextInIndex, 1);
            z.adler.update(z.nextIn, z.nextInIndex, 1);
            z.nextInIndex++;
            this.need--;
        }
        return r;
    }

    private void checksum(int n, long v) {
        for (int i = 0; i < n; i++) {
            crcbuf[i] = (byte) (v & 0xff);
            v >>= 8;
        }
        z.adler.update(crcbuf, 0, n);
    }

    public GZIPHeader getGZIPHeader() {
        return gheader;
    }

    boolean inParsingHeader() {
        switch (mode) {
            case HEAD:
            case DICT4:
            case DICT3:
            case DICT2:
            case DICT1:
            case FLAGS:
            case TIME:
            case OS:
            case EXLEN:
            case EXTRA:
            case NAME:
            case COMMENT:
            case HCRC:
                return true;
            default:
                return false;
        }
    }
}
