using System;
using System.Globalization;
using System.IO;
using System.Collections.Generic;

using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80;

//http://www.ticalc.org/pub/text/calcinfo/

namespace BizHawk.Emulation.Cores.Calculators
{
	[CoreAttributes(
		"TI83Hawk",
		"zeromus",
		isPorted: false,
		isReleased: true
		)]
	public class TI83 : IEmulator
	{
		//hardware
		private readonly Z80A cpu = new Z80A();
		private readonly byte[] rom;
		private byte[] ram;
		private int romPageLow3Bits;
		private int romPageHighBit;
		private byte maskOn;
		private bool onPressed;
		private int keyboardMask;

		private int disp_mode;
		private int disp_move;
		private uint disp_x, disp_y;
		private int m_LinkOutput, m_LinkInput;

		private int m_LinkState
		{
			get
			{
				return (m_LinkOutput | m_LinkInput) ^ 3;
			}
		}

		private bool LinkActive;
		private bool m_CursorMoved;

		//-------

		public byte ReadMemory(ushort addr)
		{
			byte ret;
			int romPage = romPageLow3Bits | (romPageHighBit << 3);
			//Console.WriteLine("read memory: {0:X4}", addr);
			if (addr < 0x4000)
				ret = rom[addr]; //ROM zero-page
			else if (addr < 0x8000)
				ret = rom[romPage * 0x4000 + addr - 0x4000]; //other rom page
			else ret = ram[addr - 0x8000];

			CoreComm.MemoryCallbackSystem.CallRead(addr);

			return ret;
		}

		public void WriteMemory(ushort addr, byte value)
		{
			if (addr < 0x4000)
				return; //ROM zero-page
			else if (addr < 0x8000)
				return; //other rom page
			else ram[addr - 0x8000] = value;

			CoreComm.MemoryCallbackSystem.CallWrite(addr);
		}

		public void WriteHardware(ushort addr, byte value)
		{
			switch (addr)
			{
				case 0: //PORT_LINK
					romPageHighBit = (value >> 4) & 1;
					m_LinkOutput = value & 3;

					if (LinkActive)
					{
						//Prevent rom calls from disturbing link port activity
						if (LinkActive && cpu.RegisterPC < 0x4000)
							return;

						LinkPort.Update();
					}
					break;
				case 1: //PORT_KEYBOARD:
					lagged = false;
					keyboardMask = value;
					//Console.WriteLine("write PORT_KEYBOARD {0:X2}",value);
					break;
				case 2: //PORT_ROMPAGE
					romPageLow3Bits = value & 0x7;
					break;
				case 3: //PORT_STATUS
					maskOn = (byte)(value & 1);
					break;
				case 16: //PORT_DISPCTRL
					//Console.WriteLine("write PORT_DISPCTRL {0}",value);
					WriteDispCtrl(value);
					break;
				case 17: //PORT_DISPDATA
					//Console.WriteLine("write PORT_DISPDATA {0}",value);
					WriteDispData(value);
					break;
			}
		}

		public byte ReadHardware(ushort addr)
		{
			switch (addr)
			{
				case 0: //PORT_LINK
					LinkPort.Update();
					return (byte)((romPageHighBit << 4) | (m_LinkState << 2) | m_LinkOutput);
				case 1: //PORT_KEYBOARD:
					//Console.WriteLine("read PORT_KEYBOARD");
					return ReadKeyboard();
				case 2: //PORT_ROMPAGE
					return (byte)romPageLow3Bits;
				case 3: //PORT_STATUS
					{
						//Console.WriteLine("read PORT_STATUS");
						// Bits:
						// 0   - Set if ON key is down and ON key is trapped
						// 1   - Update things (keyboard etc)
						// 2   - Unknown, but used
						// 3   - Set if ON key is up
						// 4-7 - Unknown
						//if (onPressed && maskOn) ret |= 1;
						//if (!onPressed) ret |= 0x8;
						return (byte)((Controller.IsPressed("ON") ? maskOn : 8) | (LinkActive ? 0 : 2));
					}

				case 4: //PORT_INTCTRL
					//Console.WriteLine("read PORT_INTCTRL");
					return 0xFF;

				case 16: //PORT_DISPCTRL
					//Console.WriteLine("read DISPCTRL");
					break;

				case 17: //PORT_DISPDATA
					return ReadDispData();
			}
			return 0xFF;
		}

		byte ReadKeyboard()
		{
			CoreComm.InputCallback.Call();
			//ref TI-9X

			int ret = 0xFF;
			//Console.WriteLine("keyboardMask: {0:X2}",keyboardMask);
			if ((keyboardMask & 1) == 0)
			{
				if (Controller.IsPressed("DOWN")) ret ^= 1;
				if (Controller.IsPressed("LEFT")) ret ^= 2;
				if (Controller.IsPressed("RIGHT")) ret ^= 4;
				if (Controller.IsPressed("UP")) ret ^= 8;
			}
			if ((keyboardMask & 2) == 0)
			{
				if (Controller.IsPressed("ENTER")) ret ^= 1;
				if (Controller.IsPressed("PLUS")) ret ^= 2;
				if (Controller.IsPressed("MINUS")) ret ^= 4;
				if (Controller.IsPressed("MULTIPLY")) ret ^= 8;
				if (Controller.IsPressed("DIVIDE")) ret ^= 16;
				if (Controller.IsPressed("EXP")) ret ^= 32;
				if (Controller.IsPressed("CLEAR")) ret ^= 64;
			}
			if ((keyboardMask & 4) == 0)
			{
				if (Controller.IsPressed("DASH")) ret ^= 1;
				if (Controller.IsPressed("3")) ret ^= 2;
				if (Controller.IsPressed("6")) ret ^= 4;
				if (Controller.IsPressed("9")) ret ^= 8;
				if (Controller.IsPressed("PARACLOSE")) ret ^= 16;
				if (Controller.IsPressed("TAN")) ret ^= 32;
				if (Controller.IsPressed("VARS")) ret ^= 64;
			}
			if ((keyboardMask & 8) == 0)
			{
				if (Controller.IsPressed("DOT")) ret ^= 1;
				if (Controller.IsPressed("2")) ret ^= 2;
				if (Controller.IsPressed("5")) ret ^= 4;
				if (Controller.IsPressed("8")) ret ^= 8;
				if (Controller.IsPressed("PARAOPEN")) ret ^= 16;
				if (Controller.IsPressed("COS")) ret ^= 32;
				if (Controller.IsPressed("PRGM")) ret ^= 64;
				if (Controller.IsPressed("STAT")) ret ^= 128;
			}
			if ((keyboardMask & 16) == 0)
			{
				if (Controller.IsPressed("0")) ret ^= 1;
				if (Controller.IsPressed("1")) ret ^= 2;
				if (Controller.IsPressed("4")) ret ^= 4;
				if (Controller.IsPressed("7")) ret ^= 8;
				if (Controller.IsPressed("COMMA")) ret ^= 16;
				if (Controller.IsPressed("SIN")) ret ^= 32;
				if (Controller.IsPressed("MATRIX")) ret ^= 64;
				if (Controller.IsPressed("X")) ret ^= 128;
			}

			if ((keyboardMask & 32) == 0)
			{
				if (Controller.IsPressed("STO")) ret ^= 2;
				if (Controller.IsPressed("LN")) ret ^= 4;
				if (Controller.IsPressed("LOG")) ret ^= 8;
				if (Controller.IsPressed("SQUARED")) ret ^= 16;
				if (Controller.IsPressed("NEG1")) ret ^= 32;
				if (Controller.IsPressed("MATH"))
					ret ^= 64;
				if (Controller.IsPressed("ALPHA")) ret ^= 128;
			}

			if ((keyboardMask & 64) == 0)
			{
				if (Controller.IsPressed("GRAPH")) ret ^= 1;
				if (Controller.IsPressed("TRACE")) ret ^= 2;
				if (Controller.IsPressed("ZOOM")) ret ^= 4;
				if (Controller.IsPressed("WINDOW")) ret ^= 8;
				if (Controller.IsPressed("Y")) ret ^= 16;
				if (Controller.IsPressed("2ND")) ret ^= 32;
				if (Controller.IsPressed("MODE")) ret ^= 64;
				if (Controller.IsPressed("DEL")) ret ^= 128;
			}

			return (byte)ret;

		}

		byte ReadDispData()
		{
			if (m_CursorMoved)
			{
				m_CursorMoved = false;
				return 0x00; //not accurate this should be stale data or something
			}

			byte ret;
			if (disp_mode == 1)
			{
				ret = vram[disp_y * 12 + disp_x];
			}
			else
			{
				int column = 6 * (int)disp_x;
				int offset = (int)disp_y * 12 + (column >> 3);
				int shift = 10 - (column & 7);
				ret = (byte)(((vram[offset] << 8) | vram[offset + 1]) >> shift);
			}

			doDispMove();
			return ret;
		}

		void WriteDispData(byte value)
		{
			int offset;
			if (disp_mode == 1)
			{
				offset = (int)disp_y * 12 + (int)disp_x;
				vram[offset] = value;
			}
			else
			{
				int column = 6 * (int)disp_x;
				offset = (int)disp_y * 12 + (column >> 3);
				if (offset < 0x300)
				{
					int shift = column & 7;
					int mask = ~(252 >> shift);
					int Data = value << 2;
					vram[offset] = (byte)(vram[offset] & mask | (Data >> shift));
					if (shift > 2 && offset < 0x2ff)
					{
						offset++;

						shift = 8 - shift;

						mask = ~(252 << shift);
						vram[offset] = (byte)(vram[offset] & mask | (Data << shift));
					}
				}
			}

			doDispMove();
		}

		void doDispMove()
		{
			switch (disp_move)
			{
				case 0: disp_y--; break;
				case 1: disp_y++; break;
				case 2: disp_x--; break;
				case 3: disp_x++; break;
			}

			disp_x &= 0xF; //0xF or 0x1F? dunno
			disp_y &= 0x3F;
		}

		void WriteDispCtrl(byte value)
		{
			if (value <= 1)
				disp_mode = value;
			else if (value >= 4 && value <= 7)
				disp_move = value - 4;
			else if ((value & 0xC0) == 0x40)
			{
				//hardware scroll
			}
			else if ((value & 0xE0) == 0x20)
			{
				disp_x = (uint)(value & 0x1F);
				m_CursorMoved = true;
			}
			else if ((value & 0xC0) == 0x80)
			{
				disp_y = (uint)(value & 0x3F);
				m_CursorMoved = true;
			}
			else if ((value & 0xC0) == 0xC0)
			{
				//contrast
			}
			else if (value == 2)
			{
			}
			else if (value == 3)
			{
			}
			else
			{
			}
		}

		public TI83(CoreComm comm, GameInfo game, byte[] rom)
		{
			CoreComm = comm;
			cpu.ReadMemory = ReadMemory;
			cpu.WriteMemory = WriteMemory;
			cpu.ReadHardware = ReadHardware;
			cpu.WriteHardware = WriteHardware;
			cpu.IRQCallback = IRQCallback;
			cpu.NMICallback = NMICallback;

			this.rom = rom;
			LinkPort = new Link(this);

			//different calculators (different revisions?) have different initPC. we track this in the game database by rom hash
			//if( *(unsigned long *)(m_pRom + 0x6ce) == 0x04D3163E ) m_Regs.PC.W = 0x6ce; //KNOWN
			//else if( *(unsigned long *)(m_pRom + 0x6f6) == 0x04D3163E ) m_Regs.PC.W = 0x6f6; //UNKNOWN

			if (game["initPC"])
				startPC = ushort.Parse(game.OptionValue("initPC"), NumberStyles.HexNumber);

			HardReset();
			SetupMemoryDomains();
		}

		void IRQCallback()
		{
			//Console.WriteLine("IRQ with vec {0} and cpu.InterruptMode {1}", cpu.RegisterI, cpu.InterruptMode);
			cpu.Interrupt = false;
		}

		void NMICallback()
		{
			Console.WriteLine("NMI");
			cpu.NonMaskableInterrupt = false;
		}


		public CoreComm CoreComm { get; private set; }

		protected byte[] vram = new byte[0x300];
		class MyVideoProvider : IVideoProvider
		{
			private readonly TI83 emu;
			public MyVideoProvider(TI83 emu)
			{
				this.emu = emu;
			}

			public int[] GetVideoBuffer()
			{
				//unflatten bit buffer
				int[] pixels = new int[96 * 64];
				int i = 0;
				for (int y = 0; y < 64; y++)
					for (int x = 0; x < 96; x++)
					{
						int offset = y * 96 + x;
						int bufbyte = offset >> 3;
						int bufbit = offset & 7;
						int bit = ((emu.vram[bufbyte] >> (7 - bufbit)) & 1);
						if (bit == 0)
							unchecked { pixels[i++] = (int)0xFFFFFFFF; }
						else
							pixels[i++] = 0;

					}
				return pixels;
			}
			public int VirtualWidth { get { return 96; } }
			public int BufferWidth { get { return 96; } }
			public int BufferHeight { get { return 64; } }
			public int BackgroundColor { get { return 0; } }
		}
		public IVideoProvider VideoProvider
		{
			get { return new MyVideoProvider(this); }
		}

		public ISoundProvider SoundProvider { get { return NullSound.SilenceProvider; } }
		public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(NullSound.SilenceProvider, 735); } }
		public bool StartAsyncSound() { return true; }
		public void EndAsyncSound() { }

		public static readonly ControllerDefinition TI83Controller =
			new ControllerDefinition
			{
				Name = "TI83 Controller",
				BoolButtons = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9","DOT",
					"ON","ENTER",
					"DOWN","LEFT","UP","RIGHT",
					"PLUS","MINUS","MULTIPLY","DIVIDE",
					"CLEAR", "EXP", "DASH", "PARACLOSE", "TAN", "VARS", "PARAOPEN",
					"COS", "PRGM", "STAT", "COMMA", "SIN", "MATRIX", "X",
					"STO", "LN", "LOG", "SQUARED", "NEG1", "MATH", "ALPHA",
					"GRAPH", "TRACE", "ZOOM", "WINDOW", "Y", "2ND", "MODE", "DEL"
				}
			};

		public ControllerDefinition ControllerDefinition { get { return TI83Controller; } }

		public IController Controller { get; set; }

		//configuration
		ushort startPC;

		public void FrameAdvance(bool render, bool rendersound)
		{
			lagged = true;
			//I eyeballed this speed
			for (int i = 0; i < 5; i++)
			{
				onPressed = Controller.IsPressed("ON");
				//and this was derived from other emus
				cpu.ExecuteCycles(10000);
				cpu.Interrupt = true;
			}

			Frame++;
			if (lagged)
			{
				lagCount++;
				isLag = true;
			}
			else
			{
				isLag = false;
			}
		}

		public void HardReset()
		{
			cpu.Reset();
			ram = new byte[0x8000];
			for (int i = 0; i < 0x8000; i++)
				ram[i] = 0xFF;
			cpu.RegisterPC = startPC;

			cpu.IFF1 = false;
			cpu.IFF2 = false;
			cpu.InterruptMode = 2;

			maskOn = 1;
			romPageHighBit = 0;
			romPageLow3Bits = 0;
			keyboardMask = 0;

			disp_mode = 0;
			disp_move = 0;
			disp_x = disp_y = 0;
		}

		private int lagCount = 0;
		private bool lagged = true;
		private bool isLag = false;
		private int frame;
		public int Frame { get { return frame; } set { frame = value; } }
		public int LagCount { get { return lagCount; } set { lagCount = value; } }
		public bool IsLagFrame { get { return isLag; } }

		public void ResetCounters()
		{
			Frame = 0;
			lagCount = 0;
			isLag = false;
		}

		public bool DeterministicEmulation { get { return true; } }

		public byte[] ReadSaveRam() { return null; }
		public void StoreSaveRam(byte[] data) { }
		public void ClearSaveRam() { }
		public bool SaveRamModified
		{
			get { return false; }
			set { }
		}

		public bool BinarySaveStatesPreferred { get { return false; } }
		public void SaveStateBinary(BinaryWriter bw) { SyncState(Serializer.CreateBinaryWriter(bw)); }
		public void LoadStateBinary(BinaryReader br) { SyncState(Serializer.CreateBinaryReader(br)); }
		public void SaveStateText(TextWriter tw) { SyncState(Serializer.CreateTextWriter(tw)); }
		public void LoadStateText(TextReader tr) { SyncState(Serializer.CreateTextReader(tr)); }

		void SyncState(Serializer ser)
		{
			ser.BeginSection("TI83");
			cpu.SyncState(ser);
			ser.Sync("RAM", ref ram, false);
			ser.Sync("romPageLow3Bits", ref romPageLow3Bits);
			ser.Sync("romPageHighBit", ref romPageHighBit);
			ser.Sync("disp_mode", ref disp_mode);
			ser.Sync("disp_move", ref disp_move);
			ser.Sync("disp_x", ref disp_x);
			ser.Sync("disp_y", ref disp_y);
			ser.Sync("m_CursorMoved", ref m_CursorMoved);
			ser.Sync("maskOn", ref maskOn);
			ser.Sync("onPressed", ref onPressed);
			ser.Sync("keyboardMask", ref keyboardMask);
			ser.Sync("m_LinkOutput", ref m_LinkOutput);
			ser.Sync("VRAM", ref vram, false);
			ser.Sync("Frame", ref frame);
			ser.Sync("LagCount", ref lagCount);
			ser.Sync("IsLag", ref isLag);
			ser.EndSection();
		}

		byte[] stateBuffer;
		public byte[] SaveStateBinary()
		{
			if (stateBuffer == null)
			{
				var stream = new MemoryStream();
				var writer = new BinaryWriter(stream);
				SaveStateBinary(writer);
				stateBuffer = stream.ToArray();
				writer.Close();
				return stateBuffer;
			}
			else
			{
				var stream = new MemoryStream(stateBuffer);
				var writer = new BinaryWriter(stream);
				SaveStateBinary(writer);
				writer.Close();
				return stateBuffer;
			}
		}

		public string SystemId { get { return "TI83"; } }
		public string BoardName { get { return null; } }

		private MemoryDomainList _memoryDomains;
		private const ushort RamSizeMask = 0x7FFF;

		private void SetupMemoryDomains()
		{
			var domains = new List<MemoryDomain>
			{
				new MemoryDomain(
					"Main RAM",
					ram.Length,
					MemoryDomain.Endian.Little,
					addr => ram[addr],
					(addr, value) => ram[addr] = value
				)
			};

			_memoryDomains = new MemoryDomainList(domains);
		}

		public MemoryDomainList MemoryDomains { get { return _memoryDomains; } }

		public void Dispose() { }

		public Link LinkPort;

		public class Link
		{
            //Emulates TI linking software.
            //See http://www.ticalc.org/archives/files/fileinfo/294/29418.html for documentation

            //Note: Each hardware read/write to the link port calls tthe update method.

			readonly TI83 Parent;

			private FileStream CurrentFile;
			//private int FileBytesLeft;
			private byte[] VariableData;

			private Action NextStep;
			private Queue<byte> CurrentData = new Queue<byte>();
			private ushort BytesToSend;
			private byte BitsLeft;
			private byte CurrentByte;
			private byte StepsLeft;

			private Status CurrentStatus = Status.Inactive;

			private enum Status
			{
				Inactive,
				PrepareReceive,
				PrepareSend,
				Receive,
				Send
			}

			public Link(TI83 Parent)
			{
				this.Parent = Parent;
			}

			public void Update()
			{
				if (CurrentStatus == Status.PrepareReceive)
				{
					//Get the first byte, and start sending it.
					CurrentByte = CurrentData.Dequeue();
					CurrentStatus = Status.Receive;
					BitsLeft = 8;
					StepsLeft = 5;
				}

				if (CurrentStatus == Status.PrepareSend && Parent.m_LinkState != 3)
				{
					CurrentStatus = Status.Send;
					BitsLeft = 8;
					StepsLeft = 5;
					CurrentByte = 0;
				}

				if (CurrentStatus == Status.Receive)
				{
					switch (StepsLeft)
					{
						case 5:
							//Receive step 1: Lower the other device's line.
							Parent.m_LinkInput = ((CurrentByte & 1) == 1) ? 2 : 1;
							CurrentByte >>= 1;
							StepsLeft--;
							break;

						case 4:
							//Receive step 2: Wait for the calc to lower the other line.
							if ((Parent.m_LinkState & 3) == 0)
								StepsLeft--;
							break;

						case 3:
							//Receive step 3: Raise the other device's line back up.
							Parent.m_LinkInput = 0;
							StepsLeft--;
							break;

						case 2:
							//Receive step 4: Wait for the calc to raise its line back up.
							if ((Parent.m_LinkState & 3) == 3)
								StepsLeft--;
							break;

						case 1:
							//Receive step 5: Finish.   
							BitsLeft--;

							if (BitsLeft == 0)
							{
								if (CurrentData.Count > 0)
									CurrentStatus = Status.PrepareReceive;
								else
								{
									CurrentStatus = Status.Inactive;
									if (NextStep != null)
										NextStep();
								}
							}
							else
								//next bit in the current byte.
								StepsLeft = 5;
							break;
					}
				}
				else if (CurrentStatus == Status.Send)
				{
					switch (StepsLeft)
					{
						case 5:
							//Send step 1: Calc lowers a line.
							if (Parent.m_LinkState != 3)
							{
								int Bit = Parent.m_LinkState & 1;
								int Shift = 8 - BitsLeft;
								CurrentByte |= (byte)(Bit << Shift);
								StepsLeft--;
							}
							break;

						case 4:
							//send step 2: Lower our line.
							Parent.m_LinkInput = Parent.m_LinkOutput ^ 3;
							StepsLeft--;
							break;

						case 3:
							//Send step 3: wait for the calc to raise its line.
							if ((Parent.m_LinkOutput & 3) == 0)
								StepsLeft--;
							break;

						case 2:
							//Send step 4: raise the other devices lines.
							Parent.m_LinkInput = 0;
							StepsLeft--;
							break;

						case 1:
							//Send step 5: Finish
							BitsLeft--;

							if (BitsLeft == 0)
							{
								BytesToSend--;
								CurrentData.Enqueue(CurrentByte);

								if (BytesToSend > 0)
									CurrentStatus = Status.PrepareSend;
								else
								{
									CurrentStatus = Status.Inactive;
									if (NextStep != null)
										NextStep();
								}
							}
							else
							{
								//next bit in the current byte.
								StepsLeft = 5;
							}
							break;
					}
				}
			}

			public void SendFileToCalc(FileStream FS, bool Verify)
			{
				if (Verify)
					VerifyFile(FS);

				FS.Seek(55, SeekOrigin.Begin);
				CurrentFile = FS;
				SendNextFile();
			}

			private void VerifyFile(FileStream FS)
			{
				//Verify the file format.
				byte[] Expected = new byte[] { 0x2a, 0x2a, 0x54, 0x49, 0x38, 0x33, 0x2a, 0x2a, 0x1a, 0x0a, 0x00 };
				byte[] Actual = new byte[11];

				FS.Seek(0, SeekOrigin.Begin);
				FS.Read(Actual, 0, 11);

				//Check the header.
				for (int n = 0; n < 11; n++)
					if (Expected[n] != Actual[n])
					{
						FS.Close();
						throw new IOException("Invalid Header.");
					}

				//Seek to the end of the comment.
				FS.Seek(53, SeekOrigin.Begin);

				int Size = FS.ReadByte() + FS.ReadByte() * 256;

				if (FS.Length != Size + 57)
				{
					FS.Close();
					throw new IOException("Invalid file length.");
				}

				//Verify the checksum.
				ushort Checksum = 0;
				for (int n = 0; n < Size; n++)
					Checksum += (ushort)FS.ReadByte();

				ushort ActualChecksum = (ushort)(FS.ReadByte() + FS.ReadByte() * 256);

				if (Checksum != ActualChecksum)
				{
					FS.Close();
					throw new IOException("Invalid Checksum.");
				}
			}

			private void SendNextFile()
			{
				byte[] Header = new byte[13];
				if (!CurrentFile.CanRead || CurrentFile.Read(Header, 0, 13) != 13)
				{
					//End of file.
					CurrentFile.Close();
					return;
				}

				int Size = Header[2] + Header[3] * 256;
				VariableData = new byte[Size + 2];
				CurrentFile.Read(VariableData, 0, Size + 2);

				//Request to send the file.
				CurrentData.Clear();

				CurrentData.Enqueue(0x03);
				CurrentData.Enqueue(0xC9);
				foreach (byte B in Header)
					CurrentData.Enqueue(B);

				//Calculate the checksum for the command.
				ushort Checksum = 0;
				for (int n = 2; n < Header.Length; n++)
					Checksum += Header[n];

				CurrentData.Enqueue((byte)(Checksum % 256));
				CurrentData.Enqueue((byte)(Checksum / 256));

				//Finalize the command.
				CurrentStatus = Status.PrepareReceive;
				NextStep = ReceiveReqAck;
				Parent.LinkActive = true;
			}

			private void ReceiveReqAck()
			{
				Parent.LinkActive = false;
				CurrentData.Clear();

				//Prepare to receive the Aknowledgement response from the calculator.
				BytesToSend = 8;
				CurrentStatus = Status.PrepareSend;
				NextStep = SendVariableData;
			}

			private void SendVariableData()
			{
				//Check to see if out of memory first.
				CurrentData.Dequeue();
				CurrentData.Dequeue();
				CurrentData.Dequeue();
				CurrentData.Dequeue();
				CurrentData.Dequeue();

				if (CurrentData.Dequeue() == 0x36)
					OutOfMemory();
				else
				{
					CurrentData.Clear();

					CurrentData.Enqueue(0x03);
					CurrentData.Enqueue(0x56);
					CurrentData.Enqueue(0x00);
					CurrentData.Enqueue(0x00);

					CurrentData.Enqueue(0x03);
					CurrentData.Enqueue(0x15);

					//Add variable data.
					foreach (byte B in VariableData)
						CurrentData.Enqueue(B);

					//Calculate the checksum.
					ushort Checksum = 0;
					for (int n = 2; n < VariableData.Length; n++)
						Checksum += VariableData[n];

					CurrentData.Enqueue((byte)(Checksum % 256));
					CurrentData.Enqueue((byte)(Checksum / 256));

					CurrentStatus = Status.PrepareReceive;
					NextStep = ReceiveDataAck;
					Parent.LinkActive = true;
				}
			}

			private void ReceiveDataAck()
			{
				Parent.LinkActive = false;
				CurrentData.Clear();

				//Prepare to receive the Aknowledgement response from the calculator.
				BytesToSend = 4;
				CurrentStatus = Status.PrepareSend;
				NextStep = EndTransmission;
			}

			private void EndTransmission()
			{
				CurrentData.Clear();

				//Send the end transmission command.
				CurrentData.Enqueue(0x03);
				CurrentData.Enqueue(0x92);
				CurrentData.Enqueue(0x00);
				CurrentData.Enqueue(0x00);

				CurrentStatus = Status.PrepareReceive;
                NextStep = FinalizeFile;
				Parent.LinkActive = true;
			}

			private void OutOfMemory()
			{
				CurrentFile.Close();
				Parent.LinkActive = false;
				CurrentData.Clear();

				//Prepare to receive the Aknowledgement response from the calculator.
				BytesToSend = 3;
				CurrentStatus = Status.PrepareSend;
				NextStep = EndOutOfMemory;
			}

			private void EndOutOfMemory()
			{
				CurrentData.Clear();

				//Send the end transmission command.
				CurrentData.Enqueue(0x03);
				CurrentData.Enqueue(0x56);
				CurrentData.Enqueue(0x01);
				CurrentData.Enqueue(0x00);

				CurrentStatus = Status.PrepareReceive;
                NextStep = FinalizeFile;
				Parent.LinkActive = true;
			}

			//adelikat: This isn't used (yet?) and causes a warning.  Did you really mean to override finalize? if so it should be protected virtual, else rename this
			//Envian: I didn't notice there was a naming conflict. Fixed it since this method is necessary for the file upload proccess.
			private void FinalizeFile()
			{
                //Resets the link software, and checks to see if there is an additional file to send.
				CurrentData.Clear();
				Parent.LinkActive = false;
				NextStep = null;
				SendNextFile();
			}
		}

		public object GetSettings() { return null; }
		public object GetSyncSettings() { return null; }
		public bool PutSettings(object o) { return false; }
		public bool PutSyncSettings(object o) { return false; }

		public Dictionary<string, int> GetCpuFlagsAndRegisters()
		{
			return new Dictionary<string, int>
			{
				{ "A", cpu.RegisterA },
				{ "AF", cpu.RegisterAF },
				{ "B", cpu.RegisterB },
				{ "BC", cpu.RegisterBC },
				{ "C", cpu.RegisterC },
				{ "D", cpu.RegisterD },
				{ "DE", cpu.RegisterDE },
				{ "E", cpu.RegisterE },
				{ "F", cpu.RegisterF },
				{ "H", cpu.RegisterH },
				{ "HL", cpu.RegisterHL },
				{ "I", cpu.RegisterI },
				{ "IX", cpu.RegisterIX },
				{ "IY", cpu.RegisterIY },
				{ "L", cpu.RegisterL },
				{ "PC", cpu.RegisterPC },
				{ "R", cpu.RegisterR },
				{ "Shadow AF", cpu.RegisterShadowAF },
				{ "Shadow BC", cpu.RegisterShadowBC },
				{ "Shadow DE", cpu.RegisterShadowDE },
				{ "Shadow HL", cpu.RegisterShadowHL },
				{ "SP", cpu.RegisterSP },
				{ "Flag C", cpu.RegisterF.Bit(0) ? 1 : 0 },
				{ "Flag N", cpu.RegisterF.Bit(1) ? 1 : 0 },
				{ "Flag P/V", cpu.RegisterF.Bit(2) ? 1 : 0 },
				{ "Flag 3rd", cpu.RegisterF.Bit(3) ? 1 : 0 },
				{ "Flag H", cpu.RegisterF.Bit(4) ? 1 : 0 },
				{ "Flag 5th", cpu.RegisterF.Bit(5) ? 1 : 0 },
				{ "Flag Z", cpu.RegisterF.Bit(6) ? 1 : 0 },
				{ "Flag S", cpu.RegisterF.Bit(7) ? 1 : 0 }
			};
		}
	}
}