/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 21.06.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 java.io.IOException;

import com.innosystec.unrar.exception.RarException;
import com.innosystec.unrar.unpack.decode.Compress;
import com.innosystec.unrar.unpack.vm.BitInput;

/**
 * DOCUMENT ME
 * 
 * @author $LastChangedBy$
 * @version $LastChangedRevision$
 */
public abstract class Unpack15 extends BitInput
{
	protected int readBorder;
	protected boolean suspended;
	protected boolean unpAllBuf;
	protected ComprDataIO unpIO;
	protected boolean unpSomeRead;
	protected int readTop;
	protected long destUnpSize;
	protected byte[] window;
	protected int[] oldDist = new int[4];
	protected int unpPtr, wrPtr;
	protected int oldDistPtr;
	protected int[] ChSet = new int[256], ChSetA = new int[256],
		ChSetB = new int[256], ChSetC = new int[256];
	protected int[] Place = new int[256], PlaceA = new int[256],
		PlaceB = new int[256], PlaceC = new int[256];
	protected int[] NToPl = new int[256], NToPlB = new int[256],
		NToPlC = new int[256];
	protected int FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3;
	protected int Buf60, NumHuf, StMode, LCount, FlagsCnt;
	protected int Nhfb, Nlzb, MaxDist3;
	protected int lastDist, lastLength;
	private static final int STARTL1 = 2;
	private static int DecL1[] =
	{
		0x8000, 0xa000, 0xc000, 0xd000, 0xe000,
		0xea00, 0xee00, 0xf000, 0xf200, 0xf200, 0xffff
	};
	private static int PosL1[] =
	{
		0, 0, 0, 2, 3, 5, 7, 11, 16, 20, 24, 32, 32
	};
	private static final int STARTL2 = 3;
	private static int DecL2[] =
	{
		0xa000, 0xc000, 0xd000, 0xe000, 0xea00,
		0xee00, 0xf000, 0xf200, 0xf240, 0xffff
	};
	private static int PosL2[] =
	{
		0, 0, 0, 0, 5, 7, 9, 13, 18, 22, 26, 34, 36
	};
	private static final int STARTHF0 = 4;
	private static int DecHf0[] =
	{
		0x8000, 0xc000, 0xe000, 0xf200, 0xf200,
		0xf200, 0xf200, 0xf200, 0xffff
	};
	private static int PosHf0[] =
	{
		0, 0, 0, 0, 0, 8, 16, 24, 33, 33, 33, 33,
		33
	};
	private static final int STARTHF1 = 5;
	private static int DecHf1[] =
	{
		0x2000, 0xc000, 0xe000, 0xf000, 0xf200,
		0xf200, 0xf7e0, 0xffff
	};
	private static int PosHf1[] =
	{
		0, 0, 0, 0, 0, 0, 4, 44, 60, 76, 80, 80,
		127
	};
	private static final int STARTHF2 = 5;
	private static int DecHf2[] =
	{
		0x1000, 0x2400, 0x8000, 0xc000, 0xfa00,
		0xffff, 0xffff, 0xffff
	};
	private static int PosHf2[] =
	{
		0, 0, 0, 0, 0, 0, 2, 7, 53, 117, 233, 0, 0
	};
	private static final int STARTHF3 = 6;
	private static int DecHf3[] =
	{
		0x800, 0x2400, 0xee00, 0xfe80, 0xffff,
		0xffff, 0xffff
	};
	private static int PosHf3[] =
	{
		0, 0, 0, 0, 0, 0, 0, 2, 16, 218, 251, 0, 0
	};
	private static final int STARTHF4 = 8;
	private static int DecHf4[] =
	{
		0xff00, 0xffff, 0xffff, 0xffff, 0xffff,
		0xffff
	};
	private static int PosHf4[] =
	{
		0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0
	};
	static int ShortLen1[] =
	{
		1, 3, 4, 4, 5, 6, 7, 8, 8, 4, 4, 5, 6, 6, 4, 0
	};
	static int ShortXor1[] =
	{
		0, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe,
		0xff, 0xc0, 0x80, 0x90, 0x98, 0x9c, 0xb0
	};
	static int ShortLen2[] =
	{
		2, 3, 3, 3, 4, 4, 5, 6, 6, 4, 4, 5, 6, 6, 4, 0
	};
	static int ShortXor2[] =
	{
		0, 0x40, 0x60, 0xa0, 0xd0, 0xe0, 0xf0, 0xf8,
		0xfc, 0xc0, 0x80, 0x90, 0x98, 0x9c, 0xb0
	};

	protected abstract void unpInitData(boolean solid);

	protected void unpack15(boolean solid) throws IOException, RarException
	{
		if(suspended)
		{
			unpPtr = wrPtr;
		}
		else
		{
			unpInitData(solid);
			oldUnpInitData(solid);
			unpReadBuf();
			if(!solid)
			{
				initHuff();
				unpPtr = 0;
			}
			else
			{
				unpPtr = wrPtr;
			}
			--destUnpSize;
		}
		if(destUnpSize >= 0)
		{
			getFlagsBuf();
			FlagsCnt = 8;
		}

		while(destUnpSize >= 0)
		{
			unpPtr &= Compress.CURWINMASK;

			if(inAddr > readTop - 30 && !unpReadBuf())
			{
				break;
			}
			if(((wrPtr - unpPtr) & Compress.CURWINMASK) < 270
				&& wrPtr != unpPtr)
			{
				oldUnpWriteBuf();
				if(suspended)
				{
					return;
				}
			}
			if(StMode != 0)
			{
				huffDecode();
				continue;
			}

			if(--FlagsCnt < 0)
			{
				getFlagsBuf();
				FlagsCnt = 7;
			}

			if((FlagBuf & 0x80) != 0)
			{
				FlagBuf <<= 1;
				if(Nlzb > Nhfb)
				{
					longLZ();
				}
				else
				{
					huffDecode();
				}
			}
			else
			{
				FlagBuf <<= 1;
				if(--FlagsCnt < 0)
				{
					getFlagsBuf();
					FlagsCnt = 7;
				}
				if((FlagBuf & 0x80) != 0)
				{
					FlagBuf <<= 1;
					if(Nlzb > Nhfb)
					{
						huffDecode();
					}
					else
					{
						longLZ();
					}
				}
				else
				{
					FlagBuf <<= 1;
					shortLZ();
				}
			}
		}
		oldUnpWriteBuf();
	}

	protected boolean unpReadBuf() throws IOException, RarException
	{
		int dataSize = readTop - inAddr;
		if(dataSize < 0)
		{
			return (false);
		}
		if(inAddr > BitInput.MAX_SIZE / 2)
		{
			if(dataSize > 0)
			{
				//memmove(InBuf,InBuf+InAddr,DataSize);
//		    	for (int i = 0; i < dataSize; i++) {
//					inBuf[i] = inBuf[inAddr + i];
//				}
				System.arraycopy(inBuf, inAddr, inBuf, 0, dataSize);
			}
			inAddr = 0;
			readTop = dataSize;
		}
		else
		{
			dataSize = readTop;
		}
		//int readCode=UnpIO->UnpRead(InBuf+DataSize,(BitInput::MAX_SIZE-DataSize)&~0xf);
		int readCode = unpIO.unpRead(inBuf, dataSize, (BitInput.MAX_SIZE - dataSize) & ~0xf);
		if(readCode > 0)
		{
			readTop += readCode;
		}
		readBorder = readTop - 30;
		return (readCode != -1);
	}

	private int getShortLen1(int pos)
	{
		return pos == 1 ? Buf60 + 3 : ShortLen1[pos];
	}

	private int getShortLen2(int pos)
	{
		return pos == 3 ? Buf60 + 3 : ShortLen2[pos];
	}

	protected void shortLZ()
	{
		int Length, SaveLength;
		int LastDistance;
		int Distance;
		int DistancePlace;
		NumHuf = 0;

		int BitField = fgetbits();
		if(LCount == 2)
		{
			faddbits(1);
			if(BitField >= 0x8000)
			{
				oldCopyString(lastDist, lastLength);
				return;
			}
			BitField <<= 1;
			LCount = 0;
		}
		BitField >>>= 8;
		if(AvrLn1 < 37)
		{
			for(Length = 0;; Length++)
			{
				if(((BitField ^ ShortXor1[Length]) & (~(0xff >>> getShortLen1(Length)))) == 0)
				{
					break;
				}
			}
			faddbits(getShortLen1(Length));
		}
		else
		{
			for(Length = 0;; Length++)
			{
				if(((BitField ^ ShortXor2[Length]) & (~(0xff >> getShortLen2(Length)))) == 0)
				{
					break;
				}
			}
			faddbits(getShortLen2(Length));
		}

		if(Length >= 9)
		{
			if(Length == 9)
			{
				LCount++;
				oldCopyString(lastDist, lastLength);
				return;
			}
			if(Length == 14)
			{
				LCount = 0;
				Length = decodeNum(fgetbits(), STARTL2, DecL2, PosL2) + 5;
				Distance = (fgetbits() >> 1) | 0x8000;
				faddbits(15);
				lastLength = Length;
				lastDist = Distance;
				oldCopyString(Distance, Length);
				return;
			}

			LCount = 0;
			SaveLength = Length;
			Distance = oldDist[(oldDistPtr - (Length - 9)) & 3];
			Length = decodeNum(fgetbits(), STARTL1, DecL1, PosL1) + 2;
			if(Length == 0x101 && SaveLength == 10)
			{
				Buf60 ^= 1;
				return;
			}
			if(Distance > 256)
			{
				Length++;
			}
			if(Distance >= MaxDist3)
			{
				Length++;
			}

			oldDist[oldDistPtr++] = Distance;
			oldDistPtr = oldDistPtr & 3;
			lastLength = Length;
			lastDist = Distance;
			oldCopyString(Distance, Length);
			return;
		}

		LCount = 0;
		AvrLn1 += Length;
		AvrLn1 -= AvrLn1 >> 4;

		DistancePlace = decodeNum(fgetbits(), STARTHF2, DecHf2, PosHf2) & 0xff;
		Distance = ChSetA[DistancePlace];
		if(--DistancePlace != -1)
		{
			PlaceA[Distance]--;
			LastDistance = ChSetA[DistancePlace];
			PlaceA[LastDistance]++;
			ChSetA[DistancePlace + 1] = LastDistance;
			ChSetA[DistancePlace] = Distance;
		}
		Length += 2;
		oldDist[oldDistPtr++] = ++Distance;
		oldDistPtr = oldDistPtr & 3;
		lastLength = Length;
		lastDist = Distance;
		oldCopyString(Distance, Length);
	}

	protected void longLZ()
	{
		int Length;
		int Distance;
		int DistancePlace, NewDistancePlace;
		int OldAvr2, OldAvr3;

		NumHuf = 0;
		Nlzb += 16;
		if(Nlzb > 0xff)
		{
			Nlzb = 0x90;
			Nhfb >>>= 1;
		}
		OldAvr2 = AvrLn2;

		int BitField = fgetbits();
		if(AvrLn2 >= 122)
		{
			Length = decodeNum(BitField, STARTL2, DecL2, PosL2);
		}
		else
		{
			if(AvrLn2 >= 64)
			{
				Length = decodeNum(BitField, STARTL1, DecL1, PosL1);
			}
			else
			{
				if(BitField < 0x100)
				{
					Length = BitField;
					faddbits(16);
				}
				else
				{
					for(Length = 0; ((BitField << Length) & 0x8000) == 0; Length++)
					{
						;
					}
					faddbits(Length + 1);
				}
			}
		}
		AvrLn2 += Length;
		AvrLn2 -= AvrLn2 >>> 5;

		BitField = fgetbits();
		if(AvrPlcB > 0x28ff)
		{
			DistancePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2);
		}
		else
		{
			if(AvrPlcB > 0x6ff)
			{
				DistancePlace = decodeNum(BitField, STARTHF1, DecHf1, PosHf1);
			}
			else
			{
				DistancePlace = decodeNum(BitField, STARTHF0, DecHf0, PosHf0);
			}
		}
		AvrPlcB += DistancePlace;
		AvrPlcB -= AvrPlcB >> 8;
		while(true)
		{
			Distance = ChSetB[DistancePlace & 0xff];
			NewDistancePlace = NToPlB[Distance++ & 0xff]++;
			if((Distance & 0xff) == 0)
			{
				corrHuff(ChSetB, NToPlB);
			}
			else
			{
				break;
			}
		}

		ChSetB[DistancePlace] = ChSetB[NewDistancePlace];
		ChSetB[NewDistancePlace] = Distance;

		Distance = ((Distance & 0xff00) | (fgetbits() >>> 8)) >>> 1;
		faddbits(7);

		OldAvr3 = AvrLn3;
		if(Length != 1 && Length != 4)
		{
			if(Length == 0 && Distance <= MaxDist3)
			{
				AvrLn3++;
				AvrLn3 -= AvrLn3 >> 8;
			}
			else
			{
				if(AvrLn3 > 0)
				{
					AvrLn3--;
				}
			}
		}
		Length += 3;
		if(Distance >= MaxDist3)
		{
			Length++;
		}
		if(Distance <= 256)
		{
			Length += 8;
		}
		if(OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40)
		{
			MaxDist3 = 0x7f00;
		}
		else
		{
			MaxDist3 = 0x2001;
		}
		oldDist[oldDistPtr++] = Distance;
		oldDistPtr = oldDistPtr & 3;
		lastLength = Length;
		lastDist = Distance;
		oldCopyString(Distance, Length);
	}

	protected void huffDecode()
	{
		int CurByte, NewBytePlace;
		int Length;
		int Distance;
		int BytePlace;

		int BitField = fgetbits();

		if(AvrPlc > 0x75ff)
		{
			BytePlace = decodeNum(BitField, STARTHF4, DecHf4, PosHf4);
		}
		else
		{
			if(AvrPlc > 0x5dff)
			{
				BytePlace = decodeNum(BitField, STARTHF3, DecHf3, PosHf3);
			}
			else
			{
				if(AvrPlc > 0x35ff)
				{
					BytePlace = decodeNum(BitField, STARTHF2, DecHf2, PosHf2);
				}
				else
				{
					if(AvrPlc > 0x0dff)
					{
						BytePlace = decodeNum(BitField, STARTHF1, DecHf1,
							PosHf1);
					}
					else
					{
						BytePlace = decodeNum(BitField, STARTHF0, DecHf0,
							PosHf0);
					}
				}
			}
		}
		BytePlace &= 0xff;
		if(StMode != 0)
		{
			if(BytePlace == 0 && BitField > 0xfff)
			{
				BytePlace = 0x100;
			}
			if(--BytePlace == -1)
			{
				BitField = fgetbits();
				faddbits(1);
				if((BitField & 0x8000) != 0)
				{
					NumHuf = StMode = 0;
					return;
				}
				else
				{
					Length = (BitField & 0x4000) != 0 ? 4 : 3;
					faddbits(1);
					Distance = decodeNum(fgetbits(), STARTHF2, DecHf2, PosHf2);
					Distance = (Distance << 5) | (fgetbits() >>> 11);
					faddbits(5);
					oldCopyString(Distance, Length);
					return;
				}
			}
		}
		else
		{
			if(NumHuf++ >= 16 && FlagsCnt == 0)
			{
				StMode = 1;
			}
		}
		AvrPlc += BytePlace;
		AvrPlc -= AvrPlc >>> 8;
		Nhfb += 16;
		if(Nhfb > 0xff)
		{
			Nhfb = 0x90;
			Nlzb >>>= 1;
		}

		window[unpPtr++] = (byte) (ChSet[BytePlace] >>> 8);
		--destUnpSize;

		while(true)
		{
			CurByte = ChSet[BytePlace];
			NewBytePlace = NToPl[CurByte++ & 0xff]++;
			if((CurByte & 0xff) > 0xa1)
			{
				corrHuff(ChSet, NToPl);
			}
			else
			{
				break;
			}
		}

		ChSet[BytePlace] = ChSet[NewBytePlace];
		ChSet[NewBytePlace] = CurByte;
	}

	protected void getFlagsBuf()
	{
		int Flags, NewFlagsPlace;
		int FlagsPlace = decodeNum(fgetbits(), STARTHF2, DecHf2, PosHf2);

		while(true)
		{
			Flags = ChSetC[FlagsPlace];
			FlagBuf = Flags >>> 8;
			NewFlagsPlace = NToPlC[Flags++ & 0xff]++;
			if((Flags & 0xff) != 0)
			{
				break;
			}
			corrHuff(ChSetC, NToPlC);
		}

		ChSetC[FlagsPlace] = ChSetC[NewFlagsPlace];
		ChSetC[NewFlagsPlace] = Flags;
	}

	protected void oldUnpInitData(boolean Solid)
	{
		if(!Solid)
		{
			AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0;
			AvrPlc = 0x3500;
			MaxDist3 = 0x2001;
			Nhfb = Nlzb = 0x80;
		}
		FlagsCnt = 0;
		FlagBuf = 0;
		StMode = 0;
		LCount = 0;
		readTop = 0;
	}

	protected void initHuff()
	{
		for(int I = 0; I < 256; I++)
		{
			Place[I] = PlaceA[I] = PlaceB[I] = I;
			PlaceC[I] = (~I + 1) & 0xff;
			ChSet[I] = ChSetB[I] = I << 8;
			ChSetA[I] = I;
			ChSetC[I] = ((~I + 1) & 0xff) << 8;
		}

		Arrays.fill(NToPl, 0);// memset(NToPl,0,sizeof(NToPl));
		Arrays.fill(NToPlB, 0); // memset(NToPlB,0,sizeof(NToPlB));
		Arrays.fill(NToPlC, 0); // memset(NToPlC,0,sizeof(NToPlC));
		corrHuff(ChSetB, NToPlB);
	}

	protected void corrHuff(int[] CharSet, int[] NumToPlace)
	{
		int I, J, pos = 0;
		for(I = 7; I >= 0; I--)
		{
			for(J = 0; J < 32; J++, pos++)
			{
				CharSet[pos] = ((CharSet[pos] & ~0xff) | I);// *CharSet=(*CharSet
				// & ~0xff) | I;
			}
		}
		Arrays.fill(NumToPlace, 0);// memset(NumToPlace,0,sizeof(NToPl));
		for(I = 6; I >= 0; I--)
		{
			NumToPlace[I] = (7 - I) * 32;
		}
	}

	protected void oldCopyString(int Distance, int Length)
	{
		destUnpSize -= Length;
		while((Length--) != 0)
		{
			window[unpPtr] = window[(unpPtr - Distance) & Compress.CURWINMASK];
			unpPtr = (unpPtr + 1) & Compress.CURWINMASK;
		}
	}

	protected int decodeNum(int Num, int StartPos, int[] DecTab, int[] PosTab)
	{
		int I;
		for(Num &= 0xfff0, I = 0; DecTab[I] <= Num; I++)
		{
			StartPos++;
		}
		faddbits(StartPos);
		return (((Num - (I != 0 ? DecTab[I - 1] : 0)) >>> (16 - StartPos)) + PosTab[StartPos]);
	}

	protected void oldUnpWriteBuf() throws IOException
	{
		if(unpPtr != wrPtr)
		{
			unpSomeRead = true;
		}
		if(unpPtr < wrPtr)
		{
			unpIO.unpWrite(window, wrPtr, -wrPtr & Compress.CURWINMASK);
			unpIO.unpWrite(window, 0, unpPtr);
			unpAllBuf = true;
		}
		else
		{
			unpIO.unpWrite(window, wrPtr, unpPtr - wrPtr);
		}
		wrPtr = unpPtr;
	}
}
