using System;

namespace DM2CompressPic
{
	// 
	public class BinaryThruWrite4Bit
	{
		// 
		System.IO.Stream m_pFileInto;
		// 
		System.Byte m_iVal = 0;
		// 
		System.Boolean m_fLeading = true;
		// 
		int m_nUsed = 0;

		// 
		public BinaryThruWrite4Bit(
			System.IO.Stream pFileInto
			)
		{
			m_pFileInto = pFileInto;
		}
		// 
		public void Write4Bit(
			System.Byte x
			)
		{
			//System.Diagnostics.Debug.WriteLine("B:" + x.ToString("X"));
			if (m_fLeading)
			{
				m_iVal = (System.Byte)((x & 0x0F)<<4);
			}
			else
			{
				m_iVal|= (System.Byte)((x & 0x0F));

				WriteByte(m_iVal);
				m_iVal = 0;
			}
			m_nUsed++;
			m_fLeading = !m_fLeading;
		}
		// 
		public void Flush()
		{
			if (m_fLeading)
				return;
			WriteByte(m_iVal);
			m_fLeading = !m_fLeading;
		}
		// 
		void WriteByte(
			System.Byte x
			)
		{
			m_pFileInto.WriteByte(x);
		}
	}
	// 
	public class DM2DE
	{
		// 
		System.Byte[,] m_pImage;
		// 
		int m_cx, m_cy;
		// 
		System.Byte[] m_pSelPal;

		// 
		public DM2DE(
			CGDR.Draw8bppImg pDrawEntity,
			System.Byte[] pSelPal
			)
		{
			int x1, y1, cx = pDrawEntity.Width, cy = pDrawEntity.Height;
			m_pImage = new System.Byte[m_cy = cy, m_cx = cx];
			System.Collections.SortedList pMap = new System.Collections.SortedList(16);

			for (x1 = 0; x1 < 16; x1++)
			{
				for (y1 = 0; y1 < 256; y1++)
				{
					if (y1 == pSelPal[x1])
					{
						if (pMap.Contains(y1))
							continue;
						pMap.Add(y1, x1);
						break;
					}
				}
			}

			for (y1 = 0; y1 < cy; y1++)
			{
				for (x1 = 0; x1 < cx; x1++)
				{
					System.Byte iPix = pDrawEntity.Image[y1, x1];
					object p = pMap[(int)iPix];
					if (p != null)
					{
						m_pImage[y1, x1] = (System.Byte)(int)p;
						continue;
					}
					throw new System.ApplicationException("Palette Index " + iPix.ToString() + " referenced from (" + x1.ToString() + "," + y1.ToString() + ") is not Assigned.");
				}
			}

			m_pSelPal = pSelPal.Clone() as System.Byte[];
		}
		// 
		public System.Byte[,] Image
		{
			get { return m_pImage; }
		}
		// 
		public System.Byte[] SelectedPalette
		{
			get { return m_pSelPal; }
		}
		// 
		public int Width
		{
			get { return m_cx; }
		}
		// 
		public int Height
		{
			get { return m_cy; }
		}
	}
	// 
	public class DM2IC
	{
		// 
		struct C8CH
		{
			// 
			public bool fIsPixel;
			// 
			public System.Byte iPix;
			// 
			public int iOffset;
			// 
			public int iLength;
			// 
			public C8CH(System.Byte _iPix)
			{
				fIsPixel = true;
				iPix = _iPix;
				iOffset = 0;
				iLength = 0;
			}
			// 
			public C8CH(int _iOffset, int _iLength)
			{
				fIsPixel = false;
				iPix = 0;
				iOffset = _iOffset;
				iLength = _iLength;
			}
		}

		// 
		public static DM2DE CreateDrawEntity(
			CGDR.Draw8bppImg pDrawEntity
			)
		{
			int[] pHisto = CreateScatterHisto(pDrawEntity);
			System.Byte[] pSelPal = CreateSelPal(pHisto);
			DM2DE pDE = new DM2DE(pDrawEntity, pSelPal);
			return pDE;
		}
		// 
		public static DM2DE CreateDrawEntity(
			CGDR.Draw8bppImg pDrawEntity,
			System.Byte[] pSelPal
			)
		{
			DM2DE pDE = new DM2DE(pDrawEntity, pSelPal);
			return pDE;
		}

		// 
		public static void Compress8(
			CGDR.Draw8bppImg pDE,
			System.IO.Stream pFileInto,
			bool fLittleEndian,
			bool fIs3
			)
		{
			byte[] pPrefix = new byte[8];
			int cx = pDE.Width, cy = pDE.Height;
			pPrefix[0] = (System.Byte)(fLittleEndian ? (cx & 0xFF) : ((cx >> 8) & 0x03));
			pPrefix[1] = (System.Byte)(fLittleEndian ? ((cx >> 8) & 0x03) : (cx & 0xFF));
			pPrefix[2] = (System.Byte)(fLittleEndian ? (cy & 0xFF) : (((cy >> 8) & 0x03) | 0x7C));
			pPrefix[3] = (System.Byte)(fLittleEndian ? (((cy >> 8) & 0x03) | 0x7C) : (cy & 0xFF));
			pPrefix[4] = 0;
			pPrefix[5] = 0;
			pPrefix[6] = (System.Byte)(fIs3 ? 3 : 2);
			pPrefix[7] = 0;
			pFileInto.Write(pPrefix, 0, 8);
			int iPixel = 0, nPixels = cx * cy;
			System.Byte iPix;
			C8CH[] pCH = new C8CH[8];
			int iCH = 0;
			// Type3: XXXX XXXX XXXY YYYY {X: 11-bits (0-2047), Y: 5-bits (0-31)}
			// Type2: XXXX XXXX XXXX YYYY {X: 12-bits (0-4095), Y: 4-bits (0-15)}
			int nTypedMaxLength = fIs3 ? (31+3) : (15+3);
			int nTypedMaxOffset = fIs3 ? 2047 : 4095;
			while (iPixel < nPixels)
			{
				int iMinLength = 3;
				int iMaxLength = System.Math.Min(nTypedMaxLength, nPixels - iPixel);
				int iMinOffset = 1;
				int iMaxOffset = System.Math.Min(nTypedMaxOffset, iPixel);

				int iBestOffset = 0;
				int iBestLength = 0;

				int iChkOffset;
				for (iChkOffset = iMinOffset; iChkOffset <= iMaxOffset; iChkOffset++)
				{
					int iOffset;
					for (iOffset = 0; iOffset < iMaxLength; iOffset++)
					{
						int iOffset1 = iPixel + iOffset;
						int iOffset2 = iPixel + iOffset - iChkOffset;
						//System.Diagnostics.Debug.Assert(0 <= iOffset1 && iOffset1 < nPixels, "Offset1 Out Of Range");
						//System.Diagnostics.Debug.Assert(0 <= iOffset2 && iOffset2 < nPixels, "Offset2 Out Of Range");
						if (pDE.Image[iOffset1 / cx, iOffset1 % cx] != pDE.Image[iOffset2 / cx, iOffset2 % cx]) break;
					}
					if (iMinLength <= iOffset && iBestLength < iOffset)
					{
						iBestOffset = iChkOffset;
						iBestLength = iOffset;

						if (iBestLength == nTypedMaxLength) break;
					}
				}
				if (iBestLength == 0)
				{
					iPix = pDE.Image[iPixel / cx, iPixel % cx];

					pCH[iCH] = new C8CH(iPix);
					iCH++;
					iPixel++;
				}
				else
				{
					pCH[iCH] = new C8CH(iBestOffset, iBestLength);
					iCH++;
					iPixel += iBestLength;
				}
				if (iCH == 8 || iPixel == nPixels)
				{
					int nCommands = iCH;
					System.Byte iCCB = 0;
					for (iCH = 0; iCH < nCommands; iCH++)
						iCCB |= (System.Byte)(pCH[iCH].fIsPixel ? (1 << iCH) : 0);
					pFileInto.WriteByte(iCCB);
					for (iCH = 0; iCH < nCommands; iCH++)
					{
						if (pCH[iCH].fIsPixel)
						{
							pFileInto.WriteByte(pCH[iCH].iPix);
						}
						else
						{
							System.Byte v1, v2;
							//System.Diagnostics.Debug.Assert(3 <= pCH[iCH].iLength && pCH[iCH].iLength <= nTypedMaxLength, "Length Out Of Range");
							//System.Diagnostics.Debug.Assert(0 <= pCH[iCH].iOffset && pCH[iCH].iOffset <= nTypedMaxOffset, "Offset Out Of Range");
							if (fIs3)
							{
								v1 = (System.Byte)(((pCH[iCH].iOffset & 0x0007) << 5)|(pCH[iCH].iLength - 3));
								v2 = (System.Byte)(((pCH[iCH].iOffset & 0xFFF8) >> 3));
							}
							else
							{
								v1 = (System.Byte)(((pCH[iCH].iOffset & 0x000F) << 4)|(pCH[iCH].iLength - 3));
								v2 = (System.Byte)(((pCH[iCH].iOffset & 0xFFF0) >> 4));
							}
							pFileInto.WriteByte(fLittleEndian ? v1 : v2);
							pFileInto.WriteByte(fLittleEndian ? v2 : v1);
						}
					}
					iCH = 0;
				}
			}
		}
		// 
		public static void Compress(
			DM2DE pDE,
			System.IO.Stream pFileInto,
			bool fLittleEndian,
			int iMostUsed6
			)
		{
			System.Byte[] pPrefix = new System.Byte[7];
			System.Byte[] pSuffix = new System.Byte[16];
			int cx = pDE.Width, cy = pDE.Height;
			pPrefix[0] = (System.Byte)(fLittleEndian ? (cx & 0xFF) : ((cx >> 8) & 0x03));
			pPrefix[1] = (System.Byte)(fLittleEndian ? ((cx >> 8) & 0x03) : (cx & 0xFF));
			pPrefix[2] = (System.Byte)(fLittleEndian ? (cy & 0xFF) : ((cy >> 8) & 0x03));
			pPrefix[3] = (System.Byte)(fLittleEndian ? ((cy >> 8) & 0x03) : (cy & 0xFF));
			pPrefix[4] = (System.Byte)((iMostUsed6 >>16) & 0xFF);
			pPrefix[5] = (System.Byte)((iMostUsed6 >> 8) & 0xFF);
			pPrefix[6] = (System.Byte)((iMostUsed6 >> 0) & 0xFF);
			pDE.SelectedPalette.CopyTo(pSuffix, 0);

			int i;
			System.Byte[] pOrgPal = pDE.SelectedPalette;
			System.Byte[] pAltIdx = new System.Byte[16];
			System.Byte[] pMU6Pal = new System.Byte[6];
			for (i = 0; i < 6; i++)
			{
				int iIdx = (iMostUsed6 >> (20 - 4 * i)) & 0xF;
				System.Byte iPal = pOrgPal[iIdx];
				pMU6Pal[i] = iPal;
			}
			for (i = 0; i < 16; i++)
			{
				System.Byte iPal = pOrgPal[i];
				int iAlt = System.Array.IndexOf(pMU6Pal, iPal);
				if (iAlt < 0)
				{
					pAltIdx[i] = (System.Byte)(6 + i);
				}
				else
				{
					pAltIdx[i] = (System.Byte)(iAlt);
				}
			}
			pFileInto.Write(pPrefix, 0, 7);
			//
			BinaryThruWrite4Bit pThruInto = new BinaryThruWrite4Bit(pFileInto);
			int iPixel = 0, nPixels = cx * cy;
			while (iPixel < nPixels)
			{
				System.Byte iPix = pDE.Image[iPixel / cx, iPixel % cx];
				int iIdentialPixel = iPixel + 1;
				for (; iIdentialPixel < nPixels && pDE.Image[iIdentialPixel / cx, iIdentialPixel % cx] == iPix; iIdentialPixel++);
				int iUpperPixel = iPixel;
				if (iPixel >= cx)
				{
					for (; iUpperPixel < nPixels && pDE.Image[iUpperPixel / cx - 1, iUpperPixel % cx] == pDE.Image[iUpperPixel / cx, iUpperPixel % cx]; iUpperPixel++);
				}
				if (iIdentialPixel <= iUpperPixel)
				{
					WritePixelAndDistance(pThruInto, 22, iUpperPixel - iPixel);
					//System.Diagnostics.Debug.WriteLine(string.Format("{0:00},{1:00000},{2:##########}", 22, iUpperPixel - iPixel, iPixel));
					iPixel = iUpperPixel;
				}
				else
				{
					WritePixelAndDistance(pThruInto, pAltIdx[iPix], iIdentialPixel - iPixel);
					//System.Diagnostics.Debug.WriteLine(string.Format("{0:00},{1:00000},{2:##########}", pAltIdx[iPix], iIdentialPixel - iPixel, iPixel));
					iPixel = iIdentialPixel;
				}
			}
			pThruInto.Flush();
			//
			pFileInto.Write(pSuffix, 0, 16);
		}

		// 
		static void Swap(ref int v1, ref int v2) { int v3 = v1; v1 = v2; v2 = v3; }
		// 
		static void Swap(ref byte v1, ref byte v2) { byte v3 = v1; v1 = v2; v2 = v3; }
		// 
		static void WritePixelAndDistance(
			BinaryThruWrite4Bit pThruInto,
			System.Byte iPix,
			int iDistance
			)
		{
			// 0- 5: Most Used 6 Colours
			// 6-21: 16 Colours
			//   22: Transparent
			System.Byte iDistanceBit = (System.Byte)((iDistance > 1) ? 0x8 : 0x0);
			switch (iPix)
			{
				case 0x0:
				case 0x1:
				case 0x2:
				case 0x3:
				case 0x4:
				case 0x5:
					pThruInto.Write4Bit(
						(System.Byte)((iPix) | iDistanceBit)
						);
					break;
				case 0x16:
					pThruInto.Write4Bit(
						(System.Byte)((0x6) | iDistanceBit)
						);
					break;
				case 0x06:
				case 0x07:
				case 0x08:
				case 0x09:
				case 0x0A:
				case 0x0B:
				case 0x0C:
				case 0x0D:
				case 0x0E:
				case 0x0F:
				case 0x10:
				case 0x11:
				case 0x12:
				case 0x13:
				case 0x14:
				case 0x15:
					pThruInto.Write4Bit(
						(System.Byte)(7 | iDistanceBit)
						);
					pThruInto.Write4Bit(
						(System.Byte)(iPix - 6)
						);
					break;
				default:
					System.Diagnostics.Debug.Fail("Invalid Colour Index");
					break;
			}
			if (iDistanceBit != 0)
			{
				WriteDistance(pThruInto, iDistance);
			}
			//System.Diagnostics.Debug.WriteLine("D:" + iDistance.ToString());
		}
		// 
		static void WriteDistance(
			BinaryThruWrite4Bit pThruInto,
			int iDistance
			)
		{
			if (2 <= iDistance && iDistance <= 16)
			{
				pThruInto.Write4Bit((System.Byte)(iDistance - 2));
			} 
			else if (17 <= iDistance && iDistance < 272)
			{
				pThruInto.Write4Bit(0xF);
				pThruInto.Write4Bit((System.Byte)((iDistance - 17) / 16));
				pThruInto.Write4Bit((System.Byte)((iDistance - 17) % 16));
			}
			else if (272 <= iDistance && iDistance < 65536)
			{
				pThruInto.Write4Bit(0xF);
				pThruInto.Write4Bit(0xF);
				pThruInto.Write4Bit(0xF);
				pThruInto.Write4Bit((System.Byte)((iDistance >>12) & 0xF));
				pThruInto.Write4Bit((System.Byte)((iDistance >> 8) & 0xF));
				pThruInto.Write4Bit((System.Byte)((iDistance >> 4) & 0xF));
				pThruInto.Write4Bit((System.Byte)((iDistance >> 0) & 0xF));
			}
			else
			{
				System.Diagnostics.Trace.Fail("Illegal Distance.");
			}
		}
		// 
		static int[] CreateHisto(
			CGDR.Draw8bppImg pDrawEntity
			)
		{
			int[] pHisto = new int[256];
			int x1, y1, cx = pDrawEntity.Width, cy = pDrawEntity.Height;
			for (x1 = 0; x1 < 256; x1++)
				pHisto[x1] = 0;
			for (y1 = 0; y1 < cy; y1++)
			{
				for (x1 = 0; x1 < cx; x1++)
				{
					pHisto[pDrawEntity.Image[y1, x1]]++;
				}
			}
			return pHisto;
		}
		// 
		static int[] CreateScatterHisto(
			CGDR.Draw8bppImg pDrawEntity
			)
		{
			int[] pHisto = new int[256];
			int x1, cx = pDrawEntity.Width, cy = pDrawEntity.Height;
			for (x1 = 0; x1 < 256; x1++)
				pHisto[x1] = 0;
			int iPixel = 0, nPixels = cx * cy;
			System.Byte iPix;
			while (iPixel < nPixels)
			{
				iPix = pDrawEntity.Image[iPixel / cx, iPixel % cx];
				int iDistance1 = iPixel + 1;
				int iDistance2 = iPixel;
				for (; iDistance1 < nPixels && iPix == pDrawEntity.Image[iDistance1 / cx, iDistance1 % cx]; iDistance1++);
				if (iPixel >= cx)
				{
					for (; iDistance2 < nPixels && pDrawEntity.Image[iDistance2 / cx, iDistance2 % cx] == pDrawEntity.Image[iDistance2 / cx - 1, iDistance2 % cx]; iDistance2++);
				}
				if (iDistance1 < iDistance2)
				{
					iPixel = iDistance2;
				}
				else
				{
					pHisto[iPix]++;
					iPixel = iDistance1;
				}
			}
			return pHisto;
		}
		// 
		static System.Byte[] CreateSelPal(
			int[] pHisto
			)
		{
			System.Collections.SortedList pAscent = new System.Collections.SortedList(256);
			int i, n = 0;
			for (i = 0; i < 256; i++)
			{
				if (pHisto[i] != 0) n++;
				pAscent.Add(-256 * (1 + pHisto[i]) + i, i);
			}
			if (n > 16)
				throw new System.ApplicationException("This Image contains " + n.ToString() + " Colors simultaneously. It have to be maximum out of 16");
			System.Byte[] pSelPal = new System.Byte[16];
			int[] pIdx2PalIdx = new int[] {
				0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0x0, 0x1,
				0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xE, 0xF,
			};
			for (i = 0; i < n; i++)
			{
				pSelPal[pIdx2PalIdx[i]] = (System.Byte)(int)pAscent.GetByIndex(i);
			}
			return pSelPal;
		}
	}
	// 
	public class DM2CompressionResultBasis
	{
		// 
		System.Byte[] m_pBinary = null;
		// 
		DM2DE m_pDE = null;

		// 
		public DM2CompressionResultBasis(
			DM2DE pDE,
			System.Byte[] pBinary
			)
		{
			m_pDE = pDE;
			m_pBinary = pBinary;
		}
		// 
		public System.Byte[] Binary
		{
			get { return m_pBinary; }
		}
		// 
		public DM2DE DrawEntity
		{
			get { return m_pDE; }
		}
	}
}
