
#ifndef _DMLZWD_H_
#define _DMLZWD_H_

#include "DMGHLt.h"

class DMlzwD
{
	// 
	class MemSrcIo
	{
		// 
		const UI8 *pbPos;
		// 
		const UI8 *pbEnd;

	public:
		// 
		MemSrcIo(const void *pbData, int cbData)
		{
			pbEnd = (pbPos = (const UI8 *)pbData) + cbData;
		}
		// 
		int ReadByte()
		{
			int r = -1;
			if (pbPos < pbEnd) {
				r = *pbPos;
				pbPos++;
			}
			return r;
		}
	};
	// 
	class MemDestIo
	{
		// 
		UI8 *pbPos;
		// 
		UI8 *pbEnd;
		// 
		UI8 *pbBegin;

	public:
		// 
		MemDestIo(void *pbData, int cbData)
		{
			pbEnd = (pbBegin = pbPos = (UI8 *)pbData) + cbData;
		}
		// 
		bool WriteByte(UI8 x)
		{
			if (pbPos < pbEnd) {
				*pbPos = x;
				pbPos++;
				return true;
			}
			return false;
		}
		// 
		int GetPosition() const
		{
			return pbPos - pbBegin;
		}
		// 
		UI8 GetAt(int i) const
		{
			return pbBegin[i];
		}
	};
	// 
	class LzRleWrap
	{
		// 
		MemDestIo &io;
		// 
		bool fRepeat;
		// 
		UI8 cWhat;

	public:
		// 
		LzRleWrap(MemDestIo &io): io(io), fRepeat(false)
		{

		}
		// 
		bool WriteByte(UI8 x)
		{
			if (!fRepeat)
			{
				if (x == 0x90)
				{
					fRepeat = true;
				}
				else
				{
					cWhat = x;
					if (!io.WriteByte(x)) return false;
				}
			}
			else
			{
				if (x == 0)
				{
					if (!io.WriteByte(0x90)) return false;
				}
				else
				{
					int n = x - 1;
					for (; n != 0; n--)
					{
						if (!io.WriteByte(cWhat)) return false;
					}
				}
				fRepeat = false;
			}
			return true;
		}
	};
	// 
	class ReadR2LXBits
	{
		// 
		MemSrcIo &io;
		// 
		int nBits;
		// 
		int nValue;

		// 
		int ReadByte()
		{
			return io.ReadByte();
		}
		// 
		static inline int BitMask(int n)
		{
			return (1 << n) - 1;
		}

	public:
		// 
		ReadR2LXBits(MemSrcIo &io): io(io)
		{
			nBits = 0;
		}
		// 
		int ReadXBits(int n)
		{
			int r = 0;
			int h = 0;
			while (n > 0)
			{
				if (nBits == 0)
				{
					if ((nValue = ReadByte()) < 0) return -1;
					nBits = 8;
				}
				if (n >= nBits)
				{
					r |= nValue << h;

					h = h + nBits;
					n = n - nBits;
					nBits = 0;
				}
				else
				{
					r |= (nValue & BitMask(n)) << h;

					nValue = nValue >> n;

					nBits = nBits - n;
					h = h + n;
					n = 0;
				}
			}
			return r;
		}
	};
	// 
	struct Leaf
	{	// 
		int nBack, nFront;
		// 
		Leaf(): nBack(-1), nFront(-1) { }
	};
	// 
	class Leaves
	{
		// 
		Leaf aLeaves[4096];

	public:
		// 
		Leaf &operator [](int i) { return aLeaves[i]; }
	};

	// 
	bool DWalkTree(LzRleWrap &lzRleWrap, Leaves &leaves, int nVal, int nMax, bool fFull)
	{
		Leaf &leaf = leaves[nVal];

		if (leaf.nBack < 256)
		{
			if (!lzRleWrap.WriteByte((UI8)leaf.nBack)) return false;
		}
		else
		{
			if (!DWalkTree(lzRleWrap, leaves, leaf.nBack, nMax, fFull)) return false;
		}

		if (leaf.nFront < 256)
		{
			if (fFull)
			{
				if (!lzRleWrap.WriteByte((UI8)leaf.nFront)) return false;
			}
		}
		else
		{
			if (fFull)
			{
				if (!DWalkTree(lzRleWrap, leaves, leaf.nFront, nMax, false)) return false;
			}
		}
		return true;
	}

public:
	// 
	bool D(const void *pvSrc, int cbSrc, void *pvDest, int *cbDest, int cbSizeMax)
	{
		MemSrcIo src(pvSrc, cbSrc);
		ReadR2LXBits thruIn(src);
		MemDestIo dest(pvDest, *cbDest);
		LzRleWrap lzRleWrap(dest);
		Leaves leaves;
		if (!pvSrc || cbSrc < 1 || !pvDest || !cbDest || *cbDest < 1 || cbSizeMax < 1) return false;
		int iPos = 257;
		int nValOlder = thruIn.ReadXBits(9);
		int nValNewer;
		if (nValOlder < 0) return false;
		if (!lzRleWrap.WriteByte(nValOlder)) return false;
		int iLoop = 0;
		int nBits = 9;
		int nBitsHit = (1 << 9) - 1;
		int iFilePosNewer = 0;
		while (dest.GetPosition() < cbSizeMax)
		{
			if (iPos > nBitsHit && nBits != 12) {
				nBits++;
				nBitsHit = (1 << nBits) - 1;
			}

			if ((nValNewer = thruIn.ReadXBits(nBits)) < 0) break;

			iFilePosNewer = dest.GetPosition();

			int nValNewerBackup = nValNewer;
			if (nValNewerBackup >= iPos)
			{
				nValNewer = nValOlder;
			}

			if (nValNewer < 256)
			{
				if (!lzRleWrap.WriteByte((UI8)nValNewer)) return false;
			}
			else if (nValNewer == 256)
			{
				iPos = 256; continue;
			}
			else
			{
				if (!DWalkTree(lzRleWrap, leaves, nValNewer, iPos, true)) return false;
			}

			if (nValNewerBackup >= iPos)
			{
				if (!lzRleWrap.WriteByte(dest.GetAt(iFilePosNewer))) return false;
			}

			if (iPos < 4096)
			{
				leaves[iPos].nFront = nValNewer;
				leaves[iPos].nBack = nValOlder;

				iPos++;
			}

			iLoop++;
			nValOlder = nValNewerBackup;
		}
		*cbDest = dest.GetPosition();
		return true;
	}
};

#endif // #define _DMLZWD_H_
