using System;
using System.IO;

namespace BizHawk.Emulation.Cores.Computers.Commodore64
{
	public static class D64
	{
		private static int[] densityTable =
		{
			3, 3, 3, 3, 3,
			3, 3, 3, 3, 3,
			3, 3, 3, 3, 3,
			3, 3, 2, 2, 2,
			2, 2, 2, 2, 1,
			1, 1, 1, 1, 1,
			0, 0, 0, 0, 0,
			0, 0, 0, 0, 0
		};

		private static int[] gcrDecodeTable =
		{
			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //00xxx
			0xFF, 0x08, 0x00, 0x01, 0xFF, 0x0C, 0x04, 0x05, //01xxx
			0xFF, 0xFF, 0x02, 0x03, 0xFF, 0x0F, 0x06, 0x07, //10xxx
			0xFF, 0x09, 0x0A, 0x0B, 0xFF, 0x0D, 0x0E, 0xFF  //11xxx
		};

		private static int[] gcrEncodeTable =
		{
			Convert.ToByte("01010", 2), // 0
			Convert.ToByte("01011", 2), // 1
			Convert.ToByte("10010", 2), // 2
			Convert.ToByte("10011", 2), // 3
			Convert.ToByte("01110", 2), // 4
			Convert.ToByte("01111", 2), // 5
			Convert.ToByte("10110", 2), // 6
			Convert.ToByte("10111", 2), // 7
			Convert.ToByte("01001", 2), // 8
			Convert.ToByte("11001", 2), // 9
			Convert.ToByte("11010", 2), // A
			Convert.ToByte("11011", 2), // B
			Convert.ToByte("01101", 2), // C
			Convert.ToByte("11101", 2), // D
			Convert.ToByte("11110", 2), // E
			Convert.ToByte("10101", 2)  // F
		};

		private static int[] sectorsPerTrack =
		{
			21, 21, 21, 21, 21,
			21, 21, 21, 21, 21,
			21, 21, 21, 21, 21,
			21, 21, 19, 19, 19,
			19, 19, 19, 19, 18,
			18, 18, 18, 18, 18,
			17, 17, 17, 17, 17,
			17, 17, 17, 17, 17
		};

		private static int[] standardTrackLengthBytes =
		{
			6250, 6666, 7142, 7692
		};

		private static byte Checksum(byte[] source)
		{
			int count = source.Length;
			byte result = 0;

			for (int i = 0; i < count; i++)
				result ^= source[i];

			return result;
		}

		private static byte[] ConvertSectorFromGCR(byte[] source, out int bitsWritten)
		{
			bitsWritten = 0;
			return new byte[] { };
		}

		private static byte[] ConvertSectorToGCR(byte[] source, byte sectorNo, byte trackNo, byte formatA, byte formatB, out int bitsWritten)
		{
			MemoryStream mem = new MemoryStream();
			BinaryWriter writer = new BinaryWriter(mem);
			byte[] writtenData;
			byte headerChecksum = (byte)(sectorNo ^ trackNo ^ formatA ^ formatB);

			// assemble written data for GCR encoding
			writtenData = new byte[260];
			Array.Copy(source, 0, writtenData, 1, 256);
			writtenData[0] = 0x07;
			writtenData[0x101] = Checksum(source);
			writtenData[0x102] = 0x00;
			writtenData[0x103] = 0x00;

			bitsWritten = 0;
			writer.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }); // sync
			writer.Write(EncodeGCR(new byte[] { 0x08, headerChecksum, sectorNo, trackNo, formatA, formatB, 0x0F, 0x0F })); // header
			writer.Write(new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }); // gap
			writer.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }); // sync
			writer.Write(EncodeGCR(writtenData)); // data
			writer.Write(new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }); // gap

			bitsWritten = (int)mem.Length * 8;

			writer.Flush();
			return mem.ToArray();
		}

		private static byte[] DecodeGCR(byte[] source)
		{
			// 5 GCR encoded bytes -> 4 bytes
			int[] gcr = new int[8];
			byte[] data = new byte[4];
			int count = source.Length;
			int outputValue;
			MemoryStream mem = new MemoryStream();
			BinaryWriter writer = new BinaryWriter(mem);

			for (int i = 0; i < count; i += 5)
			{
				Array.Copy(source, i, data, 0, 5);

				// -------- -------- -------- -------- --------
				// 11100000 32222211 44443333 66555554 77777666

				gcr[0] = ((data[0])) & 0x1F;
				gcr[1] = ((data[0] >> 5) | data[1] << 3) & 0x1F;
				gcr[2] = ((data[1] >> 2)) & 0x1F;
				gcr[3] = ((data[1] >> 7) | data[2] << 1) & 0x1F;
				gcr[4] = ((data[2] >> 4) | data[3] << 4) & 0x1F;
				gcr[5] = ((data[3] >> 1)) & 0x1F;
				gcr[6] = ((data[3] >> 6) | data[4] << 2) & 0x1F;
				gcr[7] = ((data[4] >> 3)) & 0x1F;

				outputValue = gcrDecodeTable[gcr[0]] | (gcrDecodeTable[gcr[1]] << 4);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = gcrDecodeTable[gcr[2]] | (gcrDecodeTable[gcr[3]] << 4);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = gcrDecodeTable[gcr[4]] | (gcrDecodeTable[gcr[5]] << 4);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = gcrDecodeTable[gcr[6]] | (gcrDecodeTable[gcr[7]] << 4);
				writer.Write((byte)(outputValue & 0xFF));
			}
			writer.Flush();
			return mem.ToArray();
		}

		private static byte[] EncodeGCR(byte[] source)
		{
			// 4 bytes -> 5 GCR encoded bytes
			int[] gcr = new int[8];
			byte[] data = new byte[4];
			int count = source.Length;
			int outputValue;
			MemoryStream mem = new MemoryStream();
			BinaryWriter writer = new BinaryWriter(mem);

			for (int i = 0; i < count; i += 4)
			{
				Array.Copy(source, i, data, 0, 4);
				gcr[0] = gcrEncodeTable[data[0] & 0xF];
				gcr[1] = gcrEncodeTable[data[0] >> 4];
				gcr[2] = gcrEncodeTable[data[1] & 0xF];
				gcr[3] = gcrEncodeTable[data[1] >> 4];
				gcr[4] = gcrEncodeTable[data[2] & 0xF];
				gcr[5] = gcrEncodeTable[data[2] >> 4];
				gcr[6] = gcrEncodeTable[data[3] & 0xF];
				gcr[7] = gcrEncodeTable[data[3] >> 4];
				
				// -------- -------- -------- -------- --------
				// 11100000 32222211 44443333 66555554 77777666

				outputValue = (gcr[0]) | (gcr[1] << 5);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = (gcr[1] >> 3) | (gcr[2] << 2) | (gcr[3] << 7);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = (gcr[3] >> 1) | (gcr[4] << 4);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = (gcr[4] >> 4) | (gcr[5] << 1) | (gcr[6] << 6);
				writer.Write((byte)(outputValue & 0xFF));
				outputValue = (gcr[6] >> 2) | (gcr[7] << 3);
				writer.Write((byte)(outputValue & 0xFF));
			}
			writer.Flush();
			return mem.ToArray();
		}

		public static Disk Read(byte[] source)
		{
			MemoryStream mem = new MemoryStream(source);
			BinaryReader reader = new BinaryReader(mem);
			Disk result = new Disk();
			int trackCount = 0;

			switch (source.Length)
			{
				case 174848: // 35 tracks no errors
					trackCount = 35;
					break;
				case 175531: // 35 tracks with errors
					trackCount = 35;
					break;
				case 196608: // 40 tracks no errors
					trackCount = 40;
					break;
				case 197376: // 40 tracks with errors
					trackCount = 40;
					break;
			}

			for (int i = 0; i < trackCount; i++)
			{
				Track track = new Track();
				int sectors = sectorsPerTrack[i];
				MemoryStream trackMem = new MemoryStream();

				for (int j = 0; j < sectors; j++)
				{
					int bitsWritten;
					byte[] sectorData = reader.ReadBytes(256);
					byte[] diskData = ConvertSectorToGCR(sectorData, (byte)j, (byte)i, (byte)0x00, (byte)0x00, out bitsWritten);
					trackMem.Write(diskData, 0, diskData.Length);
				}
				track.density = densityTable[i];

				// we pad the tracks with extra gap bytes to meet MNIB standards
				while (trackMem.Length < standardTrackLengthBytes[track.density])
				{
					trackMem.WriteByte(0x55);
				}
				track.data = trackMem.ToArray();
				track.bits = (int)trackMem.Length;
				track.index = i;
				result.tracks.Add(track);
				trackMem.Dispose();
			}

			result.valid = (result.tracks.Count > 0);
			return result;
		}

		public static byte[] Write(Disk source)
		{
			return null;
		}
	}
}
