/*
	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 InfBlocks extends Zlib
{
	private static final int MANY = 1440;
	private static final int TYPE = 0;
	private static final int LENS = 1;
	private static final int STORED = 2;
	private static final int TABLE = 3;
	private static final int BTREE = 4;
	private static final int DTREE = 5;
	private static final int CODES = 6;
	private static final int DRY = 7;
	private static final int DONE = 8;
	private static final int BAD = 9;
	private static final int[] INFLATE_MASK;
	private static final int[] BORDER;

	static
	{
		INFLATE_MASK = new int[] {
				0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff,
				0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
		};
		BORDER = new int[] {
				16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
		};
	}


	public int bitk;
	public int bitb;
	public int end;
	public int read;
	public int write;
	public byte[] window;
	private boolean check;
	private int mode;
	private int left;
	private int table;
	private int index;
	private int last;
	private int[] blens;
	private int[] bb;
	private int[] tb;
	private int[] hufts;
	private InfCodes codes;
	private InfTree inftree;

	InfBlocks(ZStream z, int w)
	{
		this.end = w;
		this.window = new byte[w];
		this.check = z.istate.wrap != 0;
		this.mode = TYPE;
		this.bb = new int[1];
		this.tb = new int[1];
		this.hufts = new int[MANY * 3];
		this.codes = new InfCodes();
		this.inftree = new InfTree();
		reset(z);
	}

	public void reset(ZStream z)
	{
		mode = TYPE;
		bitk = 0;
		bitb = 0;
		read = write = 0;
		if(check)
		{
			z.adler.reset();
		}
	}

	public void free(ZStream z)
	{
		reset(z);
		window = null;
		hufts = null;
	}

	public void setDictionary(byte[] d, int start, int n)
	{
		Array.copy(d, start, window, 0, n);
		read = write = n;
	}

	public int syncPoint()
	{
		return mode == LENS ? 1 : 0;
	}

	public int proc(ZStream z, int r)
	{
		int t;
		int b;
		int k;
		int p;
		int n;
		int q;
		int m;
		p = z.nextInIndex;
		n = z.availIn;
		b = bitb;
		k = bitk;
		q = write;
		m = q < read ? read - q - 1 : end - q;
		do
		{
			switch(mode)
			{
			default:
				r = STREAM_ERROR;
				bitb = b;
				bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				write = q;
				return inflateFlush(z, r);
			case TYPE:
				while(k < 3)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						bitb = b;
						bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						write = q;
						return inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				t = b & 7;
				last = t & 1;
				switch(t >>> 1)
				{
				default:
					break;
				case 0:
					b >>>= 3;
					k -= 3;
					t = k & 7;
					b >>>= t;
					k -= t;
					mode = LENS;
					break;
				case 1:
					int[] bl = new int[1];
					int[] bd = new int[1];
					int[][] tl = new int[1][];
					int[][] td = new int[1][];
					InfTree.inflateTreesFixed(bl, bd, tl, td);
					codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z);
					b >>>= 3;
					k -= 3;
					mode = CODES;
					break;
				case 2:
					b >>>= 3;
					k -= 3;
					mode = TABLE;
					break;
				case 3:
					b >>>= 3;
					k -= 3;
					mode = BAD;
					z.msg = "invalid block type";
					r = DATA_ERROR;
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				break;
			case LENS:
				while(k < 32)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						bitb = b;
						bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						write = q;
						return inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				if((((~b) >>> 16) & 0xffff) != (b & 0xffff))
				{
					mode = BAD;
					z.msg = "invalid stored block lengths";
					r = DATA_ERROR;
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				left = (b & 0xffff);
				b = k = 0;
				mode = left != 0 ? STORED : (last != 0 ? DRY : TYPE);
				break;
			case STORED:
				if(n == 0)
				{
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				if(m == 0)
				{
					if(q == end && read != 0)
					{
						q = 0;
						m = q < read ? read - q - 1 : end - q;
					}
					if(m == 0)
					{
						write = q;
						r = inflateFlush(z, r);
						q = write;
						m = q < read ? read - q - 1 : end - q;
						if(q == end && read != 0)
						{
							q = 0;
							m = q < read ? read - q - 1 : end - q;
						}
						if(m == 0)
						{
							bitb = b;
							bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							write = q;
							return inflateFlush(z, r);
						}
					}
				}
				r = OK;
				t = left;
				if(t > n)
				{
					t = n;
				}
				if(t > m)
				{
					t = m;
				}
				Array.copy(z.nextIn, p, window, q, t);
				p += t;
				n -= t;
				q += t;
				m -= t;
				if((left -= t) != 0)
				{
					break;
				}
				mode = last != 0 ? DRY : TYPE;
				break;
			case TABLE:
				while(k < 14)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						bitb = b;
						bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						write = q;
						return inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				table = t = (b & 0x3fff);
				if((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
				{
					mode = BAD;
					z.msg = "too many length or distance symbols";
					r = DATA_ERROR;
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				t = (t & 0x1f) + ((t >> 5) & 0x1f) + 258;
				if(blens == null || blens.length < t)
				{
					blens = new int[t];
				} else
				{
					for(int i = 0; i < t; i++)
					{
						blens[i] = 0;
					}
				}
				b >>>= 14;
				k -= 14;
				index = 0;
				mode = BTREE;
				/* fall through */
			case BTREE:
				while(index < (table >>> 10) + 4)
				{
					while(k < (3))
					{
						if(n != 0)
						{
							r = OK;
						} else
						{
							bitb = b;
							bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							write = q;
							return inflateFlush(z, r);
						}
						n--;
						b |= (z.nextIn[p++] & 0xff) << k;
						k += 8;
					}
					blens[BORDER[index++]] = b & 7;
					b >>>= 3;
					k -= 3;
				}
				while(index < 19)
				{
					blens[BORDER[index++]] = 0;
				}
				bb[0] = 7;
				t = inftree.inflateTreesBits(blens, bb, tb, hufts, z);
				if(t != OK)
				{
					r = t;
					if(r == DATA_ERROR)
					{
						blens = null;
						mode = BAD;
					}
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				index = 0;
				mode = DTREE;
				/* fall through */
			case DTREE:
				do
				{
					int i;
					int j;
					int c;
					t = table;
					if(index >= (t & 0x1f) + ((t >> 5) & 0x1f) + 258)
					{
						break;
					}
					t = bb[0];
					while(k < t)
					{
						if(n != 0)
						{
							r = OK;
						} else
						{
							bitb = b;
							bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							write = q;
							return inflateFlush(z, r);
						}
						n--;
						b |= (z.nextIn[p++] & 0xff) << k;
						k += 8;
					}
					t = hufts[(tb[0] + (b & INFLATE_MASK[t])) * 3 + 1];
					c = hufts[(tb[0] + (b & INFLATE_MASK[t])) * 3 + 2];
					if(c < 16)
					{
						b >>>= t;
						k -= t;
						blens[index++] = c;
					} else
					{
						i = c == 18 ? 7 : c - 14;
						j = c == 18 ? 11 : 3;
						while(k < t + i)
						{
							if(n != 0)
							{
								r = OK;
							} else
							{
								bitb = b;
								bitk = k;
								z.availIn = n;
								z.totalIn += p - z.nextInIndex;
								z.nextInIndex = p;
								write = q;
								return inflateFlush(z, r);
							}
							n--;
							b |= (z.nextIn[p++] & 0xff) << k;
							k += 8;
						}
						b >>>= t;
						k -= t;
						j += b & INFLATE_MASK[i];
						b >>>= i;
						k -= i;
						i = index;
						t = table;
						if(i + j > (t & 0x1f) + ((t >> 5) & 0x1f) + 258 || (c == 16 && i < 1))
						{
							blens = null;
							mode = BAD;
							z.msg = "invalid bit length repeat";
							r = DATA_ERROR;
							bitb = b;
							bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							write = q;
							return inflateFlush(z, r);
						}
						c = c == 16 ? blens[i - 1] : 0;
						do
						{
							blens[i++] = c;
						} while(--j != 0);
						index = i;
					}
				} while(true);
				tb[0] = -1;
				int[] bl = new int[1];
				int[] bd = new int[1];
				int[] tl = new int[1];
				int[] td = new int[1];
				bl[0] = 9;
				bd[0] = 6;
				t = table;
				t = inftree.inflateTreesDynamic((t & 0x1f) + 257, ((t >> 5) & 0x1f) + 1, blens,
						bl, bd, tl, td, hufts, z);
				if(t != OK)
				{
					if(t == DATA_ERROR)
					{
						blens = null;
						mode = BAD;
					}
					r = t;
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				codes.init(bl[0], bd[0], hufts, tl[0], hufts, td[0], z);
				mode = CODES;
				/* fall through */
			case CODES:
				bitb = b;
				bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				write = q;
				if((r = codes.proc(this, z, r)) != STREAM_END)
				{
					return inflateFlush(z, r);
				}
				r = OK;
				p = z.nextInIndex;
				n = z.availIn;
				b = bitb;
				k = bitk;
				q = write;
				m = q < read ? read - q - 1 : end - q;
				if(last == 0)
				{
					mode = TYPE;
					break;
				}
				mode = DRY;
				/* fall through */
			case DRY:
				write = q;
				r = inflateFlush(z, r);
				q = write;
				m = q < read ? read - q - 1 : end - q;
				if(read != write)
				{
					bitb = b;
					bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					write = q;
					return inflateFlush(z, r);
				}
				mode = DONE;
				/* fall through */
			case DONE:
				r = STREAM_END;
				bitb = b;
				bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				write = q;
				return inflateFlush(z, r);
			case BAD:
				r = DATA_ERROR;
				bitb = b;
				bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				write = q;
				return inflateFlush(z, r);
			}
		} while(true);
	}

	public int inflateFlush(ZStream z, int r)
	{
		int n;
		int p;
		int q;
		p = z.nextOutIndex;
		q = read;
		n = (q <= write ? write : end) - q;
		if(n > z.availOut)
		{
			n = z.availOut;
		}
		if(n != 0 && r == BUF_ERROR)
		{
			r = OK;
		}
		z.availOut -= n;
		z.totalOut += n;
		if(check)
		{
			z.adler.update(window, q, n);
		}
		Array.copy(window, q, z.nextOut, p, n);
		p += n;
		q += n;
		if(q == end)
		{
			q = 0;
			if(write == end)
			{
				write = 0;
			}
			n = write - q;
			if(n > z.availOut)
			{
				n = z.availOut;
			}
			if(n != 0 && r == BUF_ERROR)
			{
				r = OK;
			}
			z.availOut -= n;
			z.totalOut += n;
			if(check)
			{
				z.adler.update(window, q, n);
			}
			Array.copy(window, q, z.nextOut, p, n);
			p += n;
			q += n;
		}
		z.nextOutIndex = p;
		read = q;
		return r;
	}
}
