/*
	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;

import java.io.*;

final class Inflate extends Zlib
{
	private static final int PRESET_DICT = 0x20;
	private static final int Z_DEFLATED = 8;
	private static final int DICT4 = 2;
	private static final int DICT3 = 3;
	private static final int DICT2 = 4;
	private static final int DICT1 = 5;
	private static final int DICT0 = 6;
	private static final int BLOCKS = 7;
	private static final int CHECK4 = 8;
	private static final int CHECK3 = 9;
	private static final int CHECK2 = 10;
	private static final int CHECK1 = 11;
	private static final int DONE = 12;
	private static final int BAD = 13;
	private static final int HEAD = 14;
	private static final int LENGTH = 15;
	private static final int TIME = 16;
	private static final int OS = 17;
	private static final int EXLEN = 18;
	private static final int EXTRA = 19;
	private static final int NAME = 20;
	private static final int COMMENT = 21;
	private static final int HCRC = 22;
	private static final int FLAGS = 23;
	private static final byte[] MARK;

    static
    {
        MARK = new byte[] {
        		0, 0, -1, -1
        };
    }


    public int mode;
    public int wrap;
	private boolean empty;
	private int method;
	private int marker;
	private int wbits;
	private int was;
	private int need;
	private int flags;
	private int needBytes;
	private byte[] crcbuf;
	private ByteArrayOutputStream tmpString;
	private InfBlocks blocks;
	private GZIPHeader gheader;
	private ZStream stream;

	Inflate(ZStream stream)
	{
		this.needBytes = -1;
		this.was = -1;
		this.crcbuf = new byte[4];
		this.stream = stream;
	}

	public int inflateEnd()
	{
		if(blocks != null)
		{
			blocks.free(stream);
		}
		return OK;
	}

	public int inflateInit(int w)
	{
		stream.msg = null;
		blocks = null;
		wrap = 0;
		if(w < 0)
		{
			w = -w;
		} else
		{
			wrap = (w >> 4) + 1;
			if(w < 48)
			{
				w &= 15;
			}
		}
		if(w < 8 || w > 15)
		{
			inflateEnd();
			return STREAM_ERROR;
		}
		if(blocks != null && wbits != w)
		{
			blocks.free(stream);
			blocks = null;
		}
		wbits = w;
		blocks = new InfBlocks(stream, 1 << w);
		inflateReset();
		return OK;
	}

	public int inflate(int f)
	{
		int r;
		int b;
		if(stream == null || stream.nextIn == null)
		{
			return f == FINISH && mode == HEAD ? OK : STREAM_ERROR;
		}
		f = f == FINISH ? BUF_ERROR : OK;
		r = BUF_ERROR;
		do
		{
			switch(mode)
			{
			default:
				return STREAM_ERROR;
			case HEAD:
				if(wrap == 0)
				{
					mode = BLOCKS;
					break;
				}
				r = readBytes(stream, 2, r, f);
				if(empty)
				{
					return r;
				}
				if((wrap & 2) != 0 && need == 0x8b1fL)
				{
					stream.adler = new CRC32();
					checksum(2, need);
					if(gheader == null)
					{
						gheader = new GZIPHeader();
					}
					mode = FLAGS;
					break;
				}
				flags = 0;
				method = need & 0xff;
				b = (need >> 8) & 0xff;
				if((wrap & 1) == 0 || (((method << 8) + b) % 31) != 0)
				{
					mode = BAD;
					stream.msg = "incorrect header check";
					break;
				}
				if((method & 0xf) != Z_DEFLATED)
				{
					mode = BAD;
					stream.msg = "unknown compression method";
					break;
				}
				if((method >> 4) + 8 > wbits)
				{
					mode = BAD;
					stream.msg = "invalid window size";
					break;
				}
				stream.adler = new Adler32();
				if((b & PRESET_DICT) == 0)
				{
					mode = BLOCKS;
					break;
				}
				mode = DICT4;
				/* fall through */
			case DICT4:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need = ((stream.nextIn[stream.nextInIndex++] & 0xff) << 24) & 0xff000000;
				mode = DICT3;
				/* fall through */
			case DICT3:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += ((stream.nextIn[stream.nextInIndex++] & 0xff) << 16) & 0xff0000;
				mode = DICT2;
				/* fall through */
			case DICT2:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += ((stream.nextIn[stream.nextInIndex++] & 0xff) << 8) & 0xff00;
				mode = DICT1;
				/* fall through */
			case DICT1:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += (stream.nextIn[stream.nextInIndex++] & 0xffL);
				stream.adler.reset(need);
				mode = DICT0;
				return NEED_DICT;
			case DICT0:
				mode = BAD;
				stream.msg = "need dictionary";
				marker = 0;
				return STREAM_ERROR;
			case BLOCKS:
				r = blocks.proc(stream, r);
				if(r == DATA_ERROR)
				{
					mode = BAD;
					marker = 0;
					break;
				}
				if(r == OK)
				{
					r = f;
				}
				if(r != STREAM_END)
				{
					return r;
				}
				r = f;
				was = stream.adler.getValue();
				blocks.reset(stream);
				if(wrap == 0)
				{
					mode = DONE;
					break;
				}
				mode = CHECK4;
				/* fall through */
			case CHECK4:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need = ((stream.nextIn[stream.nextInIndex++] & 0xff) << 24) & 0xff000000;
				mode = CHECK3;
				/* fall through */
			case CHECK3:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += ((stream.nextIn[stream.nextInIndex++] & 0xff) << 16) & 0xff0000;
				mode = CHECK2;
				/* fall through */
			case CHECK2:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += ((stream.nextIn[stream.nextInIndex++] & 0xff) << 8) & 0xff00;
				mode = CHECK1;
				/* fall through */
			case CHECK1:
				if(stream.availIn == 0)
				{
					return r;
				}
				r = f;
				stream.availIn--;
				stream.totalIn++;
				need += (stream.nextIn[stream.nextInIndex++] & 0xff);
				if(flags != 0)
				{
					need = ((need & 0xff000000) >>> 24) | ((need & 0x00ff0000) >>> 8)
							| ((need & 0x0000ff00) << 8) | ((need & 0x000000ff) << 24);
				}
				if(was != need)
				{
					stream.msg = "incorrect data check";
				}
				else if(flags != 0 && gheader != null)
				{
					gheader.crc = need;
				}
				mode = LENGTH;
				/* fall through */
			case LENGTH:
				if(wrap != 0 && flags != 0)
				{
					r = readBytes(stream, 4, r, f);
					if(empty)
					{
						return r;
					}
					if(stream.msg != null && stream.msg.equals("incorrect data check"))
					{
						mode = BAD;
						marker = 5;
						break;
					}
					if(need != (stream.totalOut & 0xffffffffL))
					{
						stream.msg = "incorrect length check";
						mode = BAD;
						break;
					}
					stream.msg = null;
				} else
				{
					if(stream.msg != null && stream.msg.equals("incorrect data check"))
					{
						mode = BAD;
						marker = 5;
						break;
					}
				}
				mode = DONE;
				/* fall through */
			case DONE:
				return STREAM_END;
			case BAD:
				return DATA_ERROR;
			case FLAGS:
				r = readBytes(stream, 2, r, f);
				if(empty)
				{
					return r;
				}
				flags = need & 0xffff;
				if((flags & 0xff) != Z_DEFLATED)
				{
					stream.msg = "unknown compression method";
					mode = BAD;
					break;
				}
				if((flags & 0xe000) != 0)
				{
					stream.msg = "unknown header flags set";
					mode = BAD;
					break;
				}
				if((flags & 0x0200) != 0)
				{
					checksum(2, need);
				}
				mode = TIME;
				/* fall through */
			case TIME:
				r = readBytes(stream, 4, r, f);
				if(empty)
				{
					return r;
				}
				if(gheader != null)
				{
					gheader.time = need;
				}
				if((flags & 0x0200) != 0)
				{
					checksum(4, need);
				}
				mode = OS;
				/* fall through */
			case OS:
				r = readBytes(stream, 2, r, f);
				if(empty)
				{
					return r;
				}
				if(gheader != null)
				{
					gheader.xflags = need & 0xff;
					gheader.os = (need >> 8) & 0xff;
				}
				if((flags & 0x0200) != 0)
				{
					checksum(2, need);
				}
				mode = EXLEN;
				/* fall through */
			case EXLEN:
				if((flags & 0x0400) != 0)
				{
					r = readBytes(stream, 2, r, f);
					if(empty)
					{
						return r;
					}
					if(gheader != null)
					{
						gheader.extra = new byte[need & 0xffff];
					}
					if((flags & 0x0200) != 0)
					{
						checksum(2, need);
					}
				}
				else if(gheader != null)
				{
					gheader.extra = null;
				}
				mode = EXTRA;
				/* fall through */
			case EXTRA:
				if((flags & 0x0400) != 0)
				{
					r = readBytes(stream, r, f);
					if(empty)
					{
						return r;
					}
					if(gheader != null)
					{
						byte[] foo = tmpString.toByteArray();
						tmpString = null;
						if(foo.length == gheader.extra.length)
						{
							Array.copy(foo, 0, gheader.extra, 0, foo.length);
						} else
						{
							stream.msg = "bad extra field length";
							mode = BAD;
							break;
						}
					}
				}
				else if(gheader != null)
				{
					gheader.extra = null;
				}
				mode = NAME;
				/* fall through */
			case NAME:
				if((flags & 0x0800) != 0)
				{
					r = readString(stream, r, f);
					if(empty)
					{
						return r;
					}
					if(gheader != null)
					{
						gheader.name = tmpString.toByteArray();
					}
					tmpString = null;
				}
				else if(gheader != null)
				{
					gheader.name = null;
				}
				mode = COMMENT;
				/* fall through */
			case COMMENT:
				if((flags & 0x1000) != 0)
				{
					r = readString(stream, r, f);
					if(empty)
					{
						return r;
					}
					if(gheader != null)
					{
						gheader.comment = tmpString.toByteArray();
					}
					tmpString = null;
				}
				else if(gheader != null)
				{
					gheader.comment = null;
				}
				mode = HCRC;
				/* fall through */
			case HCRC:
				if((flags & 0x0200) != 0)
				{
					r = readBytes(stream, 2, r, f);
					if(empty)
					{
						return r;
					}
					if(gheader != null)
					{
						gheader.hcrc = need & 0xffff;
					}
					if(need != (stream.adler.getValue() & 0xffffL))
					{
						mode = BAD;
						stream.msg = "header crc mismatch";
						marker = 5;
						break;
					}
				}
				stream.adler = new CRC32();
				mode = BLOCKS;
				break;
			}
		} while(true);
	}

	public int inflateSetDictionary(byte[] dictionary, int dictLength)
	{
		int index;
		int length;
		int adlerNeed;
		if(stream == null || (mode != DICT0 && wrap != 0))
		{
			return STREAM_ERROR;
		}
		index = 0;
		length = dictLength;
		if(mode == DICT0)
		{
			adlerNeed = stream.adler.getValue();
			stream.adler.reset();
			stream.adler.update(dictionary, 0, dictLength);
			if(stream.adler.getValue() != adlerNeed)
			{
				return DATA_ERROR;
			}
		}
		stream.adler.reset();
		if(length >= (1 << wbits))
		{
			length = (1 << wbits) - 1;
			index = dictLength - length;
		}
		blocks.setDictionary(dictionary, index, length);
		mode = BLOCKS;
		return OK;
	}

	public int inflateSync()
	{
		int n;
		int p;
		int m;
		long r;
		long w;
		if(stream == null)
		{
			return STREAM_ERROR;
		}
		if(mode != BAD)
		{
			mode = BAD;
			marker = 0;
		}
		if((n = stream.availIn) == 0)
		{
			return BUF_ERROR;
		}
		p = stream.nextInIndex;
		m = marker;
		while(n != 0 && m < 4)
		{
			if(stream.nextIn[p] == MARK[m])
			{
				m++;
			}
			else if(stream.nextIn[p] != 0)
			{
				m = 0;
			}
			else
			{
				m = 4 - m;
			}
			p++;
			n--;
		}
		stream.totalIn += p - stream.nextInIndex;
		stream.nextInIndex = p;
		stream.availIn = n;
		marker = m;
		if(m != 4)
		{
			return DATA_ERROR;
		}
		r = stream.totalIn;
		w = stream.totalOut;
		inflateReset();
		stream.totalIn = r;
		stream.totalOut = w;
		mode = BLOCKS;
		return OK;
	}

	public int inflateSyncPoint()
	{
		if(stream == null || blocks == null)
		{
			return STREAM_ERROR;
		}
		return blocks.syncPoint();
	}

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

	private int inflateReset()
	{
		if(stream == null)
		{
			return STREAM_ERROR;
		}
		stream.totalIn = stream.totalOut = 0;
		stream.msg = null;
		mode = HEAD;
		needBytes = -1;
		blocks.reset(stream);
		return OK;
	}

	private int readBytes(ZStream z, int n, int r, int f)
	{
		if(needBytes == -1)
		{
			needBytes = n;
			need = 0;
		}
		while(needBytes > 0)
		{
			if(z.availIn == 0)
			{
				empty = true;
				return r;
			}
			r = f;
			z.availIn--;
			z.totalIn++;
			need = need | ((z.nextIn[z.nextInIndex++] & 0xff) << ((n - needBytes) * 8));
			needBytes--;
		}
		if(n == 2)
		{
			need &= 0xffffL;
		}
		else if(n == 4)
		{
			need &= 0xffffffff;
		}
		needBytes = -1;
		empty = false;
		return r;
	}

	private int readBytes(ZStream z, int r, int f)
	{
		if(tmpString == null)
		{
			tmpString = new ByteArrayOutputStream();
		}
		while(need > 0)
		{
			if(z.availIn == 0)
			{
				empty = true;
				return r;
			}
			r = f;
			z.availIn--;
			z.totalIn++;
			tmpString.write(z.nextIn, z.nextInIndex, 1);
			z.adler.update(z.nextIn, z.nextInIndex, 1);
			z.nextInIndex++;
			need--;
		}
		empty = false;
		return r;
	}

	private int readString(ZStream z, int r, int f)
	{
		if(tmpString == null)
		{
			tmpString = new ByteArrayOutputStream();
		}
		int b = 0;
		do
		{
			if(z.availIn == 0)
			{
				empty = true;
				return r;
			}
			r = f;
			z.availIn--;
			z.totalIn++;
			b = z.nextIn[z.nextInIndex];
			if(b != 0)
			{
				tmpString.write(z.nextIn, z.nextInIndex, 1);
			}
			z.adler.update(z.nextIn, z.nextInIndex, 1);
			z.nextInIndex++;
		} while(b != 0);
		empty = false;
		return r;
	}
}
