/*
	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 InfCodes extends Zlib
{
	private static final int START = 0;
	private static final int LEN = 1;
	private static final int LENEXT = 2;
	private static final int DIST = 3;
	private static final int DISTEXT = 4;
	private static final int COPY = 5;
	private static final int LIT = 6;
	private static final int WASH = 7;
	private static final int END = 8;
	private static final int BADCODE = 9;
	private static final int[] INFLATE_MASK;

	static
	{
	    INFLATE_MASK = new int[] {
				0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
				0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff,
				0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff,
				0x00007fff, 0x0000ffff
	    };
	}


	private int lbits;
	private int dbits;
	private int mode;
	private int len;
	private int need;
	private int lit;
	private int get;
	private int dist;
	private int treeIndex;
	private int ltreeIndex;
	private int dtreeIndex;
	private int[] tree;
	private int[] ltree;
	private int[] dtree;

	InfCodes()
	{
	}

	public void init(int bl, int bd, int[] tl, int tlIndex, int[] td, int tdIndex, ZStream z)
	{
		mode = START;
		lbits = (byte) bl;
		dbits = (byte) bd;
		ltree = tl;
		ltreeIndex = tlIndex;
		dtree = td;
		dtreeIndex = tdIndex;
		tree = null;
	}

	public int proc(InfBlocks s, ZStream z, int r)
	{
		int j;
		int tindex;
		int e;
		int b = 0;
		int k = 0;
		int p = 0;
		int n;
		int q;
		int m;
		int f;
		p = z.nextInIndex;
		n = z.availIn;
		b = s.bitb;
		k = s.bitk;
		q = s.write;
		m = q < s.read ? s.read - q - 1 : s.end - q;
		do
		{
			switch(mode)
			{
			default:
				r = STREAM_ERROR;
				s.bitb = b;
				s.bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				s.write = q;
				return s.inflateFlush(z, r);
			case START:
				if(m >= 258 && n >= 10)
				{
					s.bitb = b;
					s.bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					s.write = q;
					r = inflateFast(lbits, dbits, ltree, ltreeIndex, dtree, dtreeIndex, s, z);
					p = z.nextInIndex;
					n = z.availIn;
					b = s.bitb;
					k = s.bitk;
					q = s.write;
					m = q < s.read ? s.read - q - 1 : s.end - q;
					if(r != OK)
					{
						mode = r == STREAM_END ? WASH : BADCODE;
						break;
					}
				}
				need = lbits;
				tree = ltree;
				treeIndex = ltreeIndex;
				mode = LEN;
				/* fall through */
			case LEN:
				j = need;
				while(k < j)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						s.bitb = b;
						s.bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						s.write = q;
						return s.inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				tindex = (treeIndex + (b & INFLATE_MASK[j])) * 3;
				b >>>= tree[tindex + 1];
				k -= tree[tindex + 1];
				e = tree[tindex];
				if(e == 0)
				{
					lit = tree[tindex + 2];
					mode = LIT;
					break;
				}
				if((e & 16) != 0)
				{
					get = e & 15;
					len = tree[tindex + 2];
					mode = LENEXT;
					break;
				}
				if((e & 64) == 0)
				{
					need = e;
					treeIndex = tindex / 3 + tree[tindex + 2];
					break;
				}
				if((e & 32) != 0)
				{
					mode = WASH;
					break;
				}
				mode = BADCODE;
				z.msg = "invalid literal/length code";
				r = DATA_ERROR;
				s.bitb = b;
				s.bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				s.write = q;
				return s.inflateFlush(z, r);
			case LENEXT:
				j = get;
				while(k < j)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						s.bitb = b;
						s.bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						s.write = q;
						return s.inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				len += (b & INFLATE_MASK[j]);
				b >>= j;
				k -= j;
				need = dbits;
				tree = dtree;
				treeIndex = dtreeIndex;
				mode = DIST;
				/* fall through */
			case DIST:
				j = need;
				while(k < j)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						s.bitb = b;
						s.bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						s.write = q;
						return s.inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				tindex = (treeIndex + (b & INFLATE_MASK[j])) * 3;
				b >>= tree[tindex + 1];
				k -= tree[tindex + 1];
				e = (tree[tindex]);
				if((e & 16) != 0)
				{
					get = e & 15;
					dist = tree[tindex + 2];
					mode = DISTEXT;
					break;
				}
				if((e & 64) == 0)
				{
					need = e;
					treeIndex = tindex / 3 + tree[tindex + 2];
					break;
				}
				mode = BADCODE;
				z.msg = "invalid distance code";
				r = DATA_ERROR;
				s.bitb = b;
				s.bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				s.write = q;
				return s.inflateFlush(z, r);
			case DISTEXT:
				j = get;
				while(k < j)
				{
					if(n != 0)
					{
						r = OK;
					} else
					{
						s.bitb = b;
						s.bitk = k;
						z.availIn = n;
						z.totalIn += p - z.nextInIndex;
						z.nextInIndex = p;
						s.write = q;
						return s.inflateFlush(z, r);
					}
					n--;
					b |= (z.nextIn[p++] & 0xff) << k;
					k += 8;
				}
				dist += b & INFLATE_MASK[j];
				b >>= j;
				k -= j;
				mode = COPY;
				/* fall through */
			case COPY:
				f = q - dist;
				while(f < 0)
				{
					f += s.end;
				}
				while(len != 0)
				{
					if(m == 0)
					{
						if(q == s.end && s.read != 0)
						{
							q = 0;
							m = q < s.read ? s.read - q - 1 : s.end - q;
						}
						if(m == 0)
						{
							s.write = q;
							r = s.inflateFlush(z, r);
							q = s.write;
							m = q < s.read ? s.read - q - 1 : s.end - q;
							if(q == s.end && s.read != 0)
							{
								q = 0;
								m = q < s.read ? s.read - q - 1 : s.end - q;
							}
							if(m == 0)
							{
								s.bitb = b;
								s.bitk = k;
								z.availIn = n;
								z.totalIn += p - z.nextInIndex;
								z.nextInIndex = p;
								s.write = q;
								return s.inflateFlush(z, r);
							}
						}
					}
					s.window[q++] = s.window[f++];
					m--;
					if(f == s.end)
					{
						f = 0;
					}
					len--;
				}
				mode = START;
				break;
			case LIT:
				if(m == 0)
				{
					if(q == s.end && s.read != 0)
					{
						q = 0;
						m = q < s.read ? s.read - q - 1 : s.end - q;
					}
					if(m == 0)
					{
						s.write = q;
						r = s.inflateFlush(z, r);
						q = s.write;
						m = q < s.read ? s.read - q - 1 : s.end - q;
						if(q == s.end && s.read != 0)
						{
							q = 0;
							m = q < s.read ? s.read - q - 1 : s.end - q;
						}
						if(m == 0)
						{
							s.bitb = b;
							s.bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							s.write = q;
							return s.inflateFlush(z, r);
						}
					}
				}
				r = OK;
				s.window[q++] = (byte) lit;
				m--;
				mode = START;
				break;
			case WASH:
				if(k > 7)
				{
					k -= 8;
					n++;
					p--;
				}
				s.write = q;
				r = s.inflateFlush(z, r);
				q = s.write;
				m = q < s.read ? s.read - q - 1 : s.end - q;
				if(s.read != s.write)
				{
					s.bitb = b;
					s.bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					s.write = q;
					return s.inflateFlush(z, r);
				}
				mode = END;
				/* fall through */
			case END:
				r = STREAM_END;
				s.bitb = b;
				s.bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				s.write = q;
				return s.inflateFlush(z, r);
			case BADCODE:
				r = DATA_ERROR;
				s.bitb = b;
				s.bitk = k;
				z.availIn = n;
				z.totalIn += p - z.nextInIndex;
				z.nextInIndex = p;
				s.write = q;
				return s.inflateFlush(z, r);
			}
		} while(true);
	}

	private int inflateFast(int bl, int bd, int[] tl, int tlIndex, int[] td, int tdIndex,
			InfBlocks s, ZStream z)
	{
		int t;
		int tpIndex;
		int e;
		int b;
		int k;
		int p;
		int n;
		int q;
		int m;
		int ml;
		int md;
		int c;
		int d;
		int r;
		int tpIndexT3;
		int[] tp;
		p = z.nextInIndex;
		n = z.availIn;
		b = s.bitb;
		k = s.bitk;
		q = s.write;
		m = q < s.read ? s.read - q - 1 : s.end - q;
		ml = INFLATE_MASK[bl];
		md = INFLATE_MASK[bd];
		do
		{
			while(k < 20)
			{
				n--;
				b |= (z.nextIn[p++] & 0xff) << k;
				k += 8;
			}
			t = b & ml;
			tp = tl;
			tpIndex = tlIndex;
			tpIndexT3 = (tpIndex + t) * 3;
			if((e = tp[tpIndexT3]) == 0)
			{
				b >>= (tp[tpIndexT3 + 1]);
				k -= (tp[tpIndexT3 + 1]);
				s.window[q++] = (byte) tp[tpIndexT3 + 2];
				m--;
				continue;
			}
			do
			{
				b >>= tp[tpIndexT3 + 1];
				k -= tp[tpIndexT3 + 1];
				if((e & 16) != 0)
				{
					e &= 15;
					c = tp[tpIndexT3 + 2] + (b & INFLATE_MASK[e]);
					b >>= e;
					k -= e;
					while(k < 15)
					{
						n--;
						b |= (z.nextIn[p++] & 0xff) << k;
						k += 8;
					}
					t = b & md;
					tp = td;
					tpIndex = tdIndex;
					tpIndexT3 = (tpIndex + t) * 3;
					e = tp[tpIndexT3];
					do
					{
						b >>= tp[tpIndexT3 + 1];
						k -= tp[tpIndexT3 + 1];
						if((e & 16) != 0)
						{
							e &= 15;
							while(k < e)
							{
								n--;
								b |= (z.nextIn[p++] & 0xff) << k;
								k += 8;
							}
							d = tp[tpIndexT3 + 2] + (b & INFLATE_MASK[e]);
							b >>= e;
							k -= e;
							m -= c;
							if(q >= d)
							{
								r = q - d;
								if(q - r > 0 && q - r < 2)
								{
									s.window[q++] = s.window[r++];
									s.window[q++] = s.window[r++];
									c -= 2;
								} else
								{
									Array.copy(s.window, r, s.window, q, 2);
									q += 2;
									r += 2;
									c -= 2;
								}
							} else
							{
								r = q - d;
								do
								{
									r += s.end;
								} while(r < 0);
								e = s.end - r;
								if(c > e)
								{
									c -= e;
									if(q - r > 0 && e > q - r)
									{
										do
										{
											s.window[q++] = s.window[r++];
										} while(--e != 0);
									} else
									{
										Array.copy(s.window, r, s.window, q, e);
										q += e;
										r += e;
										e = 0;
									}
									r = 0;
								}
							}
							if(q - r > 0 && c > q - r)
							{
								do
								{
									s.window[q++] = s.window[r++];
								} while(--c != 0);
							} else
							{
								Array.copy(s.window, r, s.window, q, c);
								q += c;
								r += c;
								c = 0;
							}
							break;
						}
						else if((e & 64) == 0)
						{
							t += tp[tpIndexT3 + 2];
							t += (b & INFLATE_MASK[e]);
							tpIndexT3 = (tpIndex + t) * 3;
							e = tp[tpIndexT3];
						}
						else
						{
							z.msg = "invalid distance code";
							c = z.availIn - n;
							c = (k >> 3) < c ? k >> 3 : c;
							n += c;
							p -= c;
							k -= c << 3;
							s.bitb = b;
							s.bitk = k;
							z.availIn = n;
							z.totalIn += p - z.nextInIndex;
							z.nextInIndex = p;
							s.write = q;
							return DATA_ERROR;
						}
					} while(true);
					break;
				}
				if((e & 64) == 0)
				{
					t += tp[tpIndexT3 + 2];
					t += (b & INFLATE_MASK[e]);
					tpIndexT3 = (tpIndex + t) * 3;
					if((e = tp[tpIndexT3]) == 0)
					{
						b >>= tp[tpIndexT3 + 1];
						k -= tp[tpIndexT3 + 1];
						s.window[q++] = (byte) tp[tpIndexT3 + 2];
						m--;
						break;
					}
				}
				else if((e & 32) != 0)
				{
					c = z.availIn - n;
					c = (k >> 3) < c ? k >> 3 : c;
					n += c;
					p -= c;
					k -= c << 3;
					s.bitb = b;
					s.bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					s.write = q;
					return STREAM_END;
				}
				else
				{
					z.msg = "invalid literal/length code";
					c = z.availIn - n;
					c = (k >> 3) < c ? k >> 3 : c;
					n += c;
					p -= c;
					k -= c << 3;
					s.bitb = b;
					s.bitk = k;
					z.availIn = n;
					z.totalIn += p - z.nextInIndex;
					z.nextInIndex = p;
					s.write = q;
					return DATA_ERROR;
				}
			} while(true);
		} while(m >= 258 && n >= 10);
		c = z.availIn - n;
		c = (k >> 3) < c ? k >> 3 : c;
		n += c;
		p -= c;
		k -= c << 3;
		s.bitb = b;
		s.bitk = k;
		z.availIn = n;
		z.totalIn += p - z.nextInIndex;
		z.nextInIndex = p;
		s.write = q;
		return OK;
	}
}
