/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 31.05.2007
 *
 * Source: $HeadURL$
 * Last changed: $LastChangedDate$
 * 
 * the unrar licence applies to all junrar source and binary distributions 
 * you are not allowed to use this source to re-create the RAR compression algorithm
 * 
 * Here some html entities which can be used for escaping javadoc tags:
 * "&":  "&#038;" or "&amp;"
 * "<":  "&#060;" or "&lt;"
 * ">":  "&#062;" or "&gt;"
 * "@":  "&#064;" 
 */
package com.innosystec.unrar.unpack;

import com.classpath.util.Arrays;
import com.classpath.util.ByteCache;
import java.io.IOException;
import java.util.Vector;

import com.innosystec.unrar.exception.RarException;
import com.innosystec.unrar.unpack.decode.Compress;
import com.innosystec.unrar.unpack.ppm.BlockTypes;
import com.innosystec.unrar.unpack.ppm.ModelPPM;
import com.innosystec.unrar.unpack.ppm.SubAllocator;
import com.innosystec.unrar.unpack.vm.BitInput;
import com.innosystec.unrar.unpack.vm.RarVM;
import com.innosystec.unrar.unpack.vm.VMPreparedProgram;

/**
 * DOCUMENT ME
 * 
 * @author $LastChangedBy$
 * @version $LastChangedRevision$
 */
public final class Unpack extends Unpack20
{
	public static int BUFSIZE = 0x10000;

	private final ModelPPM ppm = new ModelPPM();
	private int ppmEscChar;
	private RarVM rarVM = new RarVM();

	/* Filters code, one entry per filter */
	private Vector filters = new Vector();

	/* Filters stack, several entrances of same filter are possible */
	private Vector prgStack = new Vector();

	/*
	 * lengths of preceding blocks, one length per filter. Used to reduce size
	 * required to write block length if lengths are repeating
	 */
	private Vector oldFilterLengths = new Vector();
	private int lastFilter;
	private boolean tablesRead;
	private byte[] unpOldTable = new byte[Compress.HUFF_TABLE_SIZE];
	private int unpBlockType;
	private boolean externalWindow;
	private long writtenFileSize;
	private boolean fileExtracted;
	private boolean ppmError;
	private int prevLowDist;
	private int lowDistRepCount;
	public static int[] DBitLengthCounts =
	{
		4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
		2, 2, 2, 2, 2, 14, 0, 12
	};

	public Unpack(ComprDataIO DataIO)
	{
		unpIO = DataIO;
		window = null;
		externalWindow = false;
		suspended = false;
		unpAllBuf = false;
		unpSomeRead = false;
	}

	public void init(byte[] extwnd)
	{
		if(extwnd == null)
		{
			Compress.CURWINSIZE = Compress.MAXWINSIZE;

			do
			{
				try
				{
					window = new byte[Compress.CURWINSIZE];
				}
				catch(OutOfMemoryError oome)
				{
					if(Compress.CURWINSIZE > Compress.MINWINSIZE)
					{
						Compress.CURWINSIZE >>>= 1;
					}
					else
					{
						throw new RuntimeException("Consider this as OutOfMemoryError");
					}
				}
			}
			while(window == null);

			Compress.CURWINMASK = (Compress.CURWINSIZE-1);
		}
		else
		{
			window = extwnd;
			externalWindow = true;
		}
		inAddr = 0;
		unpInitData(false);
	}

	public void doUnpack(int method, boolean solid) throws IOException, RarException
	{
		if(unpIO.getSubHeader().getUnpMethod() == 0x30)
		{
			unstoreFile();
		}
		switch(method)
		{
			case 15: // rar 1.5 compression
				unpack15(solid);
				break;
			case 20: // rar 2.x compression
			case 26: // files larger than 2GB
				unpack20(solid);
				break;
			case 29: // rar 3.x compression
			case 36: // alternative hash
				unpack29(solid);
				break;
		}
	}

	private void unstoreFile() throws IOException, RarException
	{
		byte[] buffer = null;

		buffer = new byte[BUFSIZE];

		while(true)
		{
			int code = unpIO.unpRead(buffer, 0, (int) Math.min(buffer.length, destUnpSize));
			if(code == 0 || code == -1)
			{
				break;
			}
			code = code < destUnpSize ? code : (int) destUnpSize;
			unpIO.unpWrite(buffer, 0, code);
			if(destUnpSize >= 0)
			{
				destUnpSize -= code;
			}
		}

	}

	private void unpack29(boolean solid) throws IOException, RarException
	{

		int[] DDecode = new int[Compress.DC];
		byte[] DBits = new byte[Compress.DC];

		int Bits;

		if(DDecode[1] == 0)
		{
			int Dist = 0, BitLength = 0, Slot = 0;
			for(int I = 0; I < DBitLengthCounts.length; I++, BitLength++)
			{
				int count = DBitLengthCounts[I];
				for(int J = 0; J < count; J++, Slot++, Dist += (1 << BitLength))
				{
					DDecode[Slot] = Dist;
					DBits[Slot] = (byte) BitLength;
				}
			}
		}

		fileExtracted = true;

		if(!suspended)
		{
			unpInitData(solid);
			if(!unpReadBuf())
			{
				return;
			}
			if((!solid || !tablesRead) && !readTables())
			{
				return;
			}
		}

		if(ppmError)
		{
			return;
		}

		while(true)
		{
			unpPtr &= Compress.CURWINMASK;

			if(inAddr > readBorder)
			{
				if(!unpReadBuf())
				{
					break;
				}
			}
			//System.out.println(((wrPtr - unpPtr) & Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr);
			if(((wrPtr - unpPtr) & Compress.CURWINMASK) < 260
				&& wrPtr != unpPtr)
			{

				UnpWriteBuf();
				if(writtenFileSize > destUnpSize)
				{
					return;
				}
				if(suspended)
				{
					fileExtracted = false;
					return;
				}
			}
			if(unpBlockType == BlockTypes.BLOCK_PPM)
			{
				int Ch = ppm.decodeChar();
				if(Ch == -1)
				{
					ppmError = true;
					break;
				}
				if(Ch == ppmEscChar)
				{
					int NextCh = ppm.decodeChar();
					if(NextCh == 0)
					{
						if(!readTables())
						{
							break;
						}
						continue;
					}
					if(NextCh == 2 || NextCh == -1)
					{
						break;
					}
					if(NextCh == 3)
					{
						if(!readVMCodePPM())
						{
							break;
						}
						continue;
					}
					if(NextCh == 4)
					{
						int Distance = 0, Length = 0;
						boolean failed = false;
						for(int I = 0; I < 4 && !failed; I++)
						{
							int ch = ppm.decodeChar();
							if(ch == -1)
							{
								failed = true;
							}
							else
							{
								if(I == 3)
								{
									// Bug fixed
									Length = ch & 0xff;
								}
								else
								{
									// Bug fixed
									Distance = (Distance << 8) + (ch & 0xff);
								}
							}
						}
						if(failed)
						{
							break;
						}
						copyString(Length + 32, Distance + 2);
						continue;
					}
					if(NextCh == 5)
					{
						int Length = ppm.decodeChar();
						if(Length == -1)
						{
							break;
						}
						copyString(Length + 4, 1);
						continue;
					}
				}
				window[unpPtr++] = (byte) Ch;
				continue;
			}

			int Number = decodeNumber(LD);
			if(Number < 256)
			{
				window[unpPtr++] = (byte) Number;
				continue;
			}
			if(Number >= 271)
			{
				int Length = LDecode[Number -= 271] + 3;
				if((Bits = LBits[Number]) > 0)
				{
					Length += getbits() >>> (16 - Bits);
					addbits(Bits);
				}

				int DistNumber = decodeNumber(DD);
				int Distance = DDecode[DistNumber] + 1;
				if((Bits = DBits[DistNumber]) > 0)
				{
					if(DistNumber > 9)
					{
						if(Bits > 4)
						{
							Distance += ((getbits() >>> (20 - Bits)) << 4);
							addbits(Bits - 4);
						}
						if(lowDistRepCount > 0)
						{
							lowDistRepCount--;
							Distance += prevLowDist;
						}
						else
						{
							int LowDist = decodeNumber(LDD);
							if(LowDist == 16)
							{
								lowDistRepCount = Compress.LOW_DIST_REP_COUNT - 1;
								Distance += prevLowDist;
							}
							else
							{
								Distance += LowDist;
								prevLowDist = LowDist;
							}
						}
					}
					else
					{
						Distance += getbits() >>> (16 - Bits);
						addbits(Bits);
					}
				}

				if(Distance >= 0x2000)
				{
					Length++;
					if(Distance >= 0x40000L)
					{
						Length++;
					}
				}

				insertOldDist(Distance);
				insertLastMatch(Length, Distance);

				copyString(Length, Distance);
				continue;
			}
			if(Number == 256)
			{
				if(!readEndOfBlock())
				{
					break;
				}
				continue;
			}
			if(Number == 257)
			{
				if(!readVMCode())
				{
					break;
				}
				continue;
			}
			if(Number == 258)
			{
				if(lastLength != 0)
				{
					copyString(lastLength, lastDist);
				}
				continue;
			}
			if(Number < 263)
			{
				int DistNum = Number - 259;
				int Distance = oldDist[DistNum];
				for(int I = DistNum; I > 0; I--)
				{
					oldDist[I] = oldDist[I - 1];
				}
				oldDist[0] = Distance;

				int LengthNumber = decodeNumber(RD);
				int Length = LDecode[LengthNumber] + 2;
				if((Bits = LBits[LengthNumber]) > 0)
				{
					Length += getbits() >>> (16 - Bits);
					addbits(Bits);
				}
				insertLastMatch(Length, Distance);
				copyString(Length, Distance);
				continue;
			}
			if(Number < 272)
			{
				int Distance = SDDecode[Number -= 263] + 1;
				if((Bits = SDBits[Number]) > 0)
				{
					Distance += getbits() >>> (16 - Bits);
					addbits(Bits);
				}
				insertOldDist(Distance);
				insertLastMatch(2, Distance);
				copyString(2, Distance);
				continue;
			}
		}
		UnpWriteBuf();

	}

	private void UnpWriteBuf() throws IOException
	{
		int WrittenBorder = wrPtr;
		int WriteSize = (unpPtr - WrittenBorder) & Compress.CURWINMASK;
		for(int I = 0; I < prgStack.size(); I++)
		{
			UnpackFilter flt = (UnpackFilter)prgStack.elementAt(I);
			if(flt == null)
			{
				continue;
			}
			if(flt.isNextWindow())
			{
				flt.setNextWindow(false);// ->NextWindow=false;
				continue;
			}
			int BlockStart = flt.getBlockStart();// ->BlockStart;
			int BlockLength = flt.getBlockLength();// ->BlockLength;
			if(((BlockStart - WrittenBorder) & Compress.CURWINMASK) < WriteSize)
			{
				if(WrittenBorder != BlockStart)
				{
					UnpWriteArea(WrittenBorder, BlockStart);
					WrittenBorder = BlockStart;
					WriteSize = (unpPtr - WrittenBorder) & Compress.CURWINMASK;
				}
				if(BlockLength <= WriteSize)
				{
					int BlockEnd = (BlockStart + BlockLength)
						& Compress.CURWINMASK;
					if(BlockStart < BlockEnd || BlockEnd == 0)
					{
						// VM.SetMemory(0,Window+BlockStart,BlockLength);
						rarVM.setMemory(0, window, BlockStart, BlockLength);
					}
					else
					{
						int FirstPartLength = Compress.CURWINSIZE - BlockStart;
						// VM.SetMemory(0,Window+BlockStart,FirstPartLength);
						rarVM.setMemory(0, window, BlockStart, FirstPartLength);
						// VM.SetMemory(FirstPartLength,Window,BlockEnd);
						rarVM.setMemory(FirstPartLength, window, 0, BlockEnd);

					}

					VMPreparedProgram ParentPrg = ((UnpackFilter)filters.elementAt(flt.getParentFilter())).getPrg();
					VMPreparedProgram Prg = flt.getPrg();

					if(ParentPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
					{
						// copy global data from previous script execution if
						// any
						// Prg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
						// memcpy(&Prg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
						Prg.getGlobalData().setSize(
							ParentPrg.getGlobalData().size());
						for(int i = 0; i < ParentPrg.getGlobalData().size()
							- RarVM.VM_FIXEDGLOBALSIZE; i++)
						{
							Prg.getGlobalData().setElementAt(ParentPrg.getGlobalData().elementAt(RarVM.VM_FIXEDGLOBALSIZE + i), RarVM.VM_FIXEDGLOBALSIZE + i);
						}
					}

					ExecuteCode(Prg);

					if(Prg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
					{
						// save global data for next script execution
						if(ParentPrg.getGlobalData().size() < Prg.getGlobalData().size())
						{
							ParentPrg.getGlobalData().setSize(
								Prg.getGlobalData().size());// ->GlobalData.Alloc(Prg->GlobalData.Size());
						}
						// memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&Prg->GlobalData[VM_FIXEDGLOBALSIZE],Prg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
						for(int i = 0; i < Prg.getGlobalData().size()
							- RarVM.VM_FIXEDGLOBALSIZE; i++)
						{
							ParentPrg.getGlobalData().setElementAt(Prg.getGlobalData().elementAt(RarVM.VM_FIXEDGLOBALSIZE + i), RarVM.VM_FIXEDGLOBALSIZE + i);
						}
					}
					else
					{
						ParentPrg.getGlobalData().removeAllElements();
					}

					int FilteredDataOffset = Prg.getFilteredDataOffset();
					int FilteredDataSize = Prg.getFilteredDataSize();
					byte[] FilteredData = new byte[FilteredDataSize];

					for(int i = 0; i < FilteredDataSize; i++)
					{
						FilteredData[i] = rarVM.getMem()[FilteredDataOffset + i];//Prg.getGlobalData().get(FilteredDataOffset + i);
					}

					prgStack.setElementAt(null, I);
					while(I + 1 < prgStack.size())
					{
						UnpackFilter NextFilter = (UnpackFilter)prgStack.elementAt(I + 1);
						if(NextFilter == null
							|| NextFilter.getBlockStart() != BlockStart
							|| NextFilter.getBlockLength() != FilteredDataSize
							|| NextFilter.isNextWindow())
						{
							break;
						}
						// apply several filters to same data block

						rarVM.setMemory(0, FilteredData, 0, FilteredDataSize);// .SetMemory(0,FilteredData,FilteredDataSize);

						VMPreparedProgram pPrg = ((UnpackFilter)filters.elementAt(NextFilter.getParentFilter())).getPrg();
						VMPreparedProgram NextPrg = NextFilter.getPrg();

						if(pPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
						{
							// copy global data from previous script execution
							// if any
							// NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
							NextPrg.getGlobalData().setSize(
								pPrg.getGlobalData().size());
							// memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
							for(int i = 0; i < pPrg.getGlobalData().size()
								- RarVM.VM_FIXEDGLOBALSIZE; i++)
							{
								NextPrg.getGlobalData().setElementAt(pPrg.getGlobalData().elementAt(RarVM.VM_FIXEDGLOBALSIZE + i), RarVM.VM_FIXEDGLOBALSIZE + i);
							}
						}

						ExecuteCode(NextPrg);

						if(NextPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
						{
							// save global data for next script execution
							if(pPrg.getGlobalData().size() < NextPrg.getGlobalData().size())
							{
								pPrg.getGlobalData().setSize(
									NextPrg.getGlobalData().size());
							}
							// memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
							for(int i = 0; i < NextPrg.getGlobalData().size()
								- RarVM.VM_FIXEDGLOBALSIZE; i++)
							{
								pPrg.getGlobalData().setElementAt(NextPrg.getGlobalData().elementAt(RarVM.VM_FIXEDGLOBALSIZE + i), RarVM.VM_FIXEDGLOBALSIZE + i);
							}
						}
						else
						{
							pPrg.getGlobalData().removeAllElements();
						}
						FilteredDataOffset = NextPrg.getFilteredDataOffset();
						FilteredDataSize = NextPrg.getFilteredDataSize();

						FilteredData = new byte[FilteredDataSize];
						for(int i = 0; i < FilteredDataSize; i++)
						{
							FilteredData[i] = ((Byte)NextPrg.getGlobalData().elementAt(FilteredDataOffset + i)).byteValue();
						}

						I++;
						prgStack.setElementAt(null, I);
					}
					unpIO.unpWrite(FilteredData, 0, FilteredDataSize);
					unpSomeRead = true;
					writtenFileSize += FilteredDataSize;
					WrittenBorder = BlockEnd;
					WriteSize = (unpPtr - WrittenBorder) & Compress.CURWINMASK;
				}
				else
				{
					for(int J = I; J < prgStack.size(); J++)
					{
						UnpackFilter filt = (UnpackFilter)prgStack.elementAt(J);
						if(filt != null && filt.isNextWindow())
						{
							filt.setNextWindow(false);
						}
					}
					wrPtr = WrittenBorder;
					return;
				}
			}
		}

		UnpWriteArea(WrittenBorder, unpPtr);
		wrPtr = unpPtr;

	}

	private void UnpWriteArea(int startPtr, int endPtr) throws IOException
	{
		if(endPtr != startPtr)
		{
			unpSomeRead = true;
		}
		if(endPtr < startPtr)
		{
			UnpWriteData(window, startPtr, -startPtr & Compress.CURWINMASK);
			UnpWriteData(window, 0, endPtr);
			unpAllBuf = true;
		}
		else
		{
			UnpWriteData(window, startPtr, endPtr - startPtr);
		}
	}

	private void UnpWriteData(byte[] data, int offset, int size)
		throws IOException
	{
		if(writtenFileSize >= destUnpSize)
		{
			return;
		}
		int writeSize = size;
		long leftToWrite = destUnpSize - writtenFileSize;
		if(writeSize > leftToWrite)
		{
			writeSize = (int) leftToWrite;
		}
		unpIO.unpWrite(data, offset, writeSize);

		writtenFileSize += size;

	}

	private void insertOldDist(int distance)
	{
		oldDist[3] = oldDist[2];
		oldDist[2] = oldDist[1];
		oldDist[1] = oldDist[0];
		oldDist[0] = distance;
	}

	private void insertLastMatch(int length, int distance)
	{
		lastDist = distance;
		lastLength = length;
	}

	private void copyString(int length, int distance)
	{
//		System.out.println("copyString(" + length + ", " + distance + ")");

		int destPtr = unpPtr - distance;
		//System.out.println(unpPtr+":"+distance);
		if(destPtr >= 0 && destPtr < Compress.CURWINSIZE - 260
			&& unpPtr < Compress.CURWINSIZE - 260)
		{


			window[unpPtr++] = window[destPtr++];

			while(--length > 0)
			{
				window[unpPtr++] = window[destPtr++];
			}
		}
		else
		{
			while(length-- != 0)
			{
				window[unpPtr] = window[destPtr++ & Compress.CURWINMASK];
				unpPtr = (unpPtr + 1) & Compress.CURWINMASK;
			}
		}
	}

	protected void unpInitData(boolean solid)
	{
		if(!solid)
		{
			tablesRead = false;
			Arrays.fill(oldDist, 0); // memset(oldDist,0,sizeof(OldDist));

			oldDistPtr = 0;
			lastDist = 0;
			lastLength = 0;

			Arrays.fill(unpOldTable, (byte) 0);// memset(UnpOldTable,0,sizeof(UnpOldTable));

			unpPtr = 0;
			wrPtr = 0;
			ppmEscChar = 2;

			initFilters();
		}
		InitBitInput();
		ppmError = false;
		writtenFileSize = 0;
		readTop = 0;
		readBorder = 0;
		unpInitData20(solid);
	}

	private void initFilters()
	{
		oldFilterLengths.removeAllElements();
		lastFilter = 0;

		filters.removeAllElements();

		prgStack.removeAllElements();
	}

	private boolean readEndOfBlock() throws IOException, RarException
	{
		int BitField = getbits();
		boolean NewTable, NewFile = false;
		if((BitField & 0x8000) != 0)
		{
			NewTable = true;
			addbits(1);
		}
		else
		{
			NewFile = true;
			NewTable = (BitField & 0x4000) != 0 ? true : false;
			addbits(2);
		}
		tablesRead = !NewTable;
		return !(NewFile || NewTable && !readTables());
	}

	private boolean readTables() throws IOException, RarException
	{
		byte[] bitLength = new byte[Compress.BC];

		byte[] table = new byte[Compress.HUFF_TABLE_SIZE];
		if(inAddr > readTop - 25)
		{
			if(!unpReadBuf())
			{
				return (false);
			}
		}
		faddbits((8 - inBit) & 7);
		long bitField = fgetbits() & 0xffFFffFF;
		if((bitField & 0x8000) != 0)
		{
			unpBlockType = BlockTypes.BLOCK_PPM;
			return (ppm.decodeInit(this, ppmEscChar));
		}
		unpBlockType = BlockTypes.BLOCK_LZ;

		prevLowDist = 0;
		lowDistRepCount = 0;

		if((bitField & 0x4000) == 0)
		{
			Arrays.fill(unpOldTable, (byte) 0);// memset(UnpOldTable,0,sizeof(UnpOldTable));
		}
		faddbits(2);

		for(int i = 0; i < Compress.BC; i++)
		{
			int length = (fgetbits() >>> 12) & 0xFF;
			faddbits(4);
			if(length == 15)
			{
				int zeroCount = (fgetbits() >>> 12) & 0xFF;
				faddbits(4);
				if(zeroCount == 0)
				{
					bitLength[i] = 15;
				}
				else
				{
					zeroCount += 2;
					while(zeroCount-- > 0 && i < bitLength.length)
					{
						bitLength[i++] = 0;
					}
					i--;
				}
			}
			else
			{
				bitLength[i] = (byte) length;
			}
		}

		makeDecodeTables(bitLength, 0, BD, Compress.BC);

		int TableSize = Compress.HUFF_TABLE_SIZE;

		for(int i = 0; i < TableSize;)
		{
			if(inAddr > readTop - 5)
			{
				if(!unpReadBuf())
				{
					return (false);
				}
			}
			int Number = decodeNumber(BD);
			if(Number < 16)
			{
				table[i] = (byte) ((Number + unpOldTable[i]) & 0xf);
				i++;
			}
			else if(Number < 18)
			{
				int N;
				if(Number == 16)
				{
					N = (fgetbits() >>> 13) + 3;
					faddbits(3);
				}
				else
				{
					N = (fgetbits() >>> 9) + 11;
					faddbits(7);
				}
				while(N-- > 0 && i < TableSize)
				{
					table[i] = table[i - 1];
					i++;
				}
			}
			else
			{
				int N;
				if(Number == 18)
				{
					N = (fgetbits() >>> 13) + 3;
					faddbits(3);
				}
				else
				{
					N = (fgetbits() >>> 9) + 11;
					faddbits(7);
				}
				while(N-- > 0 && i < TableSize)
				{
					table[i++] = 0;
				}
			}
		}
		tablesRead = true;
		if(inAddr > readTop)
		{
			return (false);
		}
		makeDecodeTables(table, 0, LD, Compress.NC);
		makeDecodeTables(table, Compress.NC, DD, Compress.DC);
		makeDecodeTables(table, Compress.NC + Compress.DC, LDD, Compress.LDC);
		makeDecodeTables(table, Compress.NC + Compress.DC + Compress.LDC, RD,
			Compress.RC);

		// memcpy(unpOldTable,table,sizeof(unpOldTable));
		for(int i = 0; i < unpOldTable.length; i++)
		{
			unpOldTable[i] = table[i];
		}
		return (true);

	}

	private boolean readVMCode() throws IOException, RarException
	{
		int FirstByte = getbits() >> 8;
		addbits(8);
		int Length = (FirstByte & 7) + 1;
		if(Length == 7)
		{
			Length = (getbits() >> 8) + 7;
			addbits(8);
		}
		else if(Length == 8)
		{
			Length = getbits();
			addbits(16);
		}
		Vector vmCode = new Vector();
		for(int I = 0; I < Length; I++)
		{
			if(inAddr >= readTop - 1 && !unpReadBuf() && I < Length - 1)
			{
				return (false);
			}
			vmCode.addElement(ByteCache.valueOf((byte) (getbits() >> 8)));
			addbits(8);
		}
		return (addVMCode(FirstByte, vmCode, Length));
	}

	private boolean readVMCodePPM() throws IOException, RarException
	{
		int FirstByte = ppm.decodeChar();
		if((int) FirstByte == -1)
		{
			return (false);
		}
		int Length = (FirstByte & 7) + 1;
		if(Length == 7)
		{
			int B1 = ppm.decodeChar();
			if(B1 == -1)
			{
				return (false);
			}
			Length = B1 + 7;
		}
		else if(Length == 8)
		{
			int B1 = ppm.decodeChar();
			if(B1 == -1)
			{
				return (false);
			}
			int B2 = ppm.decodeChar();
			if(B2 == -1)
			{
				return (false);
			}
			Length = B1 * 256 + B2;
		}
		Vector vmCode = new Vector();
		for(int I = 0; I < Length; I++)
		{
			int Ch = ppm.decodeChar();
			if(Ch == -1)
			{
				return (false);
			}
			vmCode.addElement(ByteCache.valueOf((byte) Ch));// VMCode[I]=Ch;
		}
		return (addVMCode(FirstByte, vmCode, Length));
	}

	private boolean addVMCode(int firstByte, Vector vmCode, int length)
	{
		BitInput Inp = new BitInput();
		Inp.InitBitInput();
		// memcpy(Inp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize));
		for(int i = 0; i < Math.min(BitInput.MAX_SIZE, vmCode.size()); i++)
		{
			Inp.getInBuf()[i] = ((Byte)vmCode.elementAt(i)).byteValue();
		}
		rarVM.init();

		int FiltPos;
		if((firstByte & 0x80) != 0)
		{
			FiltPos = RarVM.ReadData(Inp);
			if(FiltPos == 0)
			{
				initFilters();
			}
			else
			{
				FiltPos--;
			}
		}
		else
		{
			FiltPos = lastFilter; // use the same filter as last time
		}
		if(FiltPos > filters.size() || FiltPos > oldFilterLengths.size())
		{
			return (false);
		}
		lastFilter = FiltPos;
		boolean NewFilter = (FiltPos == filters.size());

		UnpackFilter StackFilter = new UnpackFilter(); // new filter for
		// PrgStack

		UnpackFilter Filter;
		if(NewFilter) // new filter code, never used before since VM reset
		{
			// too many different filters, corrupt archive
			if(FiltPos > 1024)
			{
				return (false);
			}

			// Filters[Filters.Size()-1]=Filter=new UnpackFilter;
			Filter = new UnpackFilter();
			filters.addElement(Filter);
			StackFilter.setParentFilter(filters.size() - 1);
			oldFilterLengths.addElement(new Integer(0));
			Filter.setExecCount(0);
		}
		else // filter was used in the past
		{
			Filter = (UnpackFilter)filters.elementAt(FiltPos);
			StackFilter.setParentFilter(FiltPos);
			Filter.setExecCount(Filter.getExecCount() + 1);// ->ExecCount++;
		}

		prgStack.addElement(StackFilter);
		StackFilter.setExecCount(Filter.getExecCount());// ->ExecCount;

		int BlockStart = RarVM.ReadData(Inp);
		if((firstByte & 0x40) != 0)
		{
			BlockStart += 258;
		}
		StackFilter.setBlockStart((BlockStart + unpPtr) & Compress.CURWINMASK);
		if((firstByte & 0x20) != 0)
		{
			StackFilter.setBlockLength(RarVM.ReadData(Inp));
		}
		else
		{
			StackFilter.setBlockLength(FiltPos < oldFilterLengths.size() ? ((Integer)oldFilterLengths.elementAt(FiltPos)).intValue() : 0);
		}
		StackFilter.setNextWindow((wrPtr != unpPtr)
			&& ((wrPtr - unpPtr) & Compress.CURWINMASK) <= BlockStart);

		// DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x
		// BlockStart=%08x",UnpPtr,WrPtr,BlockStart);

		oldFilterLengths.setElementAt(new Integer(StackFilter.getBlockLength()), FiltPos);

		// memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR));
		Arrays.fill(StackFilter.getPrg().getInitR(), 0);
		StackFilter.getPrg().getInitR()[3] = RarVM.VM_GLOBALMEMADDR;// StackFilter->Prg.InitR[3]=VM_GLOBALMEMADDR;
		StackFilter.getPrg().getInitR()[4] = StackFilter.getBlockLength();// StackFilter->Prg.InitR[4]=StackFilter->BlockLength;
		StackFilter.getPrg().getInitR()[5] = StackFilter.getExecCount();// StackFilter->Prg.InitR[5]=StackFilter->ExecCount;

		if((firstByte & 0x10) != 0) // set registers to optional parameters
		// if any
		{
			int InitMask = Inp.fgetbits() >>> 9;
			Inp.faddbits(7);
			for(int I = 0; I < 7; I++)
			{
				if((InitMask & (1 << I)) != 0)
				{
					// StackFilter->Prg.InitR[I]=RarVM::ReadData(Inp);
					StackFilter.getPrg().getInitR()[I] = RarVM.ReadData(Inp);
				}
			}
		}

		if(NewFilter)
		{
			int VMCodeSize = RarVM.ReadData(Inp);
			if(VMCodeSize >= 0x10000 || VMCodeSize == 0)
			{
				return (false);
			}
			byte[] VMCode = new byte[VMCodeSize];
			for(int I = 0; I < VMCodeSize; I++)
			{
				if(Inp.Overflow(3))
				{
					return (false);
				}
				VMCode[I] = (byte) (Inp.fgetbits() >> 8);
				Inp.faddbits(8);
			}
			// VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
			rarVM.prepare(VMCode, VMCodeSize, Filter.getPrg());
		}
		StackFilter.getPrg().setAltCmd(Filter.getPrg().getCmd());// StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0];
		StackFilter.getPrg().setCmdCount(Filter.getPrg().getCmdCount());// StackFilter->Prg.CmdCount=Filter->Prg.CmdCount;

		int StaticDataSize = Filter.getPrg().getStaticData().size();
		if(StaticDataSize > 0 && StaticDataSize < RarVM.VM_GLOBALMEMSIZE)
		{
			// read statically defined data contained in DB commands
			// StackFilter->Prg.StaticData.Add(StaticDataSize);
			StackFilter.getPrg().setStaticData(Filter.getPrg().getStaticData());
			// memcpy(&StackFilter->Prg.StaticData[0],&Filter->Prg.StaticData[0],StaticDataSize);
		}

		if(StackFilter.getPrg().getGlobalData().size() < RarVM.VM_FIXEDGLOBALSIZE)
		{
			// StackFilter->Prg.GlobalData.Reset();
			// StackFilter->Prg.GlobalData.Add(VM_FIXEDGLOBALSIZE);
			StackFilter.getPrg().getGlobalData().removeAllElements();
			StackFilter.getPrg().getGlobalData().setSize(RarVM.VM_FIXEDGLOBALSIZE);
		}

		// byte *GlobalData=&StackFilter->Prg.GlobalData[0];
		Vector globalData = StackFilter.getPrg().getGlobalData();
		for(int I = 0; I < 7; I++)
		{
			rarVM.setLowEndianValue(globalData, I * 4, StackFilter.getPrg().getInitR()[I]);
		}

		// VM.SetLowEndianValue((uint
		// *)&GlobalData[0x1c],StackFilter->BlockLength);
		rarVM.setLowEndianValue(globalData, 0x1c, StackFilter.getBlockLength());
		// VM.SetLowEndianValue((uint *)&GlobalData[0x20],0);
		rarVM.setLowEndianValue(globalData, 0x20, 0);
		rarVM.setLowEndianValue(globalData, 0x24, 0);
		rarVM.setLowEndianValue(globalData, 0x28, 0);

		// VM.SetLowEndianValue((uint
		// *)&GlobalData[0x2c],StackFilter->ExecCount);
		rarVM.setLowEndianValue(globalData, 0x2c, StackFilter.getExecCount());
		// memset(&GlobalData[0x30],0,16);
		for(int i = 0; i < 16; i++)
		{
			globalData.setElementAt(ByteCache.valueOf((byte) (0)), 0x30 + i);
		}
		if((firstByte & 8) != 0) // put data block passed as parameter if any
		{
			if(Inp.Overflow(3))
			{
				return (false);
			}
			int DataSize = RarVM.ReadData(Inp);
			if(DataSize > RarVM.VM_GLOBALMEMSIZE - RarVM.VM_FIXEDGLOBALSIZE)
			{
				return (false);
			}
			int CurSize = StackFilter.getPrg().getGlobalData().size();
			if(CurSize < DataSize + RarVM.VM_FIXEDGLOBALSIZE)
			{
				// StackFilter->Prg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize);
				StackFilter.getPrg().getGlobalData().setSize(
					DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize);
			}
			int offset = RarVM.VM_FIXEDGLOBALSIZE;
			globalData = StackFilter.getPrg().getGlobalData();
			for(int I = 0; I < DataSize; I++)
			{
				if(Inp.Overflow(3))
				{
					return (false);
				}
				globalData.setElementAt(ByteCache.valueOf((byte) (Inp.fgetbits() >>> 8)), offset + I);
				Inp.faddbits(8);
			}
		}
		return (true);
	}

	private void ExecuteCode(VMPreparedProgram Prg)
	{
		if(Prg.getGlobalData().size() > 0)
		{
			// Prg->InitR[6]=int64to32(WrittenFileSize);
			Prg.getInitR()[6] = (int) (writtenFileSize);
			// rarVM.SetLowEndianValue((uint
			// *)&Prg->GlobalData[0x24],int64to32(WrittenFileSize));
			rarVM.setLowEndianValue(Prg.getGlobalData(), 0x24,
				(int) writtenFileSize);
			// rarVM.SetLowEndianValue((uint
			// *)&Prg->GlobalData[0x28],int64to32(WrittenFileSize>>32));
			rarVM.setLowEndianValue(Prg.getGlobalData(), 0x28,
				(int) (writtenFileSize >>> 32));
			rarVM.execute(Prg);
		}
	}

//	Duplicate method
//	private boolean ReadEndOfBlock() throws IOException, RarException
//	{
//	int BitField = getbits();
//	boolean NewTable, NewFile = false;
//	if ((BitField & 0x8000) != 0) {
//	NewTable = true;
//	addbits(1);
//	} else {
//	NewFile = true;
//	NewTable = (BitField & 0x4000) != 0;
//	addbits(2);
//	}
//	tablesRead = !NewTable;
//	return !(NewFile || NewTable && !readTables());
//	}
	public boolean isFileExtracted()
	{
		return fileExtracted;
	}

	public void setDestSize(long destSize)
	{
		this.destUnpSize = destSize;
		this.fileExtracted = false;
	}

	public void setSuspended(boolean suspended)
	{
		this.suspended = suspended;
	}

	public int getChar() throws IOException, RarException
	{
		if(inAddr > BitInput.MAX_SIZE - 30)
		{
			unpReadBuf();
		}
		return (inBuf[inAddr++] & 0xff);
	}

	public int getPpmEscChar()
	{
		return ppmEscChar;
	}

	public void setPpmEscChar(int ppmEscChar)
	{
		this.ppmEscChar = ppmEscChar;
	}

	public void cleanUp()
	{
		if(ppm != null)
		{
			SubAllocator allocator = ppm.getSubAlloc();
			if(allocator != null)
			{
				allocator.stopSubAllocator();
			}
		}
	}
}
