namespace GarboDev
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    public class Arm7Processor
    {
        private Memory memory = null;
        private FastArmCore armCore = null;
        private ThumbCore thumbCore = null;
        private Dictionary<uint, bool> breakpoints = null;

        private int cycles = 0;
        private int timerCycles = 0;
        private int soundCycles = 0;

        // CPU mode definitions
        public const uint USR = 0x10;
        public const uint FIQ = 0x11;
        public const uint IRQ = 0x12;
        public const uint SVC = 0x13;
        public const uint ABT = 0x17;
        public const uint UND = 0x1B;
        public const uint SYS = 0x1F;

        // CPSR bit definitions
        public const int N_BIT = 31;
        public const int Z_BIT = 30;
        public const int C_BIT = 29;
        public const int V_BIT = 28;
        public const int I_BIT = 7;
        public const int F_BIT = 6;
        public const int T_BIT = 5;

        public const uint N_MASK = (uint)(1U << N_BIT);
        public const uint Z_MASK = (uint)(1U << Z_BIT);
        public const uint C_MASK = (uint)(1U << C_BIT);
        public const uint V_MASK = (uint)(1U << V_BIT);
        public const uint I_MASK = (uint)(1U << I_BIT);
        public const uint F_MASK = (uint)(1U << F_BIT);
        public const uint T_MASK = (uint)(1U << T_BIT);

        // Standard registers
        private uint[] registers = new uint[16];
        private uint cpsr = 0;

        // Banked registers
        private uint[] bankedFIQ = new uint[7];
        private uint[] bankedIRQ = new uint[2];
        private uint[] bankedSVC = new uint[2];
        private uint[] bankedABT = new uint[2];
        private uint[] bankedUND = new uint[2];

        // Saved CPSR's
        private uint spsrFIQ = 0;
        private uint spsrIRQ = 0;
        private uint spsrSVC = 0;
        private uint spsrABT = 0;
        private uint spsrUND = 0;

        private ushort keyState;

        private bool breakpointHit = false;
        private bool cpuHalted = false;

        public ushort KeyState
        {
            set { this.keyState = value; }
        }

        public int Cycles
        {
            get { return this.cycles; }
            set { this.cycles = value; }
        }

        public bool ArmState
        {
            get { return (this.cpsr & Arm7Processor.T_MASK) != Arm7Processor.T_MASK; }
        }

        public uint[] Registers
        {
            get { return this.registers; }
        }

        public uint CPSR
        {
            get { return this.cpsr; }
            set { this.cpsr = value; }
        }

        public bool SPSRExists
        {
            get
            {
                switch (this.cpsr & 0x1F)
                {
                    case USR:
                    case SYS:
                        return false;
                    case FIQ:
                    case SVC:
                    case ABT:
                    case IRQ:
                    case UND:
                        return true;
                    default:
                        return false;
                }
            }
        }

        public uint SPSR
        {
            get
            {
                switch (this.cpsr & 0x1F)
                {
                    case USR:
                    case SYS:
                        return 0xFFFFFFFF;
                    case FIQ:
                        return this.spsrFIQ;
                    case SVC:
                        return this.spsrSVC;
                    case ABT:
                        return this.spsrABT;
                    case IRQ:
                        return this.spsrIRQ;
                    case UND:
                        return this.spsrUND;
                    default:
                        throw new Exception("Unhandled CPSR state...");
                }
            }
            set
            {
                switch (this.cpsr & 0x1F)
                {
                    case USR:
                    case SYS:
                        break;
                    case FIQ:
                        this.spsrFIQ = value;
                        break;
                    case SVC:
                        this.spsrSVC = value;
                        break;
                    case ABT:
                        this.spsrABT = value;
                        break;
                    case IRQ:
                        this.spsrIRQ = value;
                        break;
                    case UND:
                        this.spsrUND = value;
                        break;
                    default:
                        throw new Exception("Unhandled CPSR state...");
                }
            }
        }

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

        public bool BreakpointHit
        {
            get { return this.breakpointHit; }
            set { this.breakpointHit = value; }
        }

        public Arm7Processor(Memory memory)
        {
            this.memory = memory;
            this.memory.Processor = this;
            this.armCore = new FastArmCore(this, this.memory);
            this.thumbCore = new ThumbCore(this, this.memory);
            this.breakpoints = new Dictionary<uint, bool>();
            this.breakpointHit = false;
        }

        private void SwapRegsHelper(uint[] swapRegs)
        {
            for (int i = 14; i > 14 - swapRegs.Length; i--)
            {
                uint tmp = this.registers[i];
                this.registers[i] = swapRegs[swapRegs.Length - (14 - i) - 1];
                swapRegs[swapRegs.Length - (14 - i) - 1] = tmp;
            }
        }

        private void SwapRegisters(uint bank)
        {
            switch (bank & 0x1F)
            {
                case FIQ:
                    this.SwapRegsHelper(this.bankedFIQ);
                    break;
                case SVC:
                    this.SwapRegsHelper(this.bankedSVC);
                    break;
                case ABT:
                    this.SwapRegsHelper(this.bankedABT);
                    break;
                case IRQ:
                    this.SwapRegsHelper(this.bankedIRQ);
                    break;
                case UND:
                    this.SwapRegsHelper(this.bankedUND);
                    break;
            }
        }

        public void WriteCpsr(uint newCpsr)
        {
            if ((newCpsr & 0x1F) != (this.cpsr & 0x1F))
            {
                // Swap out the old registers
                this.SwapRegisters(this.cpsr);
                // Swap in the new registers
                this.SwapRegisters(newCpsr);
            }

            this.cpsr = newCpsr;
        }

        public void EnterException(uint mode, uint vector, bool interruptsDisabled, bool fiqDisabled)
        {
            uint oldCpsr = this.cpsr;

            if ((oldCpsr & Arm7Processor.T_MASK) != 0)
            {
                registers[15] += 2U;
            }

            // Clear T bit, and set mode
            uint newCpsr = (oldCpsr & ~0x3FU) | mode;
            if (interruptsDisabled) newCpsr |= 1 << 7;
            if (fiqDisabled) newCpsr |= 1 << 6;
            this.WriteCpsr(newCpsr);

            this.SPSR = oldCpsr;
            registers[14] = registers[15];
            registers[15] = vector;

            this.ReloadQueue();
        }

        public void RequestIrq(int irq)
        {
            ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);
            iflag |= (ushort)(1 << irq);
            Memory.WriteU16(this.memory.IORam, Memory.IF, iflag);
        }

        public void FireIrq()
        {
            ushort ime = Memory.ReadU16(this.memory.IORam, Memory.IME);
            ushort ie = Memory.ReadU16(this.memory.IORam, Memory.IE);
            ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);

            if ((ie & (iflag)) != 0 && (ime & 1) != 0 && (this.cpsr & (1 << 7)) == 0)
            {
                // Off to the irq exception vector
                this.EnterException(Arm7Processor.IRQ, 0x18, true, false);
            }
        }

        public void Reset(bool skipBios)
        {
            this.breakpointHit = false;
            this.cpuHalted = false;

            // Default to ARM state
            this.cycles = 0;
            this.timerCycles = 0;
            this.soundCycles = 0;

            this.bankedSVC[0] = 0x03007FE0;
            this.bankedIRQ[0] = 0x03007FA0;

            this.cpsr = SYS;
            this.spsrSVC = this.cpsr;
            for (int i = 0; i < 15; i++) this.registers[i] = 0;

            if (skipBios)
            {
                this.registers[15] = 0x8000000;
            }
            else
            {
                this.registers[15] = 0;
            }

            this.armCore.BeginExecution();
        }

        public void Halt()
        {
            this.cpuHalted = true;
            this.cycles = 0;
        }

        public void Step()
        {
            this.breakpointHit = false;

            if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
            {
                this.thumbCore.Step();
            }
            else
            {
                this.armCore.Step();
            }

            this.UpdateTimers();
        }

        public void ReloadQueue()
        {
            if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
            {
                this.thumbCore.BeginExecution();
            }
            else
            {
                this.armCore.BeginExecution();
            }
        }

        private void UpdateTimer(int timer, int cycles, bool countUp)
        {
            ushort control = Memory.ReadU16(this.memory.IORam, Memory.TM0CNT + (uint)(timer * 4));

            // Make sure timer is enabled, or count up is disabled
            if ((control & (1 << 7)) == 0) return;
            if (!countUp && (control & (1 << 2)) != 0) return;

            if (!countUp)
            {
                switch (control & 3)
                {
                    case 0: cycles *= 1 << 10; break;
                    case 1: cycles *= 1 << 4; break;
                    case 2: cycles *= 1 << 2; break;
                    // Don't need to do anything for case 3
                }
            }

            this.memory.TimerCnt[timer] += (uint)cycles;
            uint timerCnt = this.memory.TimerCnt[timer] >> 10;

            if (timerCnt > 0xffff)
            {
                ushort soundCntX = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_X);
                if ((soundCntX & (1 << 7)) != 0)
                {
                    ushort soundCntH = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_H);
                    if (timer == ((soundCntH >> 10) & 1))
                    {
                        // FIFO A overflow
                        this.memory.SoundManager.DequeueA();
                        if (this.memory.SoundManager.QueueSizeA < 16)
                        {
                            this.memory.FifoDma(1);
                            // TODO
                            if (this.memory.SoundManager.QueueSizeA < 16)
                            {
                            }
                        }
                    }
                    if (timer == ((soundCntH >> 14) & 1))
                    {
                        // FIFO B overflow
                        this.memory.SoundManager.DequeueB();
                        if (this.memory.SoundManager.QueueSizeB < 16)
                        {
                            this.memory.FifoDma(2);
                        }
                    }
                }

                // Overflow, attempt to fire IRQ
                if ((control & (1 << 6)) != 0)
                {
                    this.RequestIrq(3 + timer);
                }

                if (timer < 3)
                {
                    ushort control2 = Memory.ReadU16(this.memory.IORam, Memory.TM0CNT + (uint)((timer + 1) * 4));
                    if ((control2 & (1 << 2)) != 0)
                    {
                        // Count-up
                        this.UpdateTimer(timer + 1, (int)((timerCnt >> 16) << 10), true);
                    }
                }

                // Reset the original value
                uint count = Memory.ReadU16(this.memory.IORam, Memory.TM0D + (uint)(timer * 4));
                this.memory.TimerCnt[timer] = count << 10;
            }
        }

        public void UpdateTimers()
        {
            int cycles = this.timerCycles - this.cycles;

            for (int i = 0; i < 4; i++)
            {
                this.UpdateTimer(i, cycles, false);
            }

            this.timerCycles = this.cycles;
        }

        public void UpdateKeyState()
        {
            ushort KEYCNT = this.memory.ReadU16Debug(Memory.REG_BASE + Memory.KEYCNT);

            if ((KEYCNT & (1 << 14)) != 0)
            {
                if ((KEYCNT & (1 << 15)) != 0)
                {
                    KEYCNT &= 0x3FF;
                    if (((~this.keyState) & KEYCNT) == KEYCNT)
                        this.RequestIrq(12);
                }
                else
                {
                    KEYCNT &= 0x3FF;
                    if (((~this.keyState) & KEYCNT) != 0)
                        this.RequestIrq(12);
                }
            }

            this.memory.KeyState = this.keyState;
        }

        public void UpdateSound()
        {
            this.memory.SoundManager.Mix(this.soundCycles);
            this.soundCycles = 0;
        }

        public void Execute(int cycles)
        {
            this.cycles += cycles;
            this.timerCycles += cycles;
            this.soundCycles += cycles;
            this.breakpointHit = false;

            if (this.cpuHalted)
            {
                ushort ie = Memory.ReadU16(this.memory.IORam, Memory.IE);
                ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);

                if ((ie & iflag) != 0)
                {
                    this.cpuHalted = false;
                }
                else
                {
                    this.cycles = 0;
                    this.UpdateTimers();
                    this.UpdateSound();
                    return;
                }
            }

            while (this.cycles > 0)
            {
                if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
                {
                    this.thumbCore.Execute();
                }
                else
                {
                    this.armCore.Execute();
                }

                this.UpdateTimers();
                this.UpdateSound();

                if (this.breakpointHit)
                {
                    break;
                }
            }
        }
    }
}