using System;

namespace DM2AE
{
	// GRAPHICS.DAT Reading class
	public class GDR
	{
		// 
		public enum ImgType
		{
			Unknown,
			ExtDM1,			// 4bpp: Extended image format from DM1
			Intrepray,		// 8bpp: DM2PC specific image format
			U4bpp,			// 4bpp: Uncompressed bitmap format
			U8bpp,			// 8bpp: Uncompressed bitmap format
			U320x200x8bpp,	// 8bpp: 320 by 200
		};

		// 
		public class PlaceHolder
		{
			// 
			readonly int nOffset;
			// 
			readonly int nLength;

			// 
			public PlaceHolder(int nOffset, int nLength)
			{
				this.nOffset = nOffset;
				this.nLength = nLength;
			}
			// 
			public int Offset
			{
				get { return nOffset; }
			}
			// 
			public int Length
			{
				get { return nLength; }
			}
		};
		// 
		public class Entry
		{
			// 
			readonly byte[] pBinary;

			// 
			public Entry(
				byte[] pBinary
				)
			{
				this.pBinary = pBinary;
			}
			// 
			public byte[] Binary
			{
				get { return pBinary; }
			}
		};
		// 
		public class ChromakeySpecifier
		{
			// 
			int iNewIdx;
			// 
			readonly int iOrgIdx;

			// 
			public ChromakeySpecifier(
				int iPalIdx
				)
			{
				this.iNewIdx = this.iOrgIdx = iPalIdx;
			}
			// 
			public void TestBy8bppImage()
			{

			}
			// 
			public void TestBy4p1bppImage(
				byte[] pPalette16
				)
			{
				if (0 <= iOrgIdx && iOrgIdx < 16)
				{
					iNewIdx = pPalette16[iOrgIdx];
				}
			}
			// 
			public void TestedByServer(
				byte iNewIdx
				)
			{
				this.iNewIdx = iNewIdx;
			}
			// 
			public int UntestedPalIdx
			{
				get { return iOrgIdx; }
			}
			// 
			public int TestedPalIndex
			{
				get { return iNewIdx; }
			}
		}

		// 
		private class Draw8bppImg
		{
			// 
			readonly byte[, ] pImage;
			// 
			readonly int cx, cy;

			// 
			public Draw8bppImg(
				int cx,
				int cy
				)
			{
				this.pImage = new System.Byte[this.cy = cy, this.cx = cx];
			}
			// 
			public System.Byte this[int y, int x]
			{
				get { return pImage[y, x]; }
				set { pImage[y, x] = value; }
			}
			// 
			public int Width
			{
				get { return cx; }
			}
			// 
			public int Height
			{
				get { return cy; }
			}
		};
		// 
		private class Draw4p1bppImg
		{
			// Array is subjected in form of [y, x]
			//   0~15 = Palette 16 colors
			// +16    = Empty color (Copy from previous line)
			// +32    = Transparent color (Copy from underlay image)
			// +64    = Selected from Palette 16 colors
			readonly byte[,] pImage;
			// 
			readonly int cx, cy;

			// 
			public Draw4p1bppImg(
				int cx,
				int cy
				)
			{
				this.pImage = new byte[this.cy = cy, this.cx = cx];
			}
			// 
			public byte this[int y, int x]
			{
				get { return pImage[y, x]; }
				set { pImage[y, x] = value; }
			}
			// 
			public int Width
			{
				get { return cx; }
			}
			// 
			public int Height
			{
				get { return cy; }
			}
		}

		// 
		private abstract class Rasterizing4bppImage
		{
			// 
			abstract public int Width
			{
				get;
			}
			// 
			abstract public int Height
			{
				get;
			}
			// 
			abstract public byte this[int y, int x]
			{
				get;
			}
			// 
			abstract public byte[] Palette16
			{
				get;
			}
		}
		// 
		private class Rasterize4bppImage : Rasterizing4bppImage
		{
			// 
			readonly Draw4p1bppImg pSrc;
			// 
			readonly ImgPalExtDM1 pImgPal;

			// 
			public Rasterize4bppImage(
				Draw4p1bppImg pSrc,
				ImgPalExtDM1 pImgPal
				)
			{
				this.pSrc = pSrc;
				this.pImgPal = pImgPal;
			}
			// 
			public override int Width
			{
				get { return pSrc.Width; }
			}
			// 
			public override int Height
			{
				get { return pSrc.Height; }
			}
			// 
			public override byte this[int y, int x]
			{
				get { return (byte)(pSrc[y, x] & 15); }
			}
			// 
			public override byte[] Palette16
			{
				get { return pImgPal.Palette16; }
			}
		}

		// 
		private abstract class Rasterizing8bppImage
		{
			// 
			abstract public int Width
			{
				get;	
			}
			// 
			abstract public int Height
			{
				get;
			}
			// 
			abstract public byte this[int y, int x]
			{
				get;
			}
			// 
			abstract public void TestIt(ChromakeySpecifier p);
		}
		// 
		private class Rasterize8bppImage : Rasterizing8bppImage
		{
			// 
			readonly Draw8bppImg pSrc;

			// 
			public Rasterize8bppImage(
				Draw8bppImg pSrc
				)
			{
				this.pSrc = pSrc;
			}
			// 
			public override int Width
			{
				get { return pSrc.Width; }
			}
			// 
			public override int Height
			{
				get { return pSrc.Height; }
			}
			// 
			public override byte this[int y, int x]
			{
				get { return pSrc[y, x]; }
			}
			// 
			public override void TestIt(ChromakeySpecifier p)
			{
				p.TestBy8bppImage();
			}
		}
		// 
		private class Rasterize4p1bppTo8bppImage : Rasterizing8bppImage
		{
			// 
			readonly Draw4p1bppImg pSrc;
			// 
			readonly ImgPalExtDM1 pImgPal;
			// 
			byte nHitPalIdx = 0xff;
			// 
			byte nNewPalIdx = 0xff;

			// 
			public Rasterize4p1bppTo8bppImage(
				Draw4p1bppImg pSrc,
				ImgPalExtDM1 pImgPal
				)
			{
				this.pSrc = pSrc;
				this.pImgPal = pImgPal;

				int i, t;
				for (i = 0; i < 256; i++)
				{
					for (t = 0; t < 16; t++)
						if (pImgPal.Palette16[t] == i)
							break;
					if (t == 16)
					{
						nNewPalIdx = (byte)i;
						break;
					}
				}
			}
			// 
			public override int Width
			{
				get { return pSrc.Width; }
			}
			// 
			public override int Height
			{
				get { return pSrc.Height; }
			}
			// 
			public override byte this[int y, int x]
			{
				get
				{
					byte v = pSrc[y, x];
					if ((v & 15) == nHitPalIdx)
						return nNewPalIdx;
					return pImgPal.Palette16[v & 15];
				}
			}
			// 
			public override void TestIt(ChromakeySpecifier p)
			{
				nHitPalIdx = (byte)(p.UntestedPalIdx & 15);

				p.TestedByServer(nNewPalIdx);
			}
		}

		// 
		private class OverlayPlane
		{
			// 
			int iPlane = 0;

			// 
			public bool IsFirstPlane()
			{
				return iPlane == 0;
			}
			// 
			public void NextPlane()
			{
				iPlane++;
			}
		}
		// 
		private class ChainImageTracker
		{
			// 
			private System.Collections.SortedList pA2B = new System.Collections.SortedList();

			// 
			public ChainImageTracker()
			{

			}
			// 
			public ChainImageTracker(
				System.Byte[] pBinary,
				bool fLittleEndian
				)
			{
				System.IO.Stream pFileIn = new System.IO.MemoryStream(pBinary, false);
				BinaryReadThru pReadIn =
					fLittleEndian
					? (BinaryReadThru)new BinaryReadThruLE(pFileIn)
					: (BinaryReadThru)new BinaryReadThruBE(pFileIn);

				pA2B = new System.Collections.SortedList(pBinary.Length / 2);

				for (int i = 0, n = pBinary.Length / 4; i < n; i++)
				{
					int v1 = (int)pReadIn.ReadWord();
					int v2 = (int)pReadIn.ReadWord();
					pA2B.Add(v1, v2);
				}
			}
			// 
			public int[] Track(
				int i
				)
			{
				System.Collections.Stack pIndices = new System.Collections.Stack(10);
				pIndices.Push(i);
				for (object pNext = pA2B[i]; pNext != null; pNext = pA2B[pNext])
				{
					pIndices.Push(i = (int)pNext);
				}

				int[] pArray = new int[pIndices.Count];
				System.Array.Copy(pIndices.ToArray(), pArray, pArray.Length);

				return pArray;
			}
		}

		// 
		private class ImgPalExtDM1
		{
			// 
			readonly byte[] pMostUsed6;
			// 
			readonly byte[] pPalette16;

			// 
			public ImgPalExtDM1(
				byte[] pMostUsed6,
				byte[] pPalette16
				)
			{
				this.pMostUsed6 = pMostUsed6;
				this.pPalette16 = pPalette16;
			}
			// 
			public byte[] MostUsed6
			{
				get { return pMostUsed6; }
			}
			// 
			public byte[] Palette16
			{
				get { return pPalette16; }
			}
		}

		// 
		System.IO.Stream pFileIn = null;
		// 
		bool fIsLittleEndian;
		// 
		PlaceHolder[] pPlaceHolders = null;
		// 
		ChainImageTracker pA2B = null;
		// 
		static readonly DM2Pal256 pDM2AllPal = new DM2Pal256();

		// 
		public GDR()
		{

		}
		// 
		~GDR()
		{
			Close();
		}
		// 
		public PlaceHolder[] PlaceHolders
		{
			get { return pPlaceHolders; }
		}
		// 
		public void Close()
		{
			if (pFileIn != null)
			{
				pFileIn.Close();
				pFileIn = null;
			}
			pPlaceHolders = null;
			pA2B = null;
		}
		// 
		public bool OpenRead(
			string PathName,
			bool fLittleEndian
			)
		{
			Close();

			try
			{
				pFileIn = System.IO.File.OpenRead(PathName);
				fIsLittleEndian = fLittleEndian;
				TryToReadPlaceHolder();
				return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);

				Close();
			}
			return false;
		}
		// 
		public Entry ReadEntryAt(
			int i
			)
		{
			try
			{
				PlaceHolder pPH = pPlaceHolders[i];
				byte[] pBinary = new Byte[pPH.Length];

				pFileIn.Position = pPH.Offset;
				int n = pFileIn.Read(pBinary, 0, pBinary.Length);
				if (n != pBinary.Length)
					throw(new System.ApplicationException("EOF reached while reading"));
				Entry pEntry = new Entry(pBinary);

				return pEntry;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return null;
		}
		// 
		public bool ReadImageOverriderEntryAt(
			int i
			)
		{
			Entry pEntry = ReadEntryAt(i);
			if (pEntry != null)
			{
				pA2B = new ChainImageTracker(pEntry.Binary, fIsLittleEndian);
				return true;
			}
			return false;
		}
		// 
		public bool ReadEntryAsSound(
			int i,
			out System.Byte[] pWav
			)
		{
			pWav = null;
			try
			{
				Entry pEntry = ReadEntryAt(i);
				if (TryToReadAsPCM11025(pEntry, out pWav))
					return true;
			}
			catch (System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		public bool ReadEntryAsImage(
			int i,
			ChromakeySpecifier pMH,
			out System.Drawing.Bitmap pImgFg
			)
		{
			pImgFg = null;
			try
			{
				ImgType which = DecideImgType(i);
				System.Diagnostics.Debug.WriteLine(which.ToString());
				switch (which)
				{
					case ImgType.Unknown:
						return false;
					case ImgType.Intrepray:
						if (true) 
						{
							Entry pEntry = ReadEntryAt(i);
							Draw8bppImg pDrawEntity = null;
							if (!TryToReadAsIntreprayImg(pEntry.Binary, out pDrawEntity))
								return false;
							PlayImgEntryAsIntreprayImg(pDrawEntity, pMH, out pImgFg);
							return true;
						}
					case ImgType.U4bpp:
						if (true)
						{
							Entry pEntry = ReadEntryAt(i);
							Draw4p1bppImg pLatestEntity = null;
							ImgPalExtDM1 pImgPal = null;
							if (!TryToReadAsU4bppImg(pEntry, out pLatestEntity, out pImgPal))
								return false;
							Play8bppImgEntry(
								new Rasterize4p1bppTo8bppImage(pLatestEntity, pImgPal),
								pMH,
								out pImgFg
								);
							return true;
						}
					case ImgType.U8bpp:
						if (true)
						{
							Entry pEntry = ReadEntryAt(i);
							if (!TryToReadAsU8bppImg(pEntry, pMH, out pImgFg))
								return false;
							return true;
						}
					case ImgType.U320x200x8bpp:
						if (true)
						{
							Entry pEntry = ReadEntryAt(i);
							if (!TryToReadAs320x200x8bppImg(pEntry, out pImgFg))
								return false;
							return true;
						}
					case ImgType.ExtDM1:
						if (true)
						{
							int iPlane = 0;
							int[] pIdxes = (pA2B != null) ? pA2B.Track(i) : new int[] {i};
							OverlayPlane pOverlay = new OverlayPlane();
							Draw4p1bppImg pLatestEntity = null;
							ImgPalExtDM1 pImgPal = null;
							foreach (int iIdx in pIdxes)
							{
								//System.Diagnostics.Debug.WriteLine("Image Index:" + iIdx.ToString("0000"));

								Entry pEntry = ReadEntryAt(iIdx);
								if (pEntry != null)
								{
									Draw4p1bppImg pNewerEntity;

									TryToReadAsExtDM1Img(
										pEntry.Binary,
										pOverlay,
										out pNewerEntity,
										out pImgPal
										);

									if (pLatestEntity == null)
									{
										pLatestEntity = new Draw4p1bppImg(pNewerEntity.Width, pNewerEntity.Height);

										CombineImgEntryAsExtDM1(
											pLatestEntity,
											pNewerEntity,
											true
											);
									}
									else
									{
										CombineImgEntryAsExtDM1(
											pLatestEntity,
											pNewerEntity,
											false
											);
									}

									iPlane++;
									continue;
								}
								throw(new System.ApplicationException());
							}
							if (pLatestEntity != null)
							{
								Play8bppImgEntry(
									new Rasterize4p1bppTo8bppImage(pLatestEntity, pImgPal),
									pMH,
									out pImgFg
									);
							}

							return true;
						}
					default:
						System.Diagnostics.Debug.Fail("Condition Not Satisfied");
						break;
				}
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}

		// 
		private bool Play4bppImgEntry(
			Rasterizing4bppImage pEntity,
			ChromakeySpecifier pMH,
			out System.Drawing.Bitmap pImg
			)
		{
			pImg = null;

			try
			{
				int x, y, t, cx = pEntity.Width, cy = pEntity.Height;
				pImg = new System.Drawing.Bitmap(
					cx,
					cy,
					System.Drawing.Imaging.PixelFormat.Format4bppIndexed
					);

				int i;
				System.Drawing.Imaging.ColorPalette pPal = pImg.Palette;
				for (i = 0; i < 16; i++)
				{
					pPal.Entries[i] = pDM2AllPal[pEntity.Palette16[i]];
				}
				if (pMH != null)
				{
					i = pMH.UntestedPalIdx;
					if (0 <= i && i < 16)
					{
						pPal.Entries[i] = System.Drawing.Color.FromArgb(0, pPal.Entries[i]);
					}
				}
				pImg.Palette = pPal;

				System.Drawing.Imaging.BitmapData pBD = pImg.LockBits(
					new System.Drawing.Rectangle(0, 0, cx, cy),
					System.Drawing.Imaging.ImageLockMode.WriteOnly,
					System.Drawing.Imaging.PixelFormat.Format4bppIndexed
					);

				try
				{
					byte[] pBuffer = new byte[pBD.Stride * cy];
					for (y = 0; y < cy; y++)
					{
						for (x = 0; x < cx; )
						{
							t = pBD.Stride * y + (x >> 1);
							if (x + 2 <= cx)
							{
								pBuffer[t] = (byte)(pEntity[y, x] * 16 + pEntity[y, x + 1]);
								x += 2;
							}
							else
							{
								pBuffer[t] = (byte)(pEntity[y, x] * 16);
								x++;
							}
						}
					}

					System.Runtime.InteropServices.Marshal.Copy(
						pBuffer,
						0,
						pBD.Scan0,
						pBuffer.Length
						);
				}
				finally
				{
					pImg.UnlockBits(pBD);
				}
				return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private bool Play8bppImgEntry(
			Rasterizing8bppImage pEntity,
			ChromakeySpecifier pMH,
			out System.Drawing.Bitmap pImg
			)
		{
			pImg = null;

			try
			{
				int x, y, cx = pEntity.Width, cy = pEntity.Height;
				pImg = new System.Drawing.Bitmap(
					cx,
					cy,
					System.Drawing.Imaging.PixelFormat.Format8bppIndexed
					);

				int i;
				System.Drawing.Imaging.ColorPalette pPal = pImg.Palette;
				for (i = 0; i < 256; i++)
				{
					pPal.Entries[i] = pDM2AllPal[i];
				}
				if (pMH != null)
				{
					pEntity.TestIt(pMH);
					i = pMH.TestedPalIndex;
					if (0 <= i && i < 256)
					{
						pPal.Entries[i] = System.Drawing.Color.FromArgb(0, pPal.Entries[i]);
					}
				}
				pImg.Palette = pPal;

				System.Drawing.Imaging.BitmapData pBD = pImg.LockBits(
					new System.Drawing.Rectangle(0, 0, cx, cy),
					System.Drawing.Imaging.ImageLockMode.WriteOnly,
					System.Drawing.Imaging.PixelFormat.Format8bppIndexed
					);

				try
				{
					System.Byte[] pBuffer = new System.Byte[pBD.Stride * cy];
					for (y = 0; y < cy; y++)
					{
						for (x = 0; x < cx; x++)
						{
							pBuffer[pBD.Stride * y + x] = pEntity[y, x];
						}
					}

					System.Runtime.InteropServices.Marshal.Copy(
						pBuffer, 
						0, 
						pBD.Scan0,
						pBuffer.Length
						);
				}
				finally
				{
					pImg.UnlockBits(pBD);
				}
				return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private int ReadDistance(BinaryThruRead4Bit ReadIn)
		{
			int Distance;
			System.Byte Lead;
			Lead = ReadIn.Read4Bit();
			if (Lead == 0xf) // F
			{
				Lead = ReadIn.Read4Bit();
				if (Lead == 0xf) // FF
				{
					Lead = ReadIn.Read4Bit();
					if (Lead == 0xf) // FFF
					{
						Lead = ReadIn.Read4Bit(); // 12~15
						Distance = Lead;
						Lead = ReadIn.Read4Bit(); // 08~11
						Distance = (Distance << 4) + Lead;
						Lead = ReadIn.Read4Bit(); // 04~07
						Distance = (Distance << 4) + Lead;
						Lead = ReadIn.Read4Bit(); // 00~03
						Distance = (Distance << 4) + Lead;
					}
					else
					{
						Distance = Lead + 257;
					}
				}
				else
				{
					Distance = Lead;
					Lead = ReadIn.Read4Bit();
					Distance = 17 + (Distance << 4) + Lead;
				}
			}
			else
			{
				Distance = Lead + 2;
			}
			return Distance;
		}
		// 
		private bool Uncompress(
			int cx,
			int cy,
			BinaryThruRead4Bit pThruIn,
			System.Byte[] pMostUsed6,
			System.Byte[] pSelectedPal16,
			out Draw4p1bppImg pEntity,
			bool fOverlapImage
			)
		{
			pEntity = null;
			if (1 <= cx && 1 <= cy)
			{
				int x1 = 0, y1 = 0;
				pEntity = new Draw4p1bppImg(cx, cy);
				System.Byte iLead, iNextLead;
				System.Byte iPalIdx = 0xFF;
				int iDistance;

				try
				{
					while (y1 < cy)
					{
						iLead = pThruIn.Read4Bit();

						switch (iLead & 0x07)
						{
							case 0:
							case 1:
							case 2:
							case 3:
							case 4:
								iPalIdx = (byte)(pMostUsed6[iLead & 7]);
								break;
							case 5:
								iPalIdx = (byte)(pMostUsed6[iLead & 7] + 32);
								break;
							case 6:
								iPalIdx = 16;
								break;
							case 7:
								iNextLead = pThruIn.Read4Bit();
								iPalIdx = (byte)(iNextLead + 64);
								break;
						}
						iDistance = 1;
						if ((iLead & 0x08) == 0x08)
						{
							iDistance = ReadDistance(pThruIn);
						}

						while (iDistance > 0 && y1 < cy)
						{
							pEntity[y1, x1] = iPalIdx;

							iDistance--;
							x1++;

							while (x1 >= cx)
							{
								x1 -= cx;
								y1++;
							}
						}
					}
					return true;
				}
				catch(System.Exception err)
				{
					System.Diagnostics.Debug.WriteLine(err.Message);
				}
			}
			return false;
		}
		// 
		private bool TryToReadAsIntreprayImg(
			System.Byte[] pBinary,
			out Draw8bppImg pDrawEntity
			)
		{
			pDrawEntity = null;
			int nLen = pBinary.Length;
			if (nLen < 8 || !((pBinary[6] == 3 || pBinary[6] == 2) && pBinary[7] == 0))
				return false;
			try
			{
				int x1 = 0, y1 = 0;
				int cx = pBinary[0] | ((pBinary[1]&0x01) * 256);
				int cy = pBinary[2] | ((pBinary[3]&0x01) * 256);
				System.IO.MemoryStream pFileIn = new System.IO.MemoryStream(pBinary, 8, nLen - 8, false);
				int iBit, iPixel = 0;
				pDrawEntity = new Draw8bppImg(cx, cy);
				bool fIs3 = pBinary[6] == 3;
				while (y1 < cy)
				{
					int nLead = pFileIn.ReadByte();
					if (nLead < 0) break;
					for (iBit = 0; iBit < 8; iBit++)
					{
						if ((nLead & (1 << iBit)) != 0)
						{
							int v1 = pFileIn.ReadByte();
							if (v1 < 0) break;
							pDrawEntity[y1, x1] = (System.Byte)v1;
							x1++;
							iPixel++;
							//System.Diagnostics.Debug.WriteLine("px:" + v1.ToString("X2"));

							while (x1 >= cx) { x1 -= cx; y1++; }
						}
						else
						{
							int v1 = pFileIn.ReadByte();
							int v2 = pFileIn.ReadByte();
							if (v1 < 0 || v2 < 0) break;
							int vv = fIsLittleEndian
								? (v1 | v2 * 256)
								: (v1 * 256 | v2);
							int nBack = fIs3
								? (vv >> 5)
								: (vv >> 4);
							int nSize = fIs3
								? ((vv & 0x1F) + 3)
								: ((vv & 0x0F) + 3);
							int x2 = (iPixel - nBack) % cx;
							int y2 = (iPixel - nBack) / cx;
							for (int v = 0; v < nSize; v++)
							{
								pDrawEntity[y1, x1] = pDrawEntity[y2, x2];
								x1++;
								x2++;
								iPixel++;

								while (x1 >= cx) { x1 -= cx; y1++; }
								while (x2 >= cx) { x2 -= cx; y2++; }
							}
							//System.Diagnostics.Debug.WriteLine("r+:" + ((v1 >> 5) + 8 * v2).ToString() + "," + ((v1 & 0x1F) + 3).ToString());
						}
					}
				}
				if ((x1 + cx * y1) == cx * cy) return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private bool TryToReadAsExtDM1Img(
			System.Byte[] pBinary,
			OverlayPlane pOverlay,
			out Draw4p1bppImg pEntity,
			out ImgPalExtDM1 pImgPal
			)
		{
			pEntity = null;
			pImgPal = null;

			if (23 <= pBinary.Length)
			{
				int cx, cy;
				if (fIsLittleEndian)
				{
					cx = pBinary[0] | ((pBinary[1]&0x01) * 256);
					cy = pBinary[2] | ((pBinary[3]&0x01) * 256);
				}
				else
				{
					cx = ((pBinary[0]&0x01) * 256) | pBinary[1];
					cy = ((pBinary[2]&0x01) * 256) | pBinary[3];
				}
				System.Byte[] pMostUsed6 = new System.Byte[6];
				System.Byte[] pLaterPal16 = new System.Byte[16];

				bool fOverlapImage = !pOverlay.IsFirstPlane();

				for (int iNewPal = 0; iNewPal < 16; iNewPal++)
				{
					pLaterPal16[iNewPal] = pBinary[pBinary.Length - 16 + iNewPal];
				}

				pMostUsed6[0] = (byte)((pBinary[4] >> 4) & 15);
				pMostUsed6[1] = (byte)((pBinary[4] >> 0) & 15);
				pMostUsed6[2] = (byte)((pBinary[5] >> 4) & 15);
				pMostUsed6[3] = (byte)((pBinary[5] >> 0) & 15);
				pMostUsed6[4] = (byte)((pBinary[6] >> 4) & 15);
				pMostUsed6[5] = (byte)((pBinary[6] >> 0) & 15);

				//Dev(pSelectedPal16, iPal2Color, fOverlapImage);
				//System.String sMostUsed6 = pIdxer.ToString();
				//System.Diagnostics.Debug.WriteLine("6          :" + sMostUsed6);

				System.IO.MemoryStream Mem = new System.IO.MemoryStream(pBinary, 6, pBinary.Length - 6 - 16, false);
				BinaryThruRead4Bit pThruIn = new BinaryThruRead4Bit(Mem);

				pThruIn.Read4Bit();
				if (!fOverlapImage)
					pThruIn.Read4Bit();

				Uncompress(cx, cy, pThruIn, pMostUsed6, pLaterPal16, out pEntity, fOverlapImage);

				for (int iMostUsed6 = 0; iMostUsed6 < 6; iMostUsed6++)
				{
					pMostUsed6[iMostUsed6] = pLaterPal16[pMostUsed6[iMostUsed6]];
				}

				pImgPal = new ImgPalExtDM1(pMostUsed6, pLaterPal16);

				pOverlay.NextPlane();
				return true;
			}
			return false;
		}
		// 
		private void TryToReadPlaceHolder()
		{
			pFileIn.Position = 0;
			BinaryReadThru ThruIn = fIsLittleEndian
				? ((BinaryReadThru)new BinaryReadThruLE(pFileIn))
				: ((BinaryReadThru)new BinaryReadThruBE(pFileIn))
				;
			System.UInt16 wSign = ThruIn.ReadWord();
			if (wSign != 32773)
				throw(new System.ApplicationException("Invalid GRAPHICS.DAT"));
			int i, n = ThruIn.ReadWord();
			pPlaceHolders = new PlaceHolder[n];
			int[] nLenEntries = new int[n];
			for (i = 0; i < n; i++)
			{
				nLenEntries[i] = (i == 0)
					? (int)ThruIn.ReadDWord()
					: (int)ThruIn.ReadWord()
					;
			}
			int iOffset = (int)pFileIn.Position;
			for (i = 0; i < n; i++)
			{
				pPlaceHolders[i] = new PlaceHolder(iOffset, nLenEntries[i]);

				iOffset += nLenEntries[i];
			}
		}
		// 
		private ImgType DecideImgType(
			int iIdx
			)
		{
			Entry pEntry = ReadEntryAt(iIdx);
			//
			if (TryToDecodeImageAsU4bpp(pEntry))
				return ImgType.U4bpp;
			if (TryToDecodeImageAsU8bpp(pEntry))
				return ImgType.U8bpp;
			if (TryToDecodeImageAs320x200(pEntry))
				return ImgType.U320x200x8bpp;
			//
			if (TryToDecodeImageAsIntrepray(pEntry))
				return ImgType.Intrepray;
			//
			int iIdxToTryDM1 = iIdx;
			Entry pEntryToTryDM1 = pEntry;
			if (pA2B != null)
			{
				int[] iIdxes = pA2B.Track(iIdx);
				if (iIdxToTryDM1 != iIdxes[0])
				{
					iIdxToTryDM1 = iIdxes[0];
					pEntryToTryDM1 = ReadEntryAt(iIdxToTryDM1);
				}
			}
			if (TryToDecodeImageAsExtDM1(pEntryToTryDM1))
				return ImgType.ExtDM1;
			//
			return ImgType.Unknown;
		}
		// 
		private bool TryToDecodeImageAsExtDM1(
			Entry pEntry
			)
		{
			byte[] pBinary = pEntry.Binary;
			int nLen = pBinary.Length;
			if (nLen <= 7 + 16)
				return false;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			try
			{
				System.IO.MemoryStream pFileIn = new System.IO.MemoryStream(pBinary, 7, nLen - 7 - 16, false);
				BinaryThruRead4Bit pThruIn = new BinaryThruRead4Bit(pFileIn);
				int iPixel = 0, nPixels = cx * cy;
				System.Byte nLead;
				bool fEmpty = false;
				while (iPixel < nPixels)
				{
					nLead = pThruIn.Read4Bit();
					switch (nLead & 7)
					{
						case 0:
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
							fEmpty = false;
							break;
						case 6:
							fEmpty = true;
							break;
						case 7:
							pThruIn.Read4Bit();
							break;
					}
					int nDistance = 1;
					if ((nLead & 8) != 0)
					{
						nDistance = ReadDistance(pThruIn);
					}
					int i;
					for (i = 0; i < nDistance && (!fEmpty || (fEmpty && (cx <= iPixel))); i++, iPixel++);
					if (i != nDistance) break;
				}
				if (iPixel == nPixels && pFileIn.Position == pFileIn.Length) return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private bool TryToDecodeImageAsIntrepray(
			Entry pEntry
			)
		{
			System.Byte[] pBinary = pEntry.Binary;
			int nLen = pBinary.Length;
			if (nLen <= 8)
				return false;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			switch (pBinary[6])
			{
				case 2:
				case 3:
					break;
				default:
					return false;
			}
			bool fIs3 = pBinary[6] == 3;
			//System.Diagnostics.Debug.Assert(fIs3, "Type 2");
			try
			{
				System.IO.MemoryStream pFileIn = new System.IO.MemoryStream(pBinary, 8, nLen - 8, false);
				int iPixel = 0, nPixels = cx * cy;
				int nLead, nBit, v1, v2;
				while (iPixel < nPixels)
				{
					nLead = pFileIn.ReadByte();
					if (nLead < 0) break;
					for (nBit = 0; nBit < 8 && iPixel < nPixels; nBit++)
					{
						if ((nLead & (1 << nBit)) != 0)
						{
							v1 = pFileIn.ReadByte();
							if (v1 < 0) break;
							iPixel++;
						}
						else
						{
							v1 = pFileIn.ReadByte();
							v2 = pFileIn.ReadByte();
							if (v1 < 0 || v2 < 0) break;
							if (fIs3)
							{
								if (iPixel < (v1 >> 5) + 8 * v2) break;
								iPixel += (v1 & 0x1F) + 3;
							}
							else
							{
								if (iPixel < (v1 >> 4) + 16 * v2) break;
								iPixel += (v1 & 0x0F) + 3;
							}
						}
					}
				}
				if (iPixel == nPixels && pFileIn.Position == pFileIn.Length) return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private bool TryToDecodeImageAsU4bpp(
			Entry pEntry
			)
		{
			System.Byte[] pBinary = pEntry.Binary;
			int nLen = pBinary.Length;
			if (nLen <= 7 + 16)
				return false;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			int ey = pBinary[fIsLittleEndian ? 3 : 2];
			if (nLen == 10 + (cx + 1) / 2 * cy + 16)
				if ((ey & 0x7C) != 0x7C) // FIXME:KILL BIT
					return true;
			return false;
		}
		// 
		private bool TryToDecodeImageAsU8bpp(
			Entry pEntry
			)
		{
			System.Byte[] pBinary = pEntry.Binary;
			int nLen = pBinary.Length;
			if (nLen <= 10)
				return false;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			if (nLen == 10 + cx * cy)
				return true;
			return false;
		}
		// 
		private bool TryToDecodeImageAs320x200(
			Entry pEntry
			)
		{
			if (pEntry.Binary.Length == 64000)
				return true;
			return false;
		}
		// 
		private void PlayImgEntryAsIntreprayImg(
			Draw8bppImg pDrawEntity,
			ChromakeySpecifier pMH,
			out System.Drawing.Bitmap pImg
			)
		{
			Play8bppImgEntry(new Rasterize8bppImage(pDrawEntity), pMH, out pImg);
		}
		// 
		private bool TryToReadAs320x200x8bppImg(
			Entry pEntry,
			out System.Drawing.Bitmap pImg
			)
		{
			pImg = null;
			if (pEntry.Binary.Length < 64000)
				return false;
			try
			{
				Draw8bppImg pDrawEntity = new Draw8bppImg(320, 200);
				int i = 0;
				for (int y1 = 0; y1 < 200; y1++)
				{
					for (int x1 = 0; x1 < 320; x1++)
					{
						pDrawEntity[y1, x1] = pEntry.Binary[i];
						i++;
					}
				}
				Play8bppImgEntry(
					new Rasterize8bppImage(pDrawEntity),
					new ChromakeySpecifier(-1),
					out pImg
					);
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return true;
		}
		// 
		private bool TryToReadAsU4bppImg(
			Entry pEntry,
			out Draw4p1bppImg pEntity,
			out ImgPalExtDM1 pImgPal
			)
		{
			pEntity = null;
			pImgPal = null;
			int nLen = pEntry.Binary.Length;
			if (nLen < 10 + 16)
				return false;
			System.Byte[] pBinary = pEntry.Binary;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			if (nLen < 10 + (cx + 1) / 2 * cy + 16)
				return false;
			try
			{
				pEntity = new Draw4p1bppImg(cx, cy);
				System.IO.MemoryStream pFileIn = new System.IO.MemoryStream(pEntry.Binary, 10, nLen - 10 - 16, false);
				BinaryThruRead4Bit pThruIn = new BinaryThruRead4Bit(pFileIn);
				for (int y1 = 0; y1 < cy; y1++)
				{
					for (int x1 = 0; x1 < cx; x1++)
					{
						pEntity[y1, x1] = (byte)(pThruIn.Read4Bit() | 64);
					}
					if ((cx & 1) != 0) pThruIn.Read4Bit();
				}
				int iPalPos = nLen - 16;
				byte[] pPalette16 = new byte[16];
				for (int iPalMov = 0; iPalMov < 16; iPalMov++)
				{
					pPalette16[iPalMov] = pEntry.Binary[iPalPos + iPalMov];
				}
				pImgPal = new ImgPalExtDM1(
					new byte[] {0x8,0x9,0xA,0xB,0xC,0xD},
					pPalette16
					);
				return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private bool TryToReadAsU8bppImg(
			Entry pEntry,
			ChromakeySpecifier pMH,
			out System.Drawing.Bitmap pImg
			)
		{
			pImg = null;
			int nLen = pEntry.Binary.Length;
			if (nLen < 10)
				return false;
			System.Byte[] pBinary = pEntry.Binary;
			int cx = pBinary[fIsLittleEndian ? 0 : 1] | ((pBinary[fIsLittleEndian ? 1 : 0] & 0x01) << 8);
			int cy = pBinary[fIsLittleEndian ? 2 : 3] | ((pBinary[fIsLittleEndian ? 3 : 2] & 0x01) << 8);
			if (nLen < 10 + cx * cy)
				return false;
			try
			{
				Draw8bppImg pDrawEntity = new Draw8bppImg(cx, cy);
				for (int y1 = 0; y1 < cy; y1++)
				{
					for (int x1 = 0; x1 < cx; x1++)
					{
						pDrawEntity[y1, x1] = pBinary[10 + cx * y1 + x1];
					}
				}
				Play8bppImgEntry(
					new Rasterize8bppImage(pDrawEntity),
					pMH,
					out pImg
					);
				return true;
			}
			catch(System.Exception err)
			{
				System.Diagnostics.Debug.WriteLine(err.Message);
			}
			return false;
		}
		// 
		private void CombineImgEntryAsExtDM1(
			Draw4p1bppImg pDest,
			Draw4p1bppImg pSrc,
			bool fFirstImage
			)
		{
			int cx = System.Math.Min(pDest.Width, pSrc.Width);
			int cy = System.Math.Min(pDest.Height, pSrc.Height);
			int x1, y1;
			for (y1 = 0; y1 < cy; y1++)
			{
				for (x1 = 0; x1 < cx; x1++)
				{
					byte s = pSrc[y1, x1];
					if ((s & 16) != 0)
					{
						pDest[y1, x1] = pDest[y1 - 1, x1];
					}
					else if ((s & 32) == 0 || fFirstImage)
					{
						pDest[y1, x1] = s;
					}
				}
			}
		}
		// 
		public bool TryToReadAsPCM11025(
			Entry pEntry,
			out System.Byte[] pWav
			)
		{
			pWav = null;
			if (pEntry.Binary.Length < 4)
				return false;
			if (fIsLittleEndian)
			{
				if (pEntry.Binary[0] != 0x77 || pEntry.Binary[1] != 0x2B || pEntry.Binary[2] != 0x08 || pEntry.Binary[3] != 0x01)
					return false;
			}
			else
			{
				if (pEntry.Binary[0] != 0x2B || pEntry.Binary[1] != 0x77 || pEntry.Binary[2] != 0x08 || pEntry.Binary[3] != 0x01)
					return false;
			}
			// "RIFF" "WAVE" Length = FileLength - 8 // (8+x)+(8+4)+(8+18)+(8+4)
			// "fmt "        Length = 18             // (8+x)+(8+4)+(8+18)
			// "fact"        Length = 4              // (8+x)+(8+4)
			// "data"        Length = x              // (8+x)
			int nWavLen = pEntry.Binary.Length - 4;
			int nMaxLength = (8+4)+(8+18)+(8+4)+(8+nWavLen);
			System.IO.MemoryStream pFileInto = new System.IO.MemoryStream(nMaxLength);
			BinaryWriteThru pThru = new BinaryWriteThruLE(pFileInto);
			pThru.Write((System.UInt32)(0x46464952U)); // "RIFF"
			pThru.Write((System.UInt32)(nMaxLength - 8));
			pThru.Write((System.UInt32)(0x45564157U)); // "WAVE"
			pThru.Write((System.UInt32)(0x20746D66U)); // "fmt "
			pThru.Write((System.UInt32)(18)); // sizeof(WAVEFORMATEX)
			pThru.Write((System.UInt16)(1)); // WAVEFORMATEX.wFormatTag
			pThru.Write((System.UInt16)(1)); // WAVEFORMATEX.nChannels
			pThru.Write((System.UInt32)(11025)); // WAVEFORMATEX.nSamplesPerSec
			pThru.Write((System.UInt32)(11025)); // WAVEFORMATEX.nAvgBytesPerSec
			pThru.Write((System.UInt16)(1)); // WAVEFORMATEX.nBlockAlign
			pThru.Write((System.UInt16)(8)); // WAVEFORMATEX.wBitsPerSample
			pThru.Write((System.UInt16)(0)); // WAVEFORMATEX.cbSize
			pThru.Write((System.UInt32)(0x74636166U)); // "fact"
			pThru.Write((System.UInt32)(4));
			pThru.Write((System.UInt32)(nWavLen)); // total sample count
			pThru.Write((System.UInt32)(0x61746164U)); // "data"
			pThru.Write((System.UInt32)(nWavLen));
			for (int iSample = 0, nSamples = nWavLen; iSample < nSamples; iSample++)
			{
				System.Byte x = pEntry.Binary[4 + iSample];
				x = (System.Byte)(x + 0x80);
				pThru.Write(x);
			}

			pThru = null;
			pWav = pFileInto.ToArray();
			pFileInto.Close();
			return true;
		}
		// 
		private void Dev(
			System.Byte[] p16,
			System.UInt32 n6,
			bool fOverlay
			)
		{
			int c6 = fOverlay ? 5 : 6;
			System.Text.StringBuilder pText = new System.Text.StringBuilder(256);
			int i;
			pText.Append("16:");
			for (i=0; i<16; i++)
				pText.AppendFormat("|{0:X2}", p16[i]);
			pText.Append("|\n");
			pText.Append(" 6:");
			for (i=0; i<c6; i++)
				pText.AppendFormat("| {0:X}", ((n6<<(4*i))>>20)&0xF);
			pText.Append("|\n");
			System.Diagnostics.Debug.Write(pText.ToString());
		}
	}
}
