//#define ARM_DEBUG

namespace GarboDev
{
	using System;
	using System.IO;
	using System.Threading;
	using System.Timers;
	using System.Windows.Forms;
	using System.Collections.Generic;

	using BizHawk.Emulation;
	using BizHawk;

	public class GbaManager : IEmulator, ISyncSoundProvider, IVideoProvider
	{
		public const int cpuFreq = 16 * 1024 * 1024;

		private int framesRendered;

		private Arm7Processor arm7 = null;
		private Memory memory = null;
		private VideoManager videoManager = null;
		private SoundManager soundManager = null;

		private bool skipBios = false;

		public delegate void CpuUpdateDelegate(Arm7Processor processor, Memory memory);
		private event CpuUpdateDelegate onCpuUpdate = null;

		public Arm7Processor Arm7
		{
			get { return this.arm7; }
		}

		public VideoManager VideoManager
		{
			get { return this.videoManager; }
		}

		public SoundManager SoundManager
		{
			get { return this.soundManager; }
		}

		public Memory Memory
		{
			get { return this.memory; }
		}

		public Dictionary<uint, bool> Breakpoints
		{
			get { return this.arm7.Breakpoints; }
		}

		public ushort KeyState
		{
			get
			{
				if (this.memory != null)
				{
					return this.memory.KeyState;
				}

				return 0x3FF;
			}

			set
			{
				this.arm7.KeyState = value;
			}
		}

		public int FramesRendered
		{
			get { return this.framesRendered; }
			set { this.framesRendered = value; }
		}

		public event CpuUpdateDelegate OnCpuUpdate
		{
			add
			{
				this.onCpuUpdate += value;
				this.onCpuUpdate(this.arm7, this.memory);
			}
			remove
			{
				this.onCpuUpdate -= value;
			}
		}

		public bool SkipBios
		{
			get { return this.skipBios; }
			set { this.skipBios = value; }
		}

		public GbaManager(CoreComm comm)
		{
			_corecomm = comm;

			this.memory = new Memory();
			this.arm7 = new Arm7Processor(this.memory);
			this.videoManager = new VideoManager(this);
			this.videoManager.Memory = this.memory;
			this.soundManager = new SoundManager(this.memory, 44100);
		
			this.framesRendered = 0;
			Renderer renderer = new Renderer();
			renderer.Initialize(null);
			VideoManager.Renderer = renderer;

			videoManager.Presenter = delegate(uint[] data)
			{
				Buffer.BlockCopy(data, 0, this.vbuf, 0, 240 * 160 * 4);
			};
		}

		public void Load(byte[] rom, byte[] bios)
		{
			LoadBios(bios);
			LoadRom(rom);
		}

		public void Reset()
		{
			//this.Halt();

			this.arm7.Reset(this.skipBios);
			this.memory.Reset();
			this.videoManager.Reset();
		}

		public void LoadState(BinaryReader state)
		{
		}

		public void SaveState(BinaryWriter state)
		{
			state.Write("GARB");
		}

		public void LoadBios(byte[] biosRom)
		{
			this.memory.LoadBios(biosRom);

			if (this.onCpuUpdate != null)
			{
				this.onCpuUpdate(this.arm7, this.memory);
			}
		}

		public void LoadRom(byte[] cartRom)
		{
			//this.Halt();
			
			/*
			byte[] logo = new byte[]
                    {
            			0x24,0xff,0xae,0x51,0x69,0x9a,0xa2,0x21,
            			0x3d,0x84,0x82,0x0a,0x84,0xe4,0x09,0xad,
            			0x11,0x24,0x8b,0x98,0xc0,0x81,0x7f,0x21,
            			0xa3,0x52,0xbe,0x19,0x93,0x09,0xce,0x20,
	    	        	0x10,0x46,0x4a,0x4a,0xf8,0x27,0x31,0xec,
        	    		0x58,0xc7,0xe8,0x33,0x82,0xe3,0xce,0xbf,
	        	    	0x85,0xf4,0xdf,0x94,0xce,0x4b,0x09,0xc1,
		        	    0x94,0x56,0x8a,0xc0,0x13,0x72,0xa7,0xfc,
    		    	    0x9f,0x84,0x4d,0x73,0xa3,0xca,0x9a,0x61,
        		    	0x58,0x97,0xa3,0x27,0xfc,0x03,0x98,0x76,
	        		    0x23,0x1d,0xc7,0x61,0x03,0x04,0xae,0x56,
    		        	0xbf,0x38,0x84,0x00,0x40,0xa7,0x0e,0xfd,
	    		        0xff,0x52,0xfe,0x03,0x6f,0x95,0x30,0xf1,
            			0x97,0xfb,0xc0,0x85,0x60,0xd6,0x80,0x25,
	            		0xa9,0x63,0xbe,0x03,0x01,0x4e,0x38,0xe2,
		        	    0xf9,0xa2,0x34,0xff,0xbb,0x3e,0x03,0x44,
			            0x78,0x00,0x90,0xcb,0x88,0x11,0x3a,0x94,
            			0x65,0xc0,0x7c,0x63,0x87,0xf0,0x3c,0xaf,
	            		0xd6,0x25,0xe4,0x8b,0x38,0x0a,0xac,0x72,
		            	0x21,0xd4,0xf8,0x07
                    };

			Array.Copy(logo, 0, cartRom, 4, logo.Length);
			cartRom[0xB2] = 0x96;
			cartRom[0xBD] = 0;
			for (int i = 0xA0; i <= 0xBC; i++) cartRom[0xBD] = (byte)(cartRom[0xBD] - cartRom[i]);
			cartRom[0xBD] = (byte)((cartRom[0xBD] - 0x19) & 0xFF);
			*/
			this.memory.LoadCartridge(cartRom);

			this.Reset();

			if (this.onCpuUpdate != null)
			{
				this.onCpuUpdate(this.arm7, this.memory);
			}
		}

		public void Step()
		{
			//this.Halt();

			this.arm7.Step();

			if (this.onCpuUpdate != null)
			{
				this.onCpuUpdate(this.arm7, this.memory);
			}
		}

		public void StepScanline()
		{
			//this.Halt();

			this.arm7.Execute(960);
			this.videoManager.RenderLine();
			this.videoManager.EnterHBlank(this.arm7);
			this.arm7.Execute(272);
			this.videoManager.LeaveHBlank(this.arm7);

			if (this.onCpuUpdate != null)
			{
				this.onCpuUpdate(this.arm7, this.memory);
			}
		}

		void UpdateInputState()
		{
			ushort ret = 0;
			if (_controller["Up"]) ret |= 64;
			if (_controller["Down"]) ret |= 128;
			if (_controller["Left"]) ret |= 32;
			if (_controller["Right"]) ret |= 16;
			if (_controller["Select"]) ret |= 4;
			if (_controller["Start"]) ret |= 8;
			if (_controller["B"]) ret |= 2;
			if (_controller["A"]) ret |= 1;
			if (_controller["L"]) ret |= 512;
			if (_controller["R"]) ret |= 256;
			ret ^= 0x3ff;
			KeyState = ret;
		}

		private void StepFrame()
		{
			UpdateInputState();
			if (_controller["Power"])
				Reset();

			int vramCycles = 0;
			bool inHblank = false;

			//HighPerformanceTimer profileTimer = new HighPerformanceTimer();

			while (true)
			{


				const int cycleStep = 123;


				if (vramCycles <= 0)
				{
					if (inHblank)
					{
						vramCycles += 960;
						bool HitVBlank = this.videoManager.LeaveHBlank(this.arm7);
						inHblank = false;
						if (HitVBlank)
							break;
					}
					else
					{
						vramCycles += 272;
						this.videoManager.RenderLine();
						this.videoManager.EnterHBlank(this.arm7);
						inHblank = true;
					}
				}

				this.arm7.Execute(cycleStep);
#if ARM_DEBUG
                            if (this.arm7.BreakpointHit)
                            {
                                this.waitingToHalt = true;
                                Monitor.Wait(this);
                            }
#endif
				vramCycles -= cycleStep;
				this.arm7.FireIrq();
			}


		}

		IVideoProvider IEmulator.VideoProvider
		{
			get { return this; }
		}

		ISoundProvider IEmulator.SoundProvider
		{
			get { return null; }
		}

		ISyncSoundProvider IEmulator.SyncSoundProvider
		{
			get { return this; }
		}

		bool IEmulator.StartAsyncSound()
		{
			return false;
		}

		void IEmulator.EndAsyncSound()
		{

		}

		ControllerDefinition IEmulator.ControllerDefinition
		{
			get { return BizHawk.Emulation.Consoles.Nintendo.GBA.GBA.GBAController; }
		}

		IController _controller;

		IController IEmulator.Controller
		{
			get { return _controller; }
			set { _controller = value; }
		}

		void IEmulator.FrameAdvance(bool render, bool rendersound)
		{
			StepFrame();
		}

		int IEmulator.Frame
		{
			get { return 0; }
		}

		int IEmulator.LagCount
		{
			get
			{
				return 0;
			}
			set
			{

			}
		}

		bool IEmulator.IsLagFrame
		{
			get { return false; }
		}

		string IEmulator.SystemId
		{
			get { return "GBA"; }
		}

		bool IEmulator.DeterministicEmulation
		{
			get { return true; }
		}

		byte[] IEmulator.ReadSaveRam()
		{
			return new byte[0];
		}

		void IEmulator.StoreSaveRam(byte[] data)
		{

		}

		void IEmulator.ClearSaveRam()
		{

		}

		bool IEmulator.SaveRamModified
		{
			get
			{
				return false;
			}
			set
			{

			}
		}

		void IEmulator.ResetFrameCounter()
		{

		}

		void IEmulator.SaveStateText(TextWriter writer)
		{

		}

		void IEmulator.LoadStateText(TextReader reader)
		{

		}

		void IEmulator.SaveStateBinary(BinaryWriter writer)
		{

		}

		void IEmulator.LoadStateBinary(BinaryReader reader)
		{

		}

		byte[] IEmulator.SaveStateBinary()
		{
			return new byte[16];
		}

		CoreComm _corecomm;

		CoreComm IEmulator.CoreComm
		{
			get { return _corecomm; }
		}

		IList<MemoryDomain> IEmulator.MemoryDomains
		{
			get { return new List<MemoryDomain>(); }
		}

		MemoryDomain IEmulator.MainMemory
		{
			get { return null; }
		}

		void IDisposable.Dispose()
		{

		}

		int[] vbuf = new int[240 * 160];
		int[] IVideoProvider.GetVideoBuffer() { return vbuf; }
		int IVideoProvider.VirtualWidth { get { return 240; } }
		int IVideoProvider.BufferWidth { get { return 240; } }
		int IVideoProvider.BufferHeight { get { return 160; } }
		int IVideoProvider.BackgroundColor { get { return unchecked((int)0xff000000); } }

		void ISyncSoundProvider.GetSamples(out short[] samples, out int nsamp)
		{
			nsamp = soundManager.SamplesMixed / 2;
			samples = new short[nsamp * 2];
			soundManager.GetSamples(samples, nsamp * 2);
		}

		void ISyncSoundProvider.DiscardSamples()
		{
			// should implement
		}
	}
}
