/*
	Zlib – библиотека сжатия данных общего назначения. Версия 1.1.0
	Это изменённая объектно-ориентированная версия библиотеки, полностью
	совместимая с оригинальной библиотекой.
	
	Copyright © 1995–2005 Jean-loup Gailly и Mark Adler
	Copyright © 2000–2011 ymnk, JCraft, Inc.
	Copyright © 2016, 2019 Малик Разработчик
	
	Эта библиотека поставляется «как есть», без каких-либо явных или
	подразумеваемых гарантий. Ни при каких обстоятельствах авторы не
	несут какой-либо ответственности в случае потери данных вследствие
	использования данной библиотеки.
	
	Разрешается всем использовать эту библиотеку для любых целей, в том
	числе и для коммерческих приложений, а также изменять её и
	распространять свободно при соблюдении следующих условий:
	
		1. Оригинал библиотеки не должен быть искажён; вы не должны
	заявлять, что именно вы написали оригинальную библиотеку. Если вы
	используете эту библиотеку в своём программном продукте, то ссылка
	на авторов библиотеки была бы желательна, но это не является
	обязательным требованием.
	
		2. Изменённые версии исходных текстов должны быть отчётливо
	маркированы и не должны выдаваться за оригинал библиотеки.
	
		3. Эти замечания не могут быть удалены либо изменены при
	каком-либо варианте распространения исходных текстов.
*/


package malik.emulator.compression.zlib;

final class Deflate extends Zlib
{
	private static final int NEED_MORE = 0;
	private static final int BLOCK_DONE = 1;
	private static final int FINISH_STARTED = 2;
	private static final int FINISH_DONE = 3;
	private static final int PRESET_DICT = 0x20;
	private static final int INIT_STATE = 42;
	private static final int BUSY_STATE = 113;
	private static final int FINISH_STATE = 666;
	private static final int Z_DEFLATED = 8;
	private static final int STORED_BLOCK = 0;
	private static final int STATIC_TREES = 1;
	private static final int DYN_TREES = 2;
	private static final int Z_BINARY = 0;
	private static final int Z_ASCII = 1;
	private static final int Z_UNKNOWN = 2;
	private static final int BUF_SIZE = 8 * 2;
	private static final int REP_3_6 = 16;
	private static final int REPZ_3_10 = 17;
	private static final int REPZ_11_138 = 18;
	private static final int MIN_MATCH = 3;
	private static final int MAX_MATCH = 258;
	private static final int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
	private static final int MAX_BITS = 15;
	private static final int D_CODES = 30;
	private static final int BL_CODES = 19;
	private static final int LENGTH_CODES = 29;
	private static final int LITERALS = 256;
	private static final int L_CODES = LITERALS + LENGTH_CODES + 1;
	private static final int HEAP_SIZE = 2 * L_CODES + 1;
	private static final int END_BLOCK = 256;
	private static final int MAX_MEM_LEVEL = 9;
	private static final int DEF_MEM_LEVEL = 8;
	private static final int STORED = 0;
	private static final int FAST = 1;
	private static final int SLOW = 2;
	private static final byte[] DIST_CODE;
	private static final byte[] BL_ORDER;
	private static final byte[] LENGTH_CODE;
	private static final int[] BASE_LENGTH;
	private static final int[] BASE_DIST;
	private static final Config[] CONFIG_TABLE;
	private static final String[] ERR_MSG;

	static
	{
		DIST_CODE = new byte[] {
				0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
				8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10,
				10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
				11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
				12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
				13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
				13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
				14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
				14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
				14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
				15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
				15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
				15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17,
				18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
				23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
				24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
				26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
				26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
				27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
				27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
				28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
				28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
				28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
				29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
				29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
				29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
		};
		BL_ORDER = new byte[] {
				16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
				11, 4, 12, 3, 13, 2, 14, 1, 15
		};
		LENGTH_CODE = new byte[] {
				0, 1, 2, 3, 4, 5, 6, 7, 8, 8,
				9, 9, 10, 10, 11, 11, 12, 12, 12, 12,
				13, 13, 13, 13, 14, 14, 14, 14, 15, 15,
				15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
				17, 17, 17, 17, 17, 17, 17, 17, 18, 18,
				18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
				19, 19, 19, 19, 20, 20, 20, 20, 20, 20,
				20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
				21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
				21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
				22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
				22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
				23, 23, 23, 23, 23, 23, 23, 23, 24, 24,
				24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
				24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
				24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
				25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
				25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
				25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
				25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
				26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
				26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
				26, 26, 26, 26, 27, 27, 27, 27, 27, 27,
				27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
				27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
				27, 27, 27, 27, 27, 28
		};
		BASE_LENGTH = new int[] {
				0, 1, 2, 3, 4, 5, 6, 7, 8, 10,
				12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
				64, 80, 96, 112, 128, 160, 192, 224, 0
		};
		BASE_DIST = new int[] {
				0, 1, 2, 3, 4, 6, 8, 12, 16, 24,
				32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
				1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
		};
		CONFIG_TABLE = new Config[] {
				new Config(0, 0, 0, 0, STORED),
				new Config(4, 4, 8, 4, FAST),
				new Config(4, 5, 16, 8, FAST),
				new Config(4, 6, 32, 32, FAST),
				new Config(4, 4, 16, 16, SLOW),
				new Config(8, 16, 32, 32, SLOW),
				new Config(8, 16, 128, 128, SLOW),
				new Config(8, 32, 128, 256, SLOW),
				new Config(32, 128, 258, 1024, SLOW),
				new Config(32, 258, 258, 4096, SLOW)
		};
		ERR_MSG = new String[] {
				"need dictionary",
				"stream end",
				"",
				"file error",
				"stream error",
				"data error",
				"insufficient memory",
				"buffer error",
				"incompatible version",
				""
		};
	}

	public static boolean smaller(int[] tree, int n, int m, byte[] depth)
	{
		int tn2 = tree[n * 2];
		int tm2 = tree[m * 2];
		return tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m]);
	}

	public static int deflateCopy(ZStream dst, ZStream src)
	{
		if(src.dstate == null)
		{
			return STREAM_ERROR;
		}
		if(src.nextIn != null)
		{
			dst.nextIn = new byte[src.nextIn.length];
			Array.copy(src.nextIn, 0, dst.nextIn, 0, src.nextIn.length);
		}
		dst.nextInIndex = src.nextInIndex;
		dst.availIn = src.availIn;
		dst.totalIn = src.totalIn;
		if(src.nextOut != null)
		{
			dst.nextOut = new byte[src.nextOut.length];
			Array.copy(src.nextOut, 0, dst.nextOut, 0, src.nextOut.length);
		}
		dst.nextOutIndex = src.nextOutIndex;
		dst.availOut = src.availOut;
		dst.totalOut = src.totalOut;
		dst.msg = src.msg;
		dst.dataType = src.dataType;
		dst.adler = src.adler.copy();
		dst.dstate = (Deflate) src.dstate.clone();
		dst.dstate.stream = dst;
		return OK;
	}

	private static int dcode(int dist)
	{
		return dist < 256 ? DIST_CODE[dist] : DIST_CODE[256 + (dist >>> 7)];
	}

	private static byte[] dup(byte[] buf)
	{
		byte[] result;
		Array.copy(buf, 0, result = new byte[buf.length], 0, result.length);
		return result;
	}

	private static int[] dup(int[] buf)
	{
		int[] result;
		Array.copy(buf, 0, result = new int[buf.length], 0, result.length);
		return result;
	}


	public int pendingOut;
	public int pending;
	public int wrap;
	public int level;
	public int heapLen;
	public int heapMax;
	public int optLen;
	public int staticLen;
	public byte[] pendingBuf;
	public byte[] depth;
	public int[] blCount;
	public int[] heap;
	private int status;
	private int pendingBufSize;
	private int dataType;
	private int lastFlush;
	private int wSize;
	private int wBits;
	private int wMask;
	private int windowSize;
	private int insh;
	private int hashSize;
	private int hashBits;
	private int hashMask;
	private int hashShift;
	private int blockStart;
	private int matchLength;
	private int prevMatch;
	private int matchAvailable;
	private int strStart;
	private int matchStart;
	private int lookahead;
	private int prevLength;
	private int maxChainLength;
	private int maxLazyMatch;
	private int strategy;
	private int goodMatch;
	private int niceMatch;
	private int litBufSize;
	private int lastLit;
	private int matches;
	private int lastEobLen;
	private int lBuf;
	private int dBuf;
	private int biBuf;
	private int biValid;
	private byte[] window;
	private int[] prev;
	private int[] head;
	private int[] dynLtree;
	private int[] dynDtree;
	private int[] blTree;
	private Tree lDesc;
	private Tree dDesc;
	private Tree blDesc;
	private GZIPHeader gheader;
	private ZStream stream;

	Deflate(ZStream stream)
	{
		this.wrap = 1;
		this.depth = new byte[2 * L_CODES + 1];
		this.blCount = new int[MAX_BITS + 1];
		this.heap = new int[2 * L_CODES + 1];
		this.dynLtree = new int[HEAP_SIZE * 2];
		this.dynDtree = new int[(2 * D_CODES + 1) * 2];
		this.blTree = new int[(2 * BL_CODES + 1) * 2];
		this.lDesc = new Tree();
		this.dDesc = new Tree();
		this.blDesc = new Tree();
		this.stream = stream;
	}

	public void pqDownHeap(int[] tree, int k)
	{
		int v;
		int j;
		for(v = heap[k], j = k << 1; j <= heapLen; k = j, j <<= 1)
		{
			if(j < heapLen && smaller(tree, heap[j + 1], heap[j], depth))
			{
				j++;
			}
			if(smaller(tree, v, heap[j], depth))
			{
				break;
			}
			heap[k] = heap[j];
		}
		heap[k] = v;
	}

	public void putBytes(byte[] data, int offset, int length)
	{
		Array.copy(data, offset, pendingBuf, pending, length);
		pending += length;
	}

	public void putByte(byte c)
	{
		pendingBuf[pending++] = c;
	}

	public void putShort(int w)
	{
		putByte((byte) w);
		putByte((byte) (w >>> 8));
	}

	public int deflateInit(int level, int bits, int memlevel)
	{
		return deflateInit(level, Z_DEFLATED, bits, memlevel, DEFAULT_STRATEGY);
	}

	public int deflateInit(int level, int bits)
	{
		return deflateInit(level, Z_DEFLATED, bits, DEF_MEM_LEVEL, DEFAULT_STRATEGY);
	}

	public int deflateInit(int level)
	{
		return deflateInit(level, MAX_WBITS);
	}

	public int deflateEnd()
	{
		if(status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE)
		{
			return STREAM_ERROR;
		}
		pendingBuf = null;
		head = null;
		prev = null;
		window = null;
		return status == BUSY_STATE ? DATA_ERROR : OK;
	}

	public int deflateParams(int lvl, int strtg)
	{
		int err = OK;
		if(lvl == DEFAULT_COMPRESSION)
		{
			lvl = 6;
		}
		if(lvl < 0 || lvl > 9 || strtg < 0 || strtg > HUFFMAN_ONLY)
		{
			return STREAM_ERROR;
		}
		if(CONFIG_TABLE[level].func != CONFIG_TABLE[lvl].func && stream.totalIn != 0)
		{
			err = stream.deflate(PARTIAL_FLUSH);
		}
		if(level != lvl)
		{
			Config config = CONFIG_TABLE[level];
			level = lvl;
			maxLazyMatch = config.maxLazy;
			goodMatch = config.goodLength;
			niceMatch = config.niceLength;
			maxChainLength = config.maxChain;
		}
		strategy = strtg;
		return err;
	}

	public int deflateSetDictionary(byte[] dictionary, int dictLength)
	{
		int index = 0;
		int length = dictLength;
		if(dictionary == null || status != INIT_STATE)
		{
			return STREAM_ERROR;
		}
		stream.adler.update(dictionary, 0, dictLength);
		if(length < MIN_MATCH)
		{
			return OK;
		}
		if(length > wSize - MIN_LOOKAHEAD)
		{
			length = wSize - MIN_LOOKAHEAD;
			index = dictLength - length;
		}
		Array.copy(dictionary, index, window, 0, length);
		strStart = length;
		blockStart = length;
		insh = window[0] & 0xff;
		insh = ((insh << hashShift) ^ (window[1] & 0xff)) & hashMask;
		for(int n = 0; n <= length - MIN_MATCH; n++)
		{
			insh = ((insh << hashShift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hashMask;
			prev[n & wMask] = head[insh];
			head[insh] = (short) n;
		}
		return OK;
	}

	public int deflate(int flush)
	{
		int oldFlush;
		if(flush > FINISH || flush < 0)
		{
			return STREAM_ERROR;
		}
		if(stream.nextOut == null || (stream.nextIn == null && stream.availIn != 0) ||
				(status == FINISH_STATE && flush != FINISH))
		{
			stream.msg = ERR_MSG[NEED_DICT - STREAM_ERROR];
			return STREAM_ERROR;
		}
		if(stream.availOut == 0)
		{
			stream.msg = ERR_MSG[NEED_DICT - BUF_ERROR];
			return BUF_ERROR;
		}
		oldFlush = lastFlush;
		lastFlush = flush;
		if(status == INIT_STATE)
		{
			if(wrap == 2)
			{
				getGZIPHeader().put(this);
				status = BUSY_STATE;
				stream.adler.reset();
			} else
			{
				int header = (Z_DEFLATED + ((wBits - 8) << 4)) << 8;
				int levelFlags = ((level - 1) & 0xff) >> 1;
				if(levelFlags > 3)
				{
					levelFlags = 3;
				}
				header |= (levelFlags << 6);
				if(strStart != 0)
				{
					header |= PRESET_DICT;
				}
				header += 31 - (header % 31);
				status = BUSY_STATE;
				putShortMSB(header);
				if(strStart != 0)
				{
					int adler = stream.adler.getValue();
					putShortMSB(adler >>> 16);
					putShortMSB(adler & 0xffff);
				}
				stream.adler.reset();
			}
		}
		if(pending != 0)
		{
			stream.flushPending();
			if(stream.availOut == 0)
			{
				lastFlush = -1;
				return OK;
			}
		}
		else if(stream.availIn == 0 && flush <= oldFlush && flush != FINISH)
		{
			stream.msg = ERR_MSG[NEED_DICT - BUF_ERROR];
			return BUF_ERROR;
		}
		if(status == FINISH_STATE && stream.availIn != 0)
		{
			stream.msg = ERR_MSG[NEED_DICT - BUF_ERROR];
			return BUF_ERROR;
		}
		if(stream.availIn != 0 || lookahead != 0 || (flush != NO_FLUSH && status != FINISH_STATE))
		{
			int bstate = -1;
			switch(CONFIG_TABLE[level].func)
			{
			default:
				break;
			case STORED:
				bstate = deflateStored(flush);
				break;
			case FAST:
				bstate = deflateFast(flush);
				break;
			case SLOW:
				bstate = deflateSlow(flush);
				break;
			}
			if(bstate == FINISH_STARTED || bstate == FINISH_DONE)
			{
				status = FINISH_STATE;
			}
			if(bstate == NEED_MORE || bstate == FINISH_STARTED)
			{
				if(stream.availOut == 0)
				{
					lastFlush = -1;
				}
				return OK;
			}
			if(bstate == BLOCK_DONE)
			{
				if(flush == PARTIAL_FLUSH)
				{
					trAlign();
				} else
				{
					trStoredBlock(0, 0, false);
					if(flush == FULL_FLUSH)
					{
						for(int i = 0; i < hashSize; i++)
						{
							head[i] = 0;
						}
					}
				}
				stream.flushPending();
				if(stream.availOut == 0)
				{
					lastFlush = -1;
					return OK;
				}
			}
		}
		if(flush != FINISH)
		{
			return OK;
		}
		if(wrap <= 0)
		{
			return STREAM_END;
		}
		if(wrap == 2)
		{
			int adler = stream.adler.getValue();
			putByte((byte) (adler & 0xff));
			putByte((byte) ((adler >> 8) & 0xff));
			putByte((byte) ((adler >> 16) & 0xff));
			putByte((byte) ((adler >> 24) & 0xff));
			putByte((byte) (stream.totalIn & 0xff));
			putByte((byte) ((stream.totalIn >> 8) & 0xff));
			putByte((byte) ((stream.totalIn >> 16) & 0xff));
			putByte((byte) ((stream.totalIn >> 24) & 0xff));
			getGZIPHeader().setCRC(adler);
		} else
		{
			int adler = stream.adler.getValue();
			putShortMSB(adler >>> 16);
			putShortMSB(adler & 0xffff);
		}
		stream.flushPending();
		if(wrap > 0)
		{
			wrap = -wrap;
		}
		return pending != 0 ? OK : STREAM_END;
	}

	private void lmInit()
	{
		int i;
		windowSize = 2 * wSize;
		head[hashSize - 1] = 0;
		for(i = 0; i < hashSize - 1; i++)
		{
			head[i] = 0;
		}
		Config config = CONFIG_TABLE[level];
		maxLazyMatch = config.maxLazy;
		goodMatch = config.goodLength;
		niceMatch = config.niceLength;
		maxChainLength = config.maxChain;
		strStart = 0;
		blockStart = 0;
		lookahead = 0;
		matchLength = prevLength = MIN_MATCH - 1;
		matchAvailable = 0;
		insh = 0;
	}

	private void trInit()
	{
		lDesc.dynTree = dynLtree;
		lDesc.statDesc = StaticTree.STATIC_L_DESC;
		dDesc.dynTree = dynDtree;
		dDesc.statDesc = StaticTree.STATIC_D_DESC;
		blDesc.dynTree = blTree;
		blDesc.statDesc = StaticTree.STATIC_BL_DESC;
		biBuf = 0;
		biValid = 0;
		lastEobLen = 8;
		initBlock();
	}

	private void initBlock()
	{
		int i;
		for(i = 0; i < L_CODES; i++)
		{
			dynLtree[i * 2] = 0;
		}
		for(i = 0; i < D_CODES; i++)
		{
			dynDtree[i * 2] = 0;
		}
		for(i = 0; i < BL_CODES; i++)
		{
			blTree[i * 2] = 0;
		}
		dynLtree[END_BLOCK * 2] = 1;
		optLen = staticLen = 0;
		lastLit = matches = 0;
	}

	private void scanTree(int[] tree, int maxCode)
	{
		int n;
		int prevlen = -1;
		int curlen;
		int nextlen = tree[0 * 2 + 1];
		int count = 0;
		int maxCount = 7;
		int minCount = 4;
		if(nextlen == 0)
		{
			maxCount = 138;
			minCount = 3;
		}
		tree[(maxCode + 1) * 2 + 1] = (short) 0xffff;
		for(n = 0; n <= maxCode; n++)
		{
			curlen = nextlen;
			nextlen = tree[(n + 1) * 2 + 1];
			if(++count < maxCount && curlen == nextlen)
			{
				continue;
			}
			else if(count < minCount)
			{
				blTree[curlen * 2] += count;
			}
			else if(curlen != 0)
			{
				if(curlen != prevlen)
				{
					blTree[curlen * 2]++;
				}
				blTree[REP_3_6 * 2]++;
			}
			else if(count <= 10)
			{
				blTree[REPZ_3_10 * 2]++;
			}
			else
			{
				blTree[REPZ_11_138 * 2]++;
			}
			count = 0;
			prevlen = curlen;
			if(nextlen == 0)
			{
				maxCount = 138;
				minCount = 3;
			}
			else if(curlen == nextlen)
			{
				maxCount = 6;
				minCount = 3;
			}
			else
			{
				maxCount = 7;
				minCount = 4;
			}
		}
	}

	private void sendAllTrees(int lcodes, int dcodes, int blcodes)
	{
		int rank;
		sendBits(lcodes - 257, 5);
		sendBits(dcodes - 1, 5);
		sendBits(blcodes - 4, 4);
		for(rank = 0; rank < blcodes; rank++)
		{
			sendBits(blTree[BL_ORDER[rank] * 2 + 1], 3);
		}
		sendTree(dynLtree, lcodes - 1);
		sendTree(dynDtree, dcodes - 1);
	}

	private void sendTree(int[] tree, int maxCode)
	{
		int n;
		int prevlen = -1;
		int curlen;
		int nextlen = tree[0 * 2 + 1];
		int count = 0;
		int maxCount = 7;
		int minCount = 4;
		if(nextlen == 0)
		{
			maxCount = 138;
			minCount = 3;
		}
		for(n = 0; n <= maxCode; n++)
		{
			curlen = nextlen;
			nextlen = tree[(n + 1) * 2 + 1];
			if(++count < maxCount && curlen == nextlen)
			{
				continue;
			}
			else if(count < minCount)
			{
				do
				{
					sendCode(curlen, blTree);
				} while(--count != 0);
			}
			else if(curlen != 0)
			{
				if(curlen != prevlen)
				{
					sendCode(curlen, blTree);
					count--;
				}
				sendCode(REP_3_6, blTree);
				sendBits(count - 3, 2);
			}
			else if(count <= 10)
			{
				sendCode(REPZ_3_10, blTree);
				sendBits(count - 3, 3);
			}
			else
			{
				sendCode(REPZ_11_138, blTree);
				sendBits(count - 11, 7);
			}
			count = 0;
			prevlen = curlen;
			if(nextlen == 0)
			{
				maxCount = 138;
				minCount = 3;
			}
			else if(curlen == nextlen)
			{
				maxCount = 6;
				minCount = 3;
			}
			else
			{
				maxCount = 7;
				minCount = 4;
			}
		}
	}

	private void putShortMSB(int b)
	{
		putByte((byte) (b >> 8));
		putByte((byte) b);
	}

	private void sendCode(int c, int[] tree)
	{
		int c2 = c * 2;
		sendBits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff));
	}

	private void sendBits(int value, int length)
	{
		int val;
		int len = length;
		if(biValid > BUF_SIZE - len)
		{
			val = value;
			biBuf |= ((val << biValid) & 0xffff);
			putShort(biBuf);
			biBuf = (short) (val >>> (BUF_SIZE - biValid));
			biValid += len - BUF_SIZE;
		} else
		{
			biBuf |= (((value) << biValid) & 0xffff);
			biValid += len;
		}
	}

	private void trAlign()
	{
		sendBits(STATIC_TREES << 1, 3);
		sendCode(END_BLOCK, StaticTree.STATIC_LTREE);
		biFlush();
		if(lastEobLen - biValid < -2)
		{
			sendBits(STATIC_TREES << 1, 3);
			sendCode(END_BLOCK, StaticTree.STATIC_LTREE);
			biFlush();
		}
		lastEobLen = 7;
	}

	private void compressBlock(int[] ltree, int[] dtree)
	{
		int dist;
		int code;
		int extra;
		int lc;
		int lx = 0;
		if(lastLit != 0)
		{
			do
			{
				dist = ((pendingBuf[dBuf + lx * 2] & 0xff) << 8) |
						(pendingBuf[dBuf + lx * 2 + 1] & 0xff);
				lc = (pendingBuf[lBuf + lx]) & 0xff;
				lx++;
				if(dist == 0)
				{
					sendCode(lc, ltree);
				} else
				{
					code = LENGTH_CODE[lc];
					sendCode(code + LITERALS + 1, ltree);
					extra = StaticTree.EXTRA_LBITS[code];
					if(extra != 0)
					{
						lc -= BASE_LENGTH[code];
						sendBits(lc, extra);
					}
					dist--;
					code = dcode(dist);
					sendCode(code, dtree);
					extra = StaticTree.EXTRA_DBITS[code];
					if(extra != 0)
					{
						dist -= BASE_DIST[code];
						sendBits(dist, extra);
					}
				}
			} while(lx < lastLit);
		}
		sendCode(END_BLOCK, ltree);
		lastEobLen = ltree[END_BLOCK * 2 + 1];
	}

	private void setDataType()
	{
		int n = 0;
		int asciiFreq = 0;
		int binFreq = 0;
		while(n < 7)
		{
			binFreq += dynLtree[n * 2];
			n++;
		}
		while(n < 128)
		{
			asciiFreq += dynLtree[n * 2];
			n++;
		}
		while(n < LITERALS)
		{
			binFreq += dynLtree[n * 2];
			n++;
		}
		dataType = binFreq > asciiFreq >>> 2 ? Z_BINARY : Z_ASCII;
	}

	private void biFlush()
	{
		if(biValid == 16)
		{
			putShort(biBuf);
			biBuf = 0;
			biValid = 0;
		}
		else if(biValid >= 8)
		{
			putByte((byte) biBuf);
			biBuf >>>= 8;
			biValid -= 8;
		}
	}

	private void biWindup()
	{
		if(biValid > 8)
		{
			putShort(biBuf);
		}
		else if(biValid > 0)
		{
			putByte((byte) biBuf);
		}
		biBuf = 0;
		biValid = 0;
	}

	private void copyBlock(int buf, int len, boolean header)
	{
		biWindup();
		lastEobLen = 8;
		if(header)
		{
			putShort((short) len);
			putShort((short) ~len);
		}
		putBytes(window, buf, len);
	}

	private void flushBlockOnly(boolean eof)
	{
		trFlushBlock(blockStart >= 0 ? blockStart : -1, strStart - blockStart, eof);
		blockStart = strStart;
		stream.flushPending();
	}

	private void trStoredBlock(int buf, int storedLen, boolean eof)
	{
		sendBits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3);
		copyBlock(buf, storedLen, true);
	}

	private void trFlushBlock(int buf, int storedLen, boolean eof)
	{
		int optLenb;
		int staticLenb;
		int maxBlindex = 0;
		if(level > 0)
		{
			if(dataType == Z_UNKNOWN)
			{
				setDataType();
			}
			lDesc.buildTree(this);
			dDesc.buildTree(this);
			maxBlindex = buildBlTree();
			optLenb = (optLen + 3 + 7) >>> 3;
			staticLenb = (staticLen + 3 + 7) >>> 3;
			if(staticLenb <= optLenb)
			{
				optLenb = staticLenb;
			}
		} else
		{
			optLenb = staticLenb = storedLen + 5;
		}
		if(storedLen + 4 <= optLenb && buf != -1)
		{
			trStoredBlock(buf, storedLen, eof);
		}
		else if(staticLenb == optLenb)
		{
			sendBits((STATIC_TREES << 1) + (eof ? 1 : 0), 3);
			compressBlock(StaticTree.STATIC_LTREE, StaticTree.STATIC_DTREE);
		}
		else
		{
			sendBits((DYN_TREES << 1) + (eof ? 1 : 0), 3);
			sendAllTrees(lDesc.maxCode + 1, dDesc.maxCode + 1, maxBlindex + 1);
			compressBlock(dynLtree, dynDtree);
		}
		initBlock();
		if(eof)
		{
			biWindup();
		}
	}

	private void fillWindow()
	{
		int n;
		int m;
		int p;
		int more;
		do
		{
			more = (windowSize - lookahead - strStart);
			if(more == 0 && strStart == 0 && lookahead == 0)
			{
				more = wSize;
			}
			else if(more == -1)
			{
				more--;
			}
			else if(strStart >= wSize + wSize - MIN_LOOKAHEAD)
			{
				Array.copy(window, wSize, window, 0, wSize);
				matchStart -= wSize;
				strStart -= wSize;
				blockStart -= wSize;
				n = hashSize;
				p = n;
				do
				{
					m = (head[--p] & 0xffff);
					head[p] = (m >= wSize ? (short) (m - wSize) : 0);
				} while(--n != 0);
				n = wSize;
				p = n;
				do
				{
					m = (prev[--p] & 0xffff);
					prev[p] = (m >= wSize ? (short) (m - wSize) : 0);
				} while(--n != 0);
				more += wSize;
			}
			if(stream.availIn == 0)
			{
				return;
			}
			n = stream.readBuf(window, strStart + lookahead, more);
			lookahead += n;
			if(lookahead >= MIN_MATCH)
			{
				insh = window[strStart] & 0xff;
				insh = ((insh << hashShift) ^ (window[strStart + 1] & 0xff)) & hashMask;
			}
		} while(lookahead < MIN_LOOKAHEAD && stream.availIn != 0);
	}

	private boolean trTally(int dst, int lc)
	{
		int outLength;
		int inLength;
		int dcode;
		pendingBuf[dBuf + lastLit * 2] = (byte) (dst >>> 8);
		pendingBuf[dBuf + lastLit * 2 + 1] = (byte) dst;
		pendingBuf[lBuf + lastLit] = (byte) lc;
		lastLit++;
		if(dst == 0)
		{
			dynLtree[lc * 2]++;
		} else
		{
			matches++;
			dst--;
			dynLtree[(LENGTH_CODE[lc] + LITERALS + 1) * 2]++;
			dynDtree[dcode(dst) * 2]++;
		}
		if((lastLit & 0x1fff) == 0 && level > 2)
		{
			outLength = lastLit * 8;
			inLength = strStart - blockStart;
			for(dcode = 0; dcode < D_CODES; dcode++)
			{
				outLength += (int) dynDtree[dcode * 2] * (5L + StaticTree.EXTRA_DBITS[dcode]);
			}
			outLength >>>= 3;
			if((matches < (lastLit / 2)) && outLength < inLength / 2)
			{
				return true;
			}
		}
		return lastLit == litBufSize - 1;
	}

	private int buildBlTree()
	{
		int maxBlindex;
		scanTree(dynLtree, lDesc.maxCode);
		scanTree(dynDtree, dDesc.maxCode);
		blDesc.buildTree(this);
		for(maxBlindex = BL_CODES - 1; maxBlindex >= 3; maxBlindex--)
		{
			if(blTree[BL_ORDER[maxBlindex] * 2 + 1] != 0)
			{
				break;
			}
		}
		optLen += 3 * (maxBlindex + 1) + 5 + 5 + 4;
		return maxBlindex;
	}

	private int deflateStored(int flush)
	{
		int maxStart;
		int maxBlockSize = 0xffff;
		if(maxBlockSize > pendingBufSize - 5)
		{
			maxBlockSize = pendingBufSize - 5;
		}
		do
		{
			if(lookahead <= 1)
			{
				fillWindow();
				if(lookahead == 0 && flush == NO_FLUSH)
				{
					return NEED_MORE;
				}
				if(lookahead == 0)
				{
					break;
				}
			}
			strStart += lookahead;
			lookahead = 0;
			maxStart = blockStart + maxBlockSize;
			if(strStart == 0 || strStart >= maxStart)
			{
				lookahead = (int) (strStart - maxStart);
				strStart = (int) maxStart;
				flushBlockOnly(false);
				if(stream.availOut == 0)
				{
					return NEED_MORE;
				}
			}
			if(strStart - blockStart >= wSize - MIN_LOOKAHEAD)
			{
				flushBlockOnly(false);
				if(stream.availOut == 0)
				{
					return NEED_MORE;
				}
			}
		} while(true);
		flushBlockOnly(flush == FINISH);
		return stream.availOut == 0 ?
				(flush == FINISH ? FINISH_STARTED : NEED_MORE) :
				(flush == FINISH ? FINISH_DONE : BLOCK_DONE);
	}

	private int deflateFast(int flush)
	{
		boolean bflush;
		int hashHead = 0;
		do
		{
			if(lookahead < MIN_LOOKAHEAD)
			{
				fillWindow();
				if(lookahead < MIN_LOOKAHEAD && flush == NO_FLUSH)
				{
					return NEED_MORE;
				}
				if(lookahead == 0)
				{
					break;
				}
			}
			if(lookahead >= MIN_MATCH)
			{
				insh = ((insh << hashShift) ^ (window[(strStart) + (MIN_MATCH - 1)] & 0xff)) &
						hashMask;
				hashHead = (head[insh] & 0xffff);
				prev[strStart & wMask] = head[insh];
				head[insh] = (short) strStart;
			}
			if(hashHead != 0L && ((strStart - hashHead) & 0xffff) <= wSize - MIN_LOOKAHEAD &&
					strategy != HUFFMAN_ONLY)
			{
				matchLength = longestMatch(hashHead);
			}
			if(matchLength >= MIN_MATCH)
			{
				bflush = trTally(strStart - matchStart, matchLength - MIN_MATCH);
				lookahead -= matchLength;
				if(matchLength <= maxLazyMatch && lookahead >= MIN_MATCH)
				{
					matchLength--;
					do
					{
						strStart++;
						insh = ((insh << hashShift) ^ (window[(strStart) + (MIN_MATCH - 1)] &
								0xff)) & hashMask;
						hashHead = (head[insh] & 0xffff);
						prev[strStart & wMask] = head[insh];
						head[insh] = (short) strStart;
					} while(--matchLength != 0);
					strStart++;
				} else
				{
					strStart += matchLength;
					matchLength = 0;
					insh = window[strStart] & 0xff;
					insh = ((insh << hashShift) ^ (window[strStart + 1] & 0xff)) & hashMask;
				}
			} else
			{
				bflush = trTally(0, window[strStart] & 0xff);
				lookahead--;
				strStart++;
			}
			if(bflush)
			{
				flushBlockOnly(false);
				if(stream.availOut == 0)
				{
					return NEED_MORE;
				}
			}
		} while(true);
		flushBlockOnly(flush == FINISH);
		return stream.availOut == 0 ?
				(flush == FINISH ? FINISH_STARTED : NEED_MORE) :
				(flush == FINISH ? FINISH_DONE : BLOCK_DONE);
	}

	private int deflateSlow(int flush)
	{
		boolean bflush;
		int hashHead = 0;
		do
		{
			if(lookahead < MIN_LOOKAHEAD)
			{
				fillWindow();
				if(lookahead < MIN_LOOKAHEAD && flush == NO_FLUSH)
				{
					return NEED_MORE;
				}
				if(lookahead == 0)
				{
					break;
				}
			}
			if(lookahead >= MIN_MATCH)
			{
				insh = ((insh << hashShift) ^ (window[(strStart) + (MIN_MATCH - 1)] & 0xff)) &
						hashMask;
				hashHead = (head[insh] & 0xffff);
				prev[strStart & wMask] = head[insh];
				head[insh] = (short) strStart;
			}
			prevLength = matchLength;
			prevMatch = matchStart;
			matchLength = MIN_MATCH - 1;
			if(hashHead != 0 && prevLength < maxLazyMatch &&
					((strStart - hashHead) & 0xffff) <= wSize - MIN_LOOKAHEAD)
			{
				if(strategy != HUFFMAN_ONLY)
				{
					matchLength = longestMatch(hashHead);
				}
				if(matchLength <= 5 && (strategy == FILTERED ||
						(matchLength == MIN_MATCH && strStart - matchStart > 4096)))
				{
					matchLength = MIN_MATCH - 1;
				}
			}
			if(prevLength >= MIN_MATCH && matchLength <= prevLength)
			{
				int maxInsert = strStart + lookahead - MIN_MATCH;
				bflush = trTally(strStart - 1 - prevMatch, prevLength - MIN_MATCH);
				lookahead -= prevLength - 1;
				prevLength -= 2;
				do
				{
					if(++strStart <= maxInsert)
					{
						insh = ((insh << hashShift) ^ (window[(strStart) + (MIN_MATCH - 1)] &
								0xff)) & hashMask;
						hashHead = (head[insh] & 0xffff);
						prev[strStart & wMask] = head[insh];
						head[insh] = (short) strStart;
					}
				} while(--prevLength != 0);
				matchAvailable = 0;
				matchLength = MIN_MATCH - 1;
				strStart++;
				if(bflush)
				{
					flushBlockOnly(false);
					if(stream.availOut == 0)
					{
						return NEED_MORE;
					}
				}
			}
			else if(matchAvailable != 0)
			{
				bflush = trTally(0, window[strStart - 1] & 0xff);
				if(bflush)
				{
					flushBlockOnly(false);
				}
				strStart++;
				lookahead--;
				if(stream.availOut == 0)
				{
					return NEED_MORE;
				}
			}
			else
			{
				matchAvailable = 1;
				strStart++;
				lookahead--;
			}
		} while(true);
		if(matchAvailable != 0)
		{
			bflush = trTally(0, window[strStart - 1] & 0xff);
			matchAvailable = 0;
		}
		flushBlockOnly(flush == FINISH);
		return stream.availOut == 0 ?
				(flush == FINISH ? FINISH_STARTED : NEED_MORE) :
				(flush == FINISH ? FINISH_DONE : BLOCK_DONE);
	}

	private int deflateReset()
	{
		stream.totalIn = stream.totalOut = 0;
		stream.msg = null;
		stream.dataType = Z_UNKNOWN;
		pending = 0;
		pendingOut = 0;
		if(wrap < 0)
		{
			wrap = -wrap;
		}
		status = (wrap == 0) ? BUSY_STATE : INIT_STATE;
		stream.adler.reset();
		lastFlush = NO_FLUSH;
		trInit();
		lmInit();
		return OK;
	}

	private int deflateInit(int level, int method, int windowBits, int memLevel, int strategy)
	{
		int wraplocal = 1;
		stream.msg = null;
		if(level == DEFAULT_COMPRESSION)
		{
			level = 6;
		}
		if(windowBits < 0)
		{
			wraplocal = 0;
			windowBits = -windowBits;
		}
		else if(windowBits > 15)
		{
			wraplocal = 2;
			windowBits -= 16;
			stream.adler = new CRC32();
		}
		if(memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
				windowBits < 9 || windowBits > 15 || level < 0 || level > 9 ||
				strategy < 0 || strategy > HUFFMAN_ONLY)
		{
			return STREAM_ERROR;
		}
		wrap = wraplocal;
		wBits = windowBits;
		wSize = 1 << wBits;
		wMask = wSize - 1;
		hashBits = memLevel + 7;
		hashSize = 1 << hashBits;
		hashMask = hashSize - 1;
		hashShift = (hashBits + MIN_MATCH - 1) / MIN_MATCH;
		window = new byte[wSize * 2];
		prev = new int[wSize];
		head = new int[hashSize];
		litBufSize = 1 << (memLevel + 6);
		pendingBuf = new byte[litBufSize * 4];
		pendingBufSize = litBufSize * 4;
		dBuf = litBufSize / 2;
		lBuf = (1 + 2) * litBufSize;
		this.level = level;
		this.strategy = strategy;
		return deflateReset();
	}

	private int longestMatch(int curMatch)
	{
		byte scanEnd;
		byte scanEnd1;
		int chainLength = maxChainLength;
		int scan = strStart;
		int match;
		int len;
		int bestLen = prevLength;
		int limit = strStart > wSize - MIN_LOOKAHEAD ? strStart - (wSize - MIN_LOOKAHEAD) : 0;
		int niceMatchLocal = this.niceMatch;
		int wmask = this.wMask;
		int strend = strStart + MAX_MATCH;
		scanEnd1 = window[scan + bestLen - 1];
		scanEnd = window[scan + bestLen];
		if(prevLength >= goodMatch)
		{
			chainLength >>= 2;
		}
		if(niceMatchLocal > lookahead)
		{
			niceMatchLocal = lookahead;
		}
		do
		{
			match = curMatch;
			if(window[match + bestLen] != scanEnd || window[match + bestLen - 1] != scanEnd1 ||
					window[match] != window[scan] || window[++match] != window[scan + 1])
			{
				continue;
			}
			scan += 2;
			match++;
			do
			{
			} while(window[++scan] == window[++match] && window[++scan] == window[++match] &&
					window[++scan] == window[++match] && window[++scan] == window[++match] &&
					window[++scan] == window[++match] && window[++scan] == window[++match] &&
					window[++scan] == window[++match] && window[++scan] == window[++match] &&
					scan < strend);
			len = MAX_MATCH - (strend - scan);
			scan = strend - MAX_MATCH;
			if(len > bestLen)
			{
				matchStart = curMatch;
				bestLen = len;
				if(len >= niceMatchLocal)
				{
					break;
				}
				scanEnd1 = window[scan + bestLen - 1];
				scanEnd = window[scan + bestLen];
			}
		} while((curMatch = (prev[curMatch & wmask] & 0xffff)) > limit && (--chainLength) != 0);
		return bestLen <= lookahead ? bestLen : lookahead;
	}

	private Object clone()
	{
		Deflate dst = new Deflate(stream);
		dst.pendingBuf = dup(dst.pendingBuf);
		dst.window = dup(dst.window);
		dst.prev = dup(dst.prev);
		dst.head = dup(dst.head);
		dst.dynLtree = dup(dst.dynLtree);
		dst.dynDtree = dup(dst.dynDtree);
		dst.blTree = dup(dst.blTree);
		dst.blCount = dup(dst.blCount);
		dst.heap = dup(dst.heap);
		dst.depth = dup(dst.depth);
		dst.lDesc.dynTree = dst.dynLtree;
		dst.dDesc.dynTree = dst.dynDtree;
		dst.blDesc.dynTree = dst.blTree;
		if(dst.gheader != null)
		{
			dst.gheader = (GZIPHeader) dst.gheader.clone();
		}
		return dst;
	}

	private GZIPHeader getGZIPHeader()
	{
		GZIPHeader result;
		if((result = gheader) == null)
		{
			gheader = result = new GZIPHeader();
		}
		return result;
	}
}
