using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System;

namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
{
	// Timer Emulation
	public class Timer
	{
		public GBHawk Core { get; set; }

		public ushort divider_reg;
		public byte timer_reload;
		public byte timer;
		public byte timer_old;
		public byte timer_control;
		public byte pending_reload;
		public byte write_ignore;
		public bool old_state;
		public bool state;
		public bool reload_block;
		public bool TMA_coincidence;	
		
		public byte ReadReg(int addr)
		{
			byte ret = 0;

			switch (addr)
			{
				case 0xFF04: ret = (byte)(divider_reg >> 8);		break; // DIV register
				case 0xFF05: ret = timer;							break; // TIMA (Timer Counter)
				case 0xFF06: ret = timer_reload;					break; // TMA (Timer Modulo)
				case 0xFF07: ret = timer_control;					break; // TAC (Timer Control)
			}

			return ret;
		}

		public void WriteReg(int addr, byte value)
		{
			switch (addr)
			{
				// DIV register
				case 0xFF04:
					divider_reg = 0;
					break;

				// TIMA (Timer Counter)
				case 0xFF05:
					if (write_ignore == 0)
					{
						timer_old = timer;
						timer = value;
						reload_block = true;
					}
					break;

				// TMA (Timer Modulo)
				case 0xFF06:
					timer_reload = value;
					if (TMA_coincidence)
					{
						timer = timer_reload;
						timer_old = timer;
					}
					break;

				// TAC (Timer Control)
				case 0xFF07:
					timer_control = (byte)((timer_control & 0xf8) | (value & 0x7)); // only bottom 3 bits function
					break;
			}
		}

		public void tick_1()
		{
			if (write_ignore > 0)
			{
				write_ignore--;
				if (write_ignore==0)
				{
					TMA_coincidence = false;
				}
			}

			if (pending_reload > 0)
			{
				pending_reload--;
				if (pending_reload == 0 && !reload_block)
				{
					timer = timer_reload;
					timer_old = timer;
					write_ignore = 4;
					TMA_coincidence = true;

					// set interrupts
					if (Core.REG_FFFF.Bit(2)) { Core.cpu.FlagI = true; }
					Core.REG_FF0F |= 0x04;
				}
			}
		}

		public void tick_2()
		{
			divider_reg++;

			// pick a bit to test based on the current value of timer control
			switch (timer_control & 3)
			{
				case 0:
					state = divider_reg.Bit(9);
					break;
				case 1:
					state = divider_reg.Bit(3);
					break;
				case 2:
					state = divider_reg.Bit(5);
					break;
				case 3:
					state = divider_reg.Bit(7);
					break;
			}

			// And it with the state of the timer on/off bit
			state &= timer_control.Bit(2);

			// this procedure allows several glitchy timer ticks, since it only measures falling edge of the state
			// so things like turning the timer off and resetting the divider will tick the timer
			if (old_state && !state)
			{
				timer_old = timer;
				timer++;

				// if overflow happens, set the interrupt flag and reload the timer (if applicable)
				if (timer < timer_old)
				{
					if (timer_control.Bit(2))
					{
						pending_reload = 4;
						reload_block = false;
					}
					else
					{
						//TODO: Check if timer still gets reloaded if TAC diabled causes overflow
						if (Core.REG_FFFF.Bit(2)) { Core.cpu.FlagI = true; }
						Core.REG_FF0F |= 0x04;
					}				
				}
			}

			old_state = state;
		}

		public void Reset()
		{
			divider_reg = Core._syncSettings._DivInitialTime;
			timer_reload = 0;
			timer = 0;
			timer_old = 0;
			timer_control = 0xF8;
			pending_reload = 0;
			write_ignore = 0;
			old_state = false;
			state = false;
			reload_block = false;
			TMA_coincidence = false;
	}

		public void SyncState(Serializer ser)
		{
			ser.Sync("divider_reg", ref divider_reg);
			ser.Sync("timer_reload", ref timer_reload);
			ser.Sync("timer", ref timer);
			ser.Sync("timer_old", ref timer_old);
			ser.Sync("timer_control", ref timer_control);
			ser.Sync("pending_reload", ref pending_reload);
			ser.Sync("write_ignore", ref write_ignore);
			ser.Sync("old_state", ref old_state);
			ser.Sync("state", ref state);
			ser.Sync("reload_block", ref reload_block);
			ser.Sync("TMA_coincidence", ref TMA_coincidence);
		}
	}
}