// LzBinTree.cs

using System;

namespace SevenZip.Compression.LZ
{
	public class BinTree : InWindow, IMatchFinder
	{
		UInt32 _cyclicBufferPos;
		UInt32 _cyclicBufferSize;
		UInt32 _historySize;
		UInt32 _matchMaxLen;

		// UInt32 []_dummy;
		UInt32[] _son;

		UInt32[] _hash;
		UInt32[] _hash2;
		UInt32[] _hash3;

		UInt32 _cutValue = 0xFF;

		bool HASH_ARRAY = true;
		bool HASH_BIG = false;

		const UInt32 kHash3Size = 1 << 18;

		const UInt32 kBT2HashSize = 1 << 16;
		const UInt32 kBT4Hash2Size = 1 << 10;
		const UInt32 kBT4Hash4Size = 1 << 20;
		const UInt32 kBT4bHash4Size = 1 << 23;
		const UInt32 kBT2NumHashDirectBytes = 2;
		const UInt32 kBT4NumHashDirectBytes = 0;

		UInt32 kHash2Size = kBT4Hash2Size;
		UInt32 kNumHashDirectBytes = kBT4NumHashDirectBytes;
		UInt32 kNumHashBytes = 4;
		UInt32 kHashSize = kBT4Hash4Size;

		public void SetType(int numHashBytes, bool big)
		{
			HASH_ARRAY = numHashBytes > 2;
			HASH_BIG = big;
			if (HASH_ARRAY)
			{
				kHash2Size = kBT4Hash2Size;
				kNumHashDirectBytes = kBT4NumHashDirectBytes;
				kNumHashBytes = 4;
				kHashSize = HASH_BIG ? kBT4bHash4Size : kBT4Hash4Size;
			}
			else
			{
				kNumHashDirectBytes = kBT2NumHashDirectBytes;
				kNumHashBytes = 2;
				kHashSize = kBT2HashSize;
			}
		}

		const UInt32 kEmptyHashValue = 0;

		const UInt32 kMaxValForNormalize = ((UInt32)1 << 31) - 1;

		UInt32 Hash(UInt32 offset, out UInt32 hash2Value, out UInt32 hash3Value)
		{
			UInt32 temp = CRC.Table[_bufferBase[offset]] ^ _bufferBase[offset + 1];
			hash2Value = temp & (kHash2Size - 1);
			temp ^= ((UInt32)(_bufferBase[offset + 2]) << 8);
			hash3Value = temp & (kHash3Size - 1);
			return (temp ^ (CRC.Table[_bufferBase[offset + 3]] << 5)) & (kHashSize - 1);
		}

		UInt32 Hash(UInt32 offset)
		{
			return _bufferBase[offset] ^ ((UInt32)(_bufferBase[offset + 1]) << 8);
		}

		public new void Init(System.IO.Stream inStream)
		{
			base.Init(inStream);
			UInt32 i;
			for (i = 0; i < kHashSize; i++)
				_hash[i] = kEmptyHashValue;
			if (HASH_ARRAY)
			{
				for (i = 0; i < kHash2Size; i++)
					_hash2[i] = kEmptyHashValue;
				for (i = 0; i < kHash3Size; i++)
					_hash3[i] = kEmptyHashValue;
			}
			_cyclicBufferPos = 0;
			ReduceOffsets(-1);
		}

		public new void ReleaseStream() { base.ReleaseStream(); }

		public new void MovePos()
		{
			_cyclicBufferPos++;
			if (_cyclicBufferPos >= _cyclicBufferSize)
				_cyclicBufferPos = 0;
			base.MovePos();
			if (_pos == kMaxValForNormalize)
				Normalize();
		}

		public new Byte GetIndexByte(Int32 index) { return base.GetIndexByte(index); }

		public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit)
		{ return base.GetMatchLen(index, distance, limit); }

		public new UInt32 GetNumAvailableBytes() { return base.GetNumAvailableBytes(); }

		public void Create(UInt32 historySize, UInt32 keepAddBufferBefore,
			UInt32 matchMaxLen, UInt32 keepAddBufferAfter)
		{
			// _dummy = new UInt32[matchMaxLen + 1];
			UInt32 windowReservSize = (historySize + keepAddBufferBefore +
				matchMaxLen + keepAddBufferAfter) / 2 + 256;

			_son = null;
			_hash = null;
			_hash2 = null;
			_hash3 = null;

			base.Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize);

			if (_blockSize + 256 > kMaxValForNormalize)
				throw new Exception();

			_historySize = historySize;
			_matchMaxLen = matchMaxLen;

			_cyclicBufferSize = historySize + 1;
			_son = new UInt32[_cyclicBufferSize * 2];

			_hash = new UInt32[kHashSize];
			if (HASH_ARRAY)
			{
				_hash2 = new UInt32[kHash2Size];
				_hash3 = new UInt32[kHash3Size];
			}
		}

		public UInt32 GetLongestMatch(UInt32[] distances)
		{
			UInt32 lenLimit;
			if (_pos + _matchMaxLen <= _streamPos)
				lenLimit = _matchMaxLen;
			else
			{
				lenLimit = _streamPos - _pos;
				if (lenLimit < kNumHashBytes)
					return 0;
			}

			UInt32 matchMinPos = (_pos > _historySize) ? (_pos - _historySize) : 1;

			UInt32 cur = _bufferOffset + _pos;

			UInt32 matchHashLenMax = 0;

			UInt32 hashValue, hash2Value = 0, hash3Value = 0;

			if (HASH_ARRAY)
				hashValue = Hash(cur, out hash2Value, out hash3Value);
			else
				hashValue = Hash(cur);

			UInt32 curMatch = _hash[hashValue];
			UInt32 curMatch2 = 0, curMatch3 = 0;
			UInt32 len2Distance = 0;
			UInt32 len3Distance = 0;
			bool matchLen2Exist = false;
			bool matchLen3Exist = false;
			if (HASH_ARRAY)
			{
				curMatch2 = _hash2[hash2Value];
				curMatch3 = _hash3[hash3Value];
				_hash2[hash2Value] = _pos;
				if (curMatch2 >= matchMinPos)
				{
					if (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur])
					{
						len2Distance = _pos - curMatch2 - 1;
						matchHashLenMax = 2;
						matchLen2Exist = true;
					}
				}
				{
					_hash3[hash3Value] = _pos;
					if (curMatch3 >= matchMinPos)
					{
						if (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur])
						{
							len3Distance = _pos - curMatch3 - 1;
							matchHashLenMax = 3;
							matchLen3Exist = true;
							if (matchLen2Exist)
							{
								if (len3Distance < len2Distance)
									len2Distance = len3Distance;
							}
							else
							{
								len2Distance = len3Distance;
								matchLen2Exist = true;
							}
						}
					}
				}
			}

			_hash[hashValue] = _pos;

			if (curMatch < matchMinPos)
			{
				_son[(_cyclicBufferPos << 1)] = kEmptyHashValue;
				_son[(_cyclicBufferPos << 1) + 1] = kEmptyHashValue;

				if (HASH_ARRAY)
				{
					distances[2] = len2Distance;
					distances[3] = len3Distance;
				}
				return matchHashLenMax;
			}
			UInt32 ptrLeft = (_cyclicBufferPos << 1) + 1;
			UInt32 ptrRight = (_cyclicBufferPos << 1);

			UInt32 maxLen, minLeft, minRight;
			maxLen = minLeft = minRight = kNumHashDirectBytes;

			distances[maxLen] = _pos - curMatch - 1;

			for (UInt32 count = _cutValue; count > 0; count--)
			{
				UInt32 pby1 = _bufferOffset + curMatch;
				UInt32 currentLen = Math.Min(minLeft, minRight);
				for (; currentLen < lenLimit; currentLen++)
					if (_bufferBase[pby1 + currentLen] != _bufferBase[cur + currentLen])
						break;
				UInt32 delta = _pos - curMatch;
				while (currentLen > maxLen)
					distances[++maxLen] = delta - 1;

				UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ?
							(_cyclicBufferPos - delta) :
							(_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;

				if (currentLen != lenLimit)
				{
					if (_bufferBase[pby1 + currentLen] < _bufferBase[cur + currentLen])
					{
						_son[ptrRight] = curMatch;
						ptrRight = cyclicPos + 1;
						curMatch = _son[ptrRight];
						if (currentLen > minLeft)
							minLeft = currentLen;
					}
					else
					{
						_son[ptrLeft] = curMatch;
						ptrLeft = cyclicPos;
						curMatch = _son[ptrLeft];
						if (currentLen > minRight)
							minRight = currentLen;
					}
				}
				else
				{
					if (currentLen < _matchMaxLen)
					{
						_son[ptrLeft] = curMatch;
						ptrLeft = cyclicPos;
						curMatch = _son[ptrLeft];
						if (currentLen > minRight)
							minRight = currentLen;
					}
					else
					{
						_son[ptrLeft] = _son[cyclicPos + 1];
						_son[ptrRight] = _son[cyclicPos];

						if (HASH_ARRAY)
						{
							if (matchLen2Exist && len2Distance < distances[2])
								distances[2] = len2Distance;
							if (matchLen3Exist && len3Distance < distances[3])
								distances[3] = len3Distance;
						}
						return maxLen;
					}
				}
				if (curMatch < matchMinPos)
					break;
			}
			_son[ptrLeft] = kEmptyHashValue;
			_son[ptrRight] = kEmptyHashValue;

			if (HASH_ARRAY)
			{
				if (matchLen2Exist)
				{
					if (maxLen < 2)
					{
						distances[2] = len2Distance;
						maxLen = 2;
					}
					else if (len2Distance < distances[2])
						distances[2] = len2Distance;
				}
				{
					if (matchLen3Exist)
					{
						if (maxLen < 3)
						{
							distances[3] = len3Distance;
							maxLen = 3;
						}
						else if (len3Distance < distances[3])
							distances[3] = len3Distance;
					}
				}
			}
			return maxLen;
		}

		public void DummyLongestMatch()
		{
			// GetLongestMatch(_dummy);

			UInt32 lenLimit;
			if (_pos + _matchMaxLen <= _streamPos)
				lenLimit = _matchMaxLen;
			else
			{
				lenLimit = _streamPos - _pos;
				if (lenLimit < kNumHashBytes)
					return;
			}

			UInt32 matchMinPos = (_pos > _historySize) ? (_pos - _historySize) : 1;

			UInt32 cur = _bufferOffset + _pos;

			UInt32 hashValue, hash2Value = 0, hash3Value = 0;

			if (HASH_ARRAY)
				hashValue = Hash(cur, out hash2Value, out hash3Value);
			else
				hashValue = Hash(cur);

			UInt32 curMatch = _hash[hashValue];
			UInt32 curMatch2 = 0, curMatch3 = 0;
			if (HASH_ARRAY)
			{
				curMatch2 = _hash2[hash2Value];
				curMatch3 = _hash3[hash3Value];
				_hash2[hash2Value] = _pos;
				_hash3[hash3Value] = _pos;
			}
			_hash[hashValue] = _pos;

			if (curMatch < matchMinPos)
			{
				_son[(_cyclicBufferPos << 1)] = kEmptyHashValue;
				_son[(_cyclicBufferPos << 1) + 1] = kEmptyHashValue;
				return;
			}
			UInt32 ptrLeft = (_cyclicBufferPos << 1) + 1;
			UInt32 ptrRight = (_cyclicBufferPos << 1);

			UInt32 minLeft, minRight;
			minLeft = minRight = kNumHashDirectBytes;

			for (UInt32 count = _cutValue; count > 0; count--)
			{
				UInt32 pby1 = _bufferOffset + curMatch;
				UInt32 currentLen = Math.Min(minLeft, minRight);
				for (; currentLen < lenLimit; currentLen++)
					if (_bufferBase[pby1 + currentLen] != _bufferBase[cur + currentLen])
						break;
				UInt32 delta = _pos - curMatch;

				UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ?
							(_cyclicBufferPos - delta) :
							(_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;

				if (currentLen != lenLimit)
				{
					if (_bufferBase[pby1 + currentLen] < _bufferBase[cur + currentLen])
					{
						_son[ptrRight] = curMatch;
						ptrRight = cyclicPos + 1;
						curMatch = _son[ptrRight];
						if (currentLen > minLeft)
							minLeft = currentLen;
					}
					else
					{
						_son[ptrLeft] = curMatch;
						ptrLeft = cyclicPos;
						curMatch = _son[ptrLeft];
						if (currentLen > minRight)
							minRight = currentLen;
					}
				}
				else
				{
					if (currentLen < _matchMaxLen)
					{
						_son[ptrLeft] = curMatch;
						ptrLeft = cyclicPos;
						curMatch = _son[ptrLeft];
						if (currentLen > minRight)
							minRight = currentLen;
					}
					else
					{
						_son[ptrLeft] = _son[cyclicPos + 1];
						_son[ptrRight] = _son[cyclicPos];
						return;
					}
				}
				if (curMatch < matchMinPos)
					break;
			}
			_son[ptrLeft] = kEmptyHashValue;
			_son[ptrRight] = kEmptyHashValue;
		}

		void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue)
		{
			for (UInt32 i = 0; i < numItems; i++)
			{
				UInt32 value = items[i];
				if (value <= subValue)
					value = kEmptyHashValue;
				else
					value -= subValue;
				items[i] = value;
			}
		}

		void Normalize()
		{
			UInt32 startItem = _pos - _historySize;
			UInt32 subValue = startItem - 1;
			NormalizeLinks(_son, _cyclicBufferSize * 2, subValue);

			NormalizeLinks(_hash, kHashSize, subValue);

			if (HASH_ARRAY)
			{
				NormalizeLinks(_hash2, kHash2Size, subValue);
				NormalizeLinks(_hash3, kHash3Size, subValue);
			}
			ReduceOffsets((Int32)subValue);
		}

		public void SetCutValue(UInt32 cutValue) { _cutValue = cutValue; }
	}
}
