/*
 * M6502.cs
 *
 * CPU emulator for the MOS Technology 6502 microprocessor.
 * 
 * Copyright © 2003-2005 Mike Murphy
 *
 */
using System;

namespace EMU7800.Core
{
    public sealed class M6502
    {
        delegate void OpcodeHandler();

        OpcodeHandler[] Opcodes;

        const ushort
            // non-maskable interrupt vector
            NMI_VEC = 0xfffa,
            // reset vector
            RST_VEC = 0xfffc,
            // interrupt request vector
            IRQ_VEC = 0xfffe;

        readonly MachineBase M;
        AddressSpace Mem { get { return M.Mem; } }

        public ulong Clock { get; set; }
        public int RunClocks { get; set; }
        public int RunClocksMultiple { get; private set; }

        public bool EmulatorPreemptRequest { get; set; }
        public bool Jammed { get; set; }
        public bool IRQInterruptRequest { get; set; }
        public bool NMIInterruptRequest { get; set; }

        // 16-bit register
        // program counter
        public ushort PC { get; set; }

        // 8-bit registers
        // accumulator
        public byte A { get; set; }
        // x index register
        public byte X { get; set; }
        // y index register
        public byte Y { get; set; }
        // stack pointer
        public byte S { get; set; }
        // processor status
        public byte P { get; set; }

        public void Reset()
        {
            Jammed = false;

            // clear the stack
            S = 0xff;

            fI = fZ = true;

            // reset the program counter
            PC = WORD(Mem[RST_VEC], Mem[RST_VEC + 1]);

            clk(6);

            Log("{0} (PC:${1:x4}) reset", this, PC);
        }

        public override String ToString()
        {
            return "M6502 CPU";
        }

        public void Execute()
        {
            EmulatorPreemptRequest = false;

            while (RunClocks > 0 && !EmulatorPreemptRequest && !Jammed)
            {
                if (NMIInterruptRequest)
                {
                    InterruptNMI();
                    NMIInterruptRequest = false;
                }
                else if (IRQInterruptRequest)
                {
                    InterruptIRQ();
                    IRQInterruptRequest = false;
                }
                else
                {
                    Opcodes[Mem[PC++]]();
                }
            }
        }

        private M6502()
        {
            InstallOpcodes();

            Clock = 0;
            RunClocks = 0;
            RunClocksMultiple = 1;

            // initialize processor status, bit 5 is always set
            P = 1 << 5;
        }

        public M6502(MachineBase m, int runClocksMultiple) : this()
        {
            if (m == null)
                throw new ArgumentNullException("m");
            if (runClocksMultiple <= 0)
                throw new ArgumentException("runClocksMultiple must be greater than zero.");

            M = m;
            RunClocksMultiple = runClocksMultiple;
        }

        static byte MSB(ushort u16)
        {
            return (byte)(u16 >> 8);
        }

        static byte LSB(ushort u16)
        {
            return (byte)u16;
        }

        static ushort WORD(byte lsb, byte msb)
        {
            return (ushort)(lsb | msb << 8);
        }

        // Processor Status Flag Bits
        //

        // Flag bit setters and getters
        void fset(byte flag, bool value)
        {
            P = (byte)(value ? P | flag : P & ~flag);
        }

        bool fget(byte flag)
        {
            return (P & flag) != 0;
        }

        // Carry: set if the add produced a carry, if the subtraction
        //      produced a borrow.  Also used in shift instructions.
        public bool fC
        {
            get { return fget(1 << 0); }
			private set { fset(1 << 0, value); }
        }
    
        // Zero: set if the result of the last operation was zero
        public bool fZ
        {
            get { return fget(1 << 1); }
			private set { fset(1 << 1, value); }
        }

        // Irq Disable: set if maskable interrupts are disabled
        public bool fI
        {
            get { return fget(1 << 2); }
			private set { fset(1 << 2, value); }
        }

        // Decimal Mode: set if decimal mode active
        public bool fD
        {
            get { return fget(1 << 3); }
			private set { fset(1 << 3, value); }
        }

        // Brk: set if an interrupt caused by a BRK instruction,
        //      reset if caused by an internal interrupt
        public bool fB
        {
            get { return fget(1 << 4); }
			private set { fset(1 << 4, value); }
        }

        // Overflow: set if the addition of two-like-signed numbers
        //      or the subtraction of two unlike-signed numbers
        //      produces a result greater than +127 or less than -128.
        public bool fV
        {
            get { return fget(1 << 6); }
			private set { fset(1 << 6, value); }
        }

        // Negative: set if bit 7 of the accumulator is set
        public bool fN
        {
            get { return fget(1 << 7); }
			private set { fset(1 << 7, value); }
        }

        void set_fNZ(byte u8)
        {
            fN = (u8 & 0x80) != 0;
            fZ = (u8 & 0xff) == 0;
        }

        byte pull()
        {
            S++;
            return Mem[(ushort)(0x0100 + S)];
        }

        void push(byte data)
        {
            Mem[(ushort)(0x0100 + S)] = data;
            S--;
        }

        void clk(int ticks)
        {
            Clock += (ulong)ticks;
            RunClocks -= (ticks*RunClocksMultiple);
        }

        void InterruptNMI()
        {
            push(MSB(PC));
            push(LSB(PC));
            fB = false;
            push(P);
            fI = true;
            PC = WORD(Mem[NMI_VEC], Mem[NMI_VEC + 1]);
            clk(7);
        }

        void InterruptIRQ()
        {
            if (IRQInterruptRequest && !fI)
            {
                push(MSB(PC));
                push(LSB(PC));
                fB = false;
                push(P);
                fI = true;
                PC = WORD(Mem[IRQ_VEC], Mem[IRQ_VEC + 1]);
            }
            clk(7);
        }

        void br(bool cond, ushort ea)
        {
            if (cond)
            {
                clk( (MSB(PC) == MSB(ea)) ? 1 : 2 );
                PC = ea;
            }
        }


        // Relative: Bxx $aa  (branch instructions only)
        ushort aREL()
        {
            var bo = (sbyte)Mem[PC];
            PC++;
            return (ushort)(PC + bo);
        }

        // Zero Page: $aa
        ushort aZPG()
        {
            return WORD(Mem[PC++], 0x00);
        }

        // Zero Page Indexed,X: $aa,X
        ushort aZPX()
        {
            return WORD((byte)(Mem[PC++] + X), 0x00);
        }

        // Zero Page Indexed,Y: $aa,Y
        ushort aZPY()
        {
            return WORD((byte)(Mem[PC++] + Y), 0x00);
        }

        // Absolute: $aaaa
        ushort aABS()
        {
            var lsb = Mem[PC++];
            var msb = Mem[PC++];
            return WORD(lsb, msb);
        }

        // Absolute Indexed,X: $aaaa,X
        ushort aABX(int eclk)
        {
            var ea = aABS();
            if (LSB(ea) + X > 0xff)
            {
                clk(eclk);
            }
            return (ushort)(ea + X);
        }

        // Absolute Indexed,Y: $aaaa,Y
        ushort aABY(int eclk)
        {
            var ea = aABS();
            if (LSB(ea) + Y > 0xff)
            {
                clk(eclk);
            }
            return (ushort)(ea + Y);
        }

        // Indexed Indirect: ($aa,X)
        ushort aIDX()
        {
            var zpa = (byte)(Mem[PC++] + X);
            var lsb = Mem[zpa++];
            var msb = Mem[zpa];
            return WORD(lsb, msb);
        }

        // Indirect Indexed: ($aa),Y
        ushort aIDY(int eclk)
        {
            var zpa = Mem[PC++];
            var lsb = Mem[zpa++];
            var msb = Mem[zpa];
            if (lsb + Y > 0xff) 
            {
                clk(eclk);
            }
            return (ushort)(WORD(lsb, msb) + Y);
        }

        // Indirect Absolute: ($aaaa)    (only used by JMP)
        ushort aIND()
        {
            var ea = aABS();
            var lsb = Mem[ea];
            ea = WORD((byte)(LSB(ea) + 1), MSB(ea));   // NMOS 6502/7 quirk: does not fetch across page boundaries
            var msb = Mem[ea];
            return WORD(lsb, msb);
        }

        // aACC = Accumulator
        // aIMM = Immediate
        // aIMP = Implied

        // ADC: Add with carry
        void iADC(byte mem)
        {
            var c = fC ? 1 : 0;
            var sum = A + mem + c;
            fV = (~(A ^ mem) & (A ^ (sum & 0xff)) & 0x80) != 0;
            if (fD)
            {
                // NMOS 6502/7 quirk: The N, V, and Z flags reflect the binary result, not the BCD result
                var lo = (A & 0xf) + (mem & 0xf) + c;
                var hi = (A >> 4) + (mem >> 4);
                if (lo > 9)
                {
                    lo += 6;
                    hi++;
                }
                if (hi > 9)
                {
                    hi += 6;
                }
                A = (byte)((lo & 0xf) | (hi << 4));
                fC = (hi & 0x10) != 0;
            }
            else
            {
                A = (byte)sum;
                fC = (sum & 0x100) != 0;
            }
            set_fNZ((byte)sum);
        }

        // AND: Logical and
        void iAND(byte mem)
        {
            A &= mem;
            set_fNZ(A);
        }

        // ASL: Arithmetic shift left: C <- [7][6][5][4][3][2][1][0] <- 0
        byte iASL(byte mem)
        {
            fC = (mem & 0x80) != 0;
            mem <<= 1;
            set_fNZ(mem);
            return mem;
        }

        // BIT: Bit test
        void iBIT(byte mem)
        {
            fN = (mem & 0x80) != 0;
            fV = (mem & 0x40) != 0;
            fZ = (mem & A) == 0;
        }

        // BRK Force Break  (cause software interrupt)
        void iBRK()
        {
            PC++;
            fB = true;
            push(MSB(PC));
            push(LSB(PC));
            push(P);
            fI = true;
            var lsb = Mem[IRQ_VEC];
            var msb = Mem[IRQ_VEC+1];
            PC = WORD(lsb, msb);
        }

        // CLC: Clear carry flag
        void iCLC()
        {
            fC = false;
        }

        // CLD: Clear decimal mode
        void iCLD()
        {
            fD = false;
        }

        // CLI: Clear interrupt disable */
        void iCLI()
        {
            fI = false;
        }

        // CLV: Clear overflow flag
        void iCLV()
        {
            fV = false;
        }

        // CMP: Compare accumulator
        void iCMP(byte mem)
        {
            fC = A >= mem;
            set_fNZ((byte)(A - mem));
        }

        // CPX: Compare index X
        void iCPX(byte mem)
        {
            fC = X >= mem;
            set_fNZ((byte)(X - mem));
        }

        // CPY: Compare index Y
        void iCPY(byte mem)
        {
            fC = Y >= mem;
            set_fNZ((byte)(Y - mem));
        }

        // DEC: Decrement memory
        byte iDEC(byte mem)
        {
            mem--;
            set_fNZ(mem);
            return mem;
        }

        // DEX: Decrement index x
        void iDEX()
        {
            X--;
            set_fNZ(X);
        }

        // DEY: Decrement index y
        void iDEY()
        {
            Y--;
            set_fNZ(Y);
        }

        // EOR: Logical exclusive or
        void iEOR(byte mem)
        {
            A ^= mem;
            set_fNZ(A);
        }

        // INC: Increment memory
        byte iINC(byte mem)
        {
            mem++;
            set_fNZ(mem);
            return mem;
        }

        // INX: Increment index x
        void iINX()
        {
            X++;
            set_fNZ(X);
        }

        // INY: Increment index y
        void iINY()
        {
            Y++;
            set_fNZ(Y);
        }

        // JMP Jump to address
        void iJMP(ushort ea)
        {
            PC = ea;
        }

        // JSR Jump to subroutine
        void iJSR(ushort ea)
        {
            PC--;                   // NMOS 6502/7 quirk: iRTS compensates
            push(MSB(PC));
            push(LSB(PC));
            PC = ea;
        }

        // LDA: Load accumulator
        void iLDA(byte mem)
        {
            A = mem;
            set_fNZ(A);
        }

        // LDX: Load index X
        void iLDX(byte mem)
        {
            X = mem;
            set_fNZ(X);
        }

        // LDY: Load index Y
        void iLDY(byte mem)
        {
            Y = mem;
            set_fNZ(Y);
        }

        // LSR: Logic shift right: 0 -> [7][6][5][4][3][2][1][0] -> C
        byte iLSR(byte mem)
        {
            fC = (mem & 0x01) != 0;
            mem >>= 1;
            set_fNZ(mem);
            return mem;
        }

        // NOP: No operation
        void iNOP()
        {
            if (M.NOPRegisterDumping)
            {
                Log("NOP: {0}", M6502DASM.GetRegisters(this));
            }
        }

        // ORA: Logical inclusive or
        void iORA(byte mem)
        {
            A |= mem;
            set_fNZ(A);
        }

        // PHA: Push accumulator
        void iPHA()
        {
            push(A);
        }

        // PHP: Push processor status (flags)
        void iPHP()
        {
            push(P);
        }

        // PLA: Pull accumuator
        void iPLA()
        {
            A = pull();
            set_fNZ(A);
        }

        // PLP: Pull processor status (flags)
        void iPLP()
        {
            P = pull();
            fB = true;
        }

        // ROL: Rotate left: new C <- [7][6][5][4][3][2][1][0] <- C
        byte iROL(byte mem)
        {
            var d0 = (byte)(fC ? 0x01 : 0x00);

            fC = (mem & 0x80) != 0;
            mem <<= 1;
            mem |= d0;
            set_fNZ(mem);
            return mem;
        }

        // ROR: Rotate right: C -> [7][6][5][4][3][2][1][0] -> new C
        byte iROR(byte mem)
        {
            var d7 = (byte)(fC ? 0x80 : 0x00);

            fC = (mem & 0x01) != 0;
            mem >>= 1;
            mem |= d7;
            set_fNZ(mem);
            return mem;
        }

        // RTI: Return from interrupt
        void iRTI()
        {
            P = pull();
            var lsb = pull();
            var msb = pull();
            PC = WORD(lsb, msb);
            fB = true;
        }

        // RTS: Return from subroutine
        void iRTS()
        {
            var lsb = pull();
            var msb = pull();
            PC = WORD(lsb, msb);
            PC++;                   // NMOS 6502/7 quirk: iJSR compensates
        }

        // SBC: Subtract with carry (borrow)
        void iSBC(byte mem)
        {
            var c = fC ? 0 : 1;
            var sum = A - mem - c;
            fV = ((A ^ mem) & (A ^ (sum & 0xff)) & 0x80) != 0;
            if (fD)
            {
                // NMOS 6502/7 quirk: The N, V, and Z flags reflect the binary result, not the BCD result
                var lo = (A & 0xf) - (mem & 0xf) - c;
                var hi = (A >> 4) - (mem >> 4);
                if ((lo & 0x10) != 0)
                {
                    lo -= 6;
                    hi--;
                }
                if ((hi & 0x10) != 0)
                {
                    hi -= 6;
                }
                A = (byte)((lo & 0xf) | (hi << 4));
            }
            else
            {
                A = (byte)sum;
            }
            fC = (sum & 0x100) == 0;
            set_fNZ((byte)sum);
        }

        // SEC: Set carry flag
        void iSEC()
        {
            fC = true;
        }

        // SED: Set decimal mode
        void iSED()
        {
            fD = true;
        }

        // SEI: Set interrupt disable
        void iSEI()
        {
            fI = true;
        }

        // STA: Store accumulator
        byte iSTA()
        {
            return A;
        }

        // STX: Store index X
        byte iSTX()
        {
            return X;
        }

        // STY: Store index Y
        byte iSTY()
        {
            return Y;
        }

        // TAX: Transfer accumlator to index X
        void iTAX()
        {
            X = A;
            set_fNZ(X);
        }

        // TAY: Transfer accumlator to index Y
        void iTAY()
        {
            Y = A;
            set_fNZ(Y);
        }

        // TSX: Transfer stack to index X
        void iTSX()
        {
            X = S;
            set_fNZ(X);
        }

        // TXA: Transfer index X to accumlator
        void iTXA()
        {
            A = X;
            set_fNZ(A);
        }

        // TXS: Transfer index X to stack
        void iTXS()
        {
            S = X;
            // No flags set..!  Weird, huh?
        }

        // TYA: Transfer index Y to accumulator
        void iTYA()
        {
            A = Y;
            set_fNZ(A);
        }

        // Illegal opcodes

        // KIL: Jam the processor
        void iKIL()
        {
            Jammed = true;
            Log("{0}: Processor jammed!", this);
        }

        // LAX: Load accumulator and index x
        void iLAX(byte mem)
        {
            A = X = mem;
            set_fNZ(A);
        }

        // ISB: Increment and subtract with carry
        void iISB(byte mem)
        {
            mem++;
            iSBC(mem);
        }

        // RLA: Rotate left and logical and accumulator
        // new C <- [7][6][5][4][3][2][1][0] <- C
        void iRLA(byte mem)
        {
            var d0 = (byte)(fC ? 0x01 : 0x00);

            fC = (mem & 0x80) != 0;
            mem <<= 1;
            mem |= d0;

            A &= mem;
            set_fNZ(A);
        }

        // SAX: logical and accumulator with index X and store
        byte iSAX()
        {
            return (byte)(A & X);
        }

        void InstallOpcodes()
        {
            Opcodes = new OpcodeHandler[0x100];
            ushort EA;

            Opcodes[0x65] = delegate { EA = aZPG();  clk(3); iADC(Mem[EA]); };
            Opcodes[0x75] = delegate { EA = aZPX();  clk(4); iADC(Mem[EA]); };
            Opcodes[0x61] = delegate { EA = aIDX();  clk(6); iADC(Mem[EA]); };
            Opcodes[0x71] = delegate { EA = aIDY(1); clk(5); iADC(Mem[EA]); };
            Opcodes[0x79] = delegate { EA = aABY(1); clk(4); iADC(Mem[EA]); };
            Opcodes[0x6d] = delegate { EA = aABS();  clk(4); iADC(Mem[EA]); };
            Opcodes[0x7d] = delegate { EA = aABX(1); clk(4); iADC(Mem[EA]); };
            Opcodes[0x69] = delegate { /*aIMM*/      clk(2); iADC(Mem[PC++]); };

            Opcodes[0x25] = delegate { EA = aZPG();  clk(3); iAND(Mem[EA]); }; // may be 2 clk
            Opcodes[0x35] = delegate { EA = aZPX();  clk(4); iAND(Mem[EA]); }; // may be 3 clk
            Opcodes[0x21] = delegate { EA = aIDX();  clk(6); iAND(Mem[EA]); };
            Opcodes[0x31] = delegate { EA = aIDY(1); clk(5); iAND(Mem[EA]); };
            Opcodes[0x2d] = delegate { EA = aABS();  clk(4); iAND(Mem[EA]); };
            Opcodes[0x39] = delegate { EA = aABY(1); clk(4); iAND(Mem[EA]); };
            Opcodes[0x3d] = delegate { EA = aABX(1); clk(4); iAND(Mem[EA]); };
            Opcodes[0x29] = delegate {    /*aIMM*/   clk(2); iAND(Mem[PC++]); };

            Opcodes[0x06] = delegate { EA = aZPG();  clk(5); Mem[EA] = iASL(Mem[EA]); };
            Opcodes[0x16] = delegate { EA = aZPX();  clk(6); Mem[EA] = iASL(Mem[EA]); };
            Opcodes[0x0e] = delegate { EA = aABS();  clk(6); Mem[EA] = iASL(Mem[EA]); };
            Opcodes[0x1e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iASL(Mem[EA]); };
            Opcodes[0x0a] = delegate {    /*aACC*/   clk(2);       A = iASL(A); };

            Opcodes[0x24] = delegate { EA = aZPG();  clk(3); iBIT(Mem[EA]); };
            Opcodes[0x2c] = delegate { EA = aABS();  clk(4); iBIT(Mem[EA]); };

            Opcodes[0x10] = delegate { EA = aREL();  clk(2); br(!fN, EA); /* BPL */ };
            Opcodes[0x30] = delegate { EA = aREL();  clk(2); br( fN, EA); /* BMI */ };
            Opcodes[0x50] = delegate { EA = aREL();  clk(2); br(!fV, EA); /* BVC */ };
            Opcodes[0x70] = delegate { EA = aREL();  clk(2); br( fV, EA); /* BVS */ };
            Opcodes[0x90] = delegate { EA = aREL();  clk(2); br(!fC, EA); /* BCC */ };
            Opcodes[0xb0] = delegate { EA = aREL();  clk(2); br( fC, EA); /* BCS */ };
            Opcodes[0xd0] = delegate { EA = aREL();  clk(2); br(!fZ, EA); /* BNE */ };
            Opcodes[0xf0] = delegate { EA = aREL();  clk(2); br( fZ, EA); /* BEQ */ };

            Opcodes[0x00] = delegate {    /*aIMP*/   clk(7); iBRK(); };

            Opcodes[0x18] = delegate {    /*aIMP*/   clk(2); iCLC(); };

            Opcodes[0xd8] = delegate {    /*aIMP*/   clk(2); iCLD(); };

            Opcodes[0x58] = delegate {    /*aIMP*/   clk(2); iCLI(); };

            Opcodes[0xb8] = delegate {    /*aIMP*/   clk(2); iCLV(); };

            Opcodes[0xc5] = delegate { EA = aZPG();  clk(3); iCMP(Mem[EA]); };
            Opcodes[0xd5] = delegate { EA = aZPX();  clk(4); iCMP(Mem[EA]); };
            Opcodes[0xc1] = delegate { EA = aIDX();  clk(6); iCMP(Mem[EA]); };
            Opcodes[0xd1] = delegate { EA = aIDY(1); clk(5); iCMP(Mem[EA]); };
            Opcodes[0xcd] = delegate { EA = aABS();  clk(4); iCMP(Mem[EA]); };
            Opcodes[0xdd] = delegate { EA = aABX(1); clk(4); iCMP(Mem[EA]); };
            Opcodes[0xd9] = delegate { EA = aABY(1); clk(4); iCMP(Mem[EA]); };
            Opcodes[0xc9] = delegate { /*aIMM*/      clk(2); iCMP(Mem[PC++]); };

            Opcodes[0xe4] = delegate { EA = aZPG();  clk(3); iCPX(Mem[EA]); };
            Opcodes[0xec] = delegate { EA = aABS();  clk(4); iCPX(Mem[EA]); };
            Opcodes[0xe0] = delegate { /*aIMM*/      clk(2); iCPX(Mem[PC++]); };

            Opcodes[0xc4] = delegate { EA = aZPG();  clk(3); iCPY(Mem[EA]); };
            Opcodes[0xcc] = delegate { EA = aABS();  clk(4); iCPY(Mem[EA]); };
            Opcodes[0xc0] = delegate { /*aIMM*/      clk(2); iCPY(Mem[PC++]); };

            Opcodes[0xc6] = delegate { EA = aZPG();  clk(5); Mem[EA] = iDEC(Mem[EA]); };
            Opcodes[0xd6] = delegate { EA = aZPX();  clk(6); Mem[EA] = iDEC(Mem[EA]); };
            Opcodes[0xce] = delegate { EA = aABS();  clk(6); Mem[EA] = iDEC(Mem[EA]); };
            Opcodes[0xde] = delegate { EA = aABX(0); clk(7); Mem[EA] = iDEC(Mem[EA]); };

            Opcodes[0xca] = delegate {    /*aIMP*/   clk(2); iDEX(); };

            Opcodes[0x88] = delegate {    /*aIMP*/   clk(2); iDEY(); };

            Opcodes[0x45] = delegate { EA = aZPG();  clk(3); iEOR(Mem[EA]); };
            Opcodes[0x55] = delegate { EA = aZPX();  clk(4); iEOR(Mem[EA]); };
            Opcodes[0x41] = delegate { EA = aIDX();  clk(6); iEOR(Mem[EA]); };
            Opcodes[0x51] = delegate { EA = aIDY(1); clk(5); iEOR(Mem[EA]); };
            Opcodes[0x4d] = delegate { EA = aABS();  clk(4); iEOR(Mem[EA]); };
            Opcodes[0x5d] = delegate { EA = aABX(1); clk(4); iEOR(Mem[EA]); };
            Opcodes[0x59] = delegate { EA = aABY(1); clk(4); iEOR(Mem[EA]); };
            Opcodes[0x49] = delegate {    /*aIMM*/   clk(2); iEOR(Mem[PC++]); };

            Opcodes[0xe6] = delegate { EA = aZPG();  clk(5); Mem[EA] = iINC(Mem[EA]); };
            Opcodes[0xf6] = delegate { EA = aZPX();  clk(6); Mem[EA] = iINC(Mem[EA]); };
            Opcodes[0xee] = delegate { EA = aABS();  clk(6); Mem[EA] = iINC(Mem[EA]); };
            Opcodes[0xfe] = delegate { EA = aABX(0); clk(7); Mem[EA] = iINC(Mem[EA]); };

            Opcodes[0xe8] = delegate {    /*aIMP*/   clk(2); iINX(); };

            Opcodes[0xc8] = delegate {    /*aIMP*/   clk(2); iINY(); };

            Opcodes[0xa5] = delegate { EA = aZPG();  clk(3); iLDA(Mem[EA]); };
            Opcodes[0xb5] = delegate { EA = aZPX();  clk(4); iLDA(Mem[EA]); };
            Opcodes[0xa1] = delegate { EA = aIDX();  clk(6); iLDA(Mem[EA]); };
            Opcodes[0xb1] = delegate { EA = aIDY(1); clk(5); iLDA(Mem[EA]); };
            Opcodes[0xad] = delegate { EA = aABS();  clk(4); iLDA(Mem[EA]); };
            Opcodes[0xbd] = delegate { EA = aABX(1); clk(4); iLDA(Mem[EA]); };
            Opcodes[0xb9] = delegate { EA = aABY(1); clk(4); iLDA(Mem[EA]); };
            Opcodes[0xa9] = delegate {    /*aIMM*/   clk(2); iLDA(Mem[PC++]); };

            Opcodes[0xa6] = delegate { EA = aZPG();  clk(3); iLDX(Mem[EA]); };
            Opcodes[0xb6] = delegate { EA = aZPY();  clk(4); iLDX(Mem[EA]); };
            Opcodes[0xae] = delegate { EA = aABS();  clk(4); iLDX(Mem[EA]); };
            Opcodes[0xbe] = delegate { EA = aABY(1); clk(4); iLDX(Mem[EA]); };
            Opcodes[0xa2] = delegate {    /*aIMM*/   clk(2); iLDX(Mem[PC++]); };

            Opcodes[0xa4] = delegate { EA = aZPG();  clk(3); iLDY(Mem[EA]); };
            Opcodes[0xb4] = delegate { EA = aZPX();  clk(4); iLDY(Mem[EA]); };
            Opcodes[0xac] = delegate { EA = aABS();  clk(4); iLDY(Mem[EA]); };
            Opcodes[0xbc] = delegate { EA = aABX(1); clk(4); iLDY(Mem[EA]); };
            Opcodes[0xa0] = delegate {    /*aIMM*/   clk(2); iLDY(Mem[PC++]); };

            Opcodes[0x46] = delegate { EA = aZPG();  clk(5); Mem[EA] = iLSR(Mem[EA]); };
            Opcodes[0x56] = delegate { EA = aZPX();  clk(6); Mem[EA] = iLSR(Mem[EA]); };
            Opcodes[0x4e] = delegate { EA = aABS();  clk(6); Mem[EA] = iLSR(Mem[EA]); };
            Opcodes[0x5e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iLSR(Mem[EA]); };
            Opcodes[0x4a] = delegate {    /*aACC*/   clk(2);       A = iLSR(A); };

            Opcodes[0x4c] = delegate { EA = aABS();  clk(3); iJMP(EA); };
            Opcodes[0x6c] = delegate { EA = aIND();  clk(5); iJMP(EA); };

            Opcodes[0x20] = delegate { EA = aABS();  clk(6); iJSR(EA); };

            Opcodes[0xea] = delegate {    /*aIMP*/   clk(2); iNOP(); };

            Opcodes[0x05] = delegate { EA = aZPG();  clk(3); iORA(Mem[EA]); }; // may be 2 clk
            Opcodes[0x15] = delegate { EA = aZPX();  clk(4); iORA(Mem[EA]); }; // may be 3 clk
            Opcodes[0x01] = delegate { EA = aIDX();  clk(6); iORA(Mem[EA]); };
            Opcodes[0x11] = delegate { EA = aIDY(1); clk(5); iORA(Mem[EA]); };
            Opcodes[0x0d] = delegate { EA = aABS();  clk(4); iORA(Mem[EA]); };
            Opcodes[0x1d] = delegate { EA = aABX(1); clk(4); iORA(Mem[EA]); };
            Opcodes[0x19] = delegate { EA = aABY(1); clk(4); iORA(Mem[EA]); };
            Opcodes[0x09] = delegate {    /*aIMM*/   clk(2); iORA(Mem[PC++]); };

            Opcodes[0x48] = delegate {    /*aIMP*/   clk(3); iPHA(); };

            Opcodes[0x68] = delegate {    /*aIMP*/   clk(4); iPLA(); };

            Opcodes[0x08] = delegate {    /*aIMP*/   clk(3); iPHP(); };

            Opcodes[0x28] = delegate {    /*aIMP*/   clk(4); iPLP(); };

            Opcodes[0x26] = delegate { EA = aZPG();  clk(5); Mem[EA] = iROL(Mem[EA]); };
            Opcodes[0x36] = delegate { EA = aZPX();  clk(6); Mem[EA] = iROL(Mem[EA]); };
            Opcodes[0x2e] = delegate { EA = aABS();  clk(6); Mem[EA] = iROL(Mem[EA]); };
            Opcodes[0x3e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iROL(Mem[EA]); };
            Opcodes[0x2a] = delegate {    /*aACC*/   clk(2);       A = iROL(A);       };

            Opcodes[0x66] = delegate { EA = aZPG();  clk(5); Mem[EA] = iROR(Mem[EA]); };
            Opcodes[0x76] = delegate { EA = aZPX();  clk(6); Mem[EA] = iROR(Mem[EA]); };
            Opcodes[0x6e] = delegate { EA = aABS();  clk(6); Mem[EA] = iROR(Mem[EA]); };
            Opcodes[0x7e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iROR(Mem[EA]); };
            Opcodes[0x6a] = delegate {    /*aACC*/   clk(2);       A = iROR(A); };

            Opcodes[0x40] = delegate {    /*aIMP*/   clk(6); iRTI(); };

            Opcodes[0x60] = delegate {    /*aIMP*/   clk(6); iRTS(); };

            Opcodes[0xe5] = delegate { EA = aZPG();  clk(3); iSBC(Mem[EA]); };
            Opcodes[0xf5] = delegate { EA = aZPX();  clk(4); iSBC(Mem[EA]); };
            Opcodes[0xe1] = delegate { EA = aIDX();  clk(6); iSBC(Mem[EA]); };
            Opcodes[0xf1] = delegate { EA = aIDY(1); clk(5); iSBC(Mem[EA]); };
            Opcodes[0xed] = delegate { EA = aABS();  clk(4); iSBC(Mem[EA]); };
            Opcodes[0xfd] = delegate { EA = aABX(1); clk(4); iSBC(Mem[EA]); };
            Opcodes[0xf9] = delegate { EA = aABY(1); clk(4); iSBC(Mem[EA]); };
            Opcodes[0xe9] = delegate {    /*aIMM*/   clk(2); iSBC(Mem[PC++]); };

            Opcodes[0x38] = delegate {    /*aIMP*/   clk(2); iSEC(); };

            Opcodes[0xf8] = delegate {    /*aIMP*/   clk(2); iSED(); };

            Opcodes[0x78] = delegate {    /*aIMP*/   clk(2); iSEI(); };

            Opcodes[0x85] = delegate { EA = aZPG();  clk(3); Mem[EA] = iSTA(); };
            Opcodes[0x95] = delegate { EA = aZPX();  clk(4); Mem[EA] = iSTA(); };
            Opcodes[0x81] = delegate { EA = aIDX();  clk(6); Mem[EA] = iSTA(); };
            Opcodes[0x91] = delegate { EA = aIDY(0); clk(6); Mem[EA] = iSTA(); };
            Opcodes[0x8d] = delegate { EA = aABS();  clk(4); Mem[EA] = iSTA(); };
            Opcodes[0x99] = delegate { EA = aABY(0); clk(5); Mem[EA] = iSTA(); };
            Opcodes[0x9d] = delegate { EA = aABX(0); clk(5); Mem[EA] = iSTA(); };

            Opcodes[0x86] = delegate { EA = aZPG();  clk(3); Mem[EA] = iSTX(); };
            Opcodes[0x96] = delegate { EA = aZPY();  clk(4); Mem[EA] = iSTX(); };
            Opcodes[0x8e] = delegate { EA = aABS();  clk(4); Mem[EA] = iSTX(); };

            Opcodes[0x84] = delegate { EA = aZPG();  clk(3); Mem[EA] = iSTY(); };
            Opcodes[0x94] = delegate { EA = aZPX();  clk(4); Mem[EA] = iSTY(); };
            Opcodes[0x8c] = delegate { EA = aABS();  clk(4); Mem[EA] = iSTY(); };

            Opcodes[0xaa] = delegate {    /*aIMP*/   clk(2); iTAX(); };

            Opcodes[0xa8] = delegate {    /*aIMP*/   clk(2); iTAY(); };

            Opcodes[0xba] = delegate {    /*aIMP*/   clk(2); iTSX(); };

            Opcodes[0x8a] = delegate {    /*aIMP*/   clk(2); iTXA(); };

            Opcodes[0x9a] = delegate {    /*aIMP*/   clk(2); iTXS(); };

            Opcodes[0x98] = delegate {    /*aIMP*/   clk(2); iTYA(); };

            // Illegal opcodes
            foreach (int opCode in new ushort[] { 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, 0x92, 0xb2, 0xd2, 0xf2 })
            {
                Opcodes[opCode] = delegate { clk(2); iKIL(); };
            }
            Opcodes[0x3f] = delegate { EA = aABX(0); clk(4); iRLA(Mem[EA]); };
            Opcodes[0xa7] = delegate { EA = aZPX();  clk(3); iLAX(Mem[EA]); };
            Opcodes[0xb3] = delegate { EA = aIDY(0); clk(6); iLAX(Mem[EA]); };
            Opcodes[0xef] = delegate { EA = aABS();  clk(6); iISB(Mem[EA]); };
            Opcodes[0x0c] = delegate { EA = aABS();  clk(2); iNOP(); };
            foreach (int opCode in new ushort[] { 0x1c, 0x3c, 0x5c, 0x7c, 0x9c, 0xdc, 0xfc })
            {
                Opcodes[opCode] = delegate { EA = aABX(0); clk(2); iNOP(); };
            }
            Opcodes[0x83] = delegate { EA = aIDX();  clk(6); Mem[EA] = iSAX(); };
            Opcodes[0x87] = delegate { EA = aZPG();  clk(3); Mem[EA] = iSAX(); };
            Opcodes[0x8f] = delegate { EA = aABS();  clk(4); Mem[EA] = iSAX(); };
            Opcodes[0x97] = delegate { EA = aZPY();  clk(4); Mem[EA] = iSAX(); };
            Opcodes[0xa3] = delegate { EA = aIDX();  clk(6); iLAX(Mem[EA]); };
            Opcodes[0xb7] = delegate { EA = aZPY();  clk(4); iLAX(Mem[EA]); };
            Opcodes[0xaf] = delegate { EA = aABS();  clk(5); iLAX(Mem[EA]); };
            Opcodes[0xbf] = delegate { EA = aABY(0); clk(6); iLAX(Mem[EA]); };
            Opcodes[0xff] = delegate { EA = aABX(0); clk(7); iISB(Mem[EA]); };

            OpcodeHandler opNULL = () => Log("{0}:**UNKNOWN OPCODE: ${1:x2} at ${2:x4}\n", this, Mem[(ushort)(PC - 1)], PC - 1);

            for (var i=0; i < Opcodes.Length; i++)
            {
                if (Opcodes[i] == null)
                {
                    Opcodes[i] = opNULL;
                }
            }
        }

        #region Serialization Members

        public M6502(DeserializationContext input, MachineBase m, int runClocksMultiple) : this(m, runClocksMultiple)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            input.CheckVersion(1);
            Clock = input.ReadUInt64();
            RunClocks = input.ReadInt32();
            RunClocksMultiple = input.ReadInt32();
            EmulatorPreemptRequest = input.ReadBoolean();
            Jammed = input.ReadBoolean();
            IRQInterruptRequest = input.ReadBoolean();
            NMIInterruptRequest = input.ReadBoolean();
            PC = input.ReadUInt16();
            A = input.ReadByte();
            X = input.ReadByte();
            Y = input.ReadByte();
            S = input.ReadByte();
            P = input.ReadByte();
        }

        public void GetObjectData(SerializationContext output)
        {
            if (output == null)
                throw new ArgumentNullException("output");

            output.WriteVersion(1);
            output.Write(Clock);
            output.Write(RunClocks);
            output.Write(RunClocksMultiple);
            output.Write(EmulatorPreemptRequest);
            output.Write(Jammed);
            output.Write(IRQInterruptRequest);
            output.Write(NMIInterruptRequest);
            output.Write(PC);
            output.Write(A);
            output.Write(X);
            output.Write(Y);
            output.Write(S);
            output.Write(P);
        }

        #endregion

        #region Helpers

        void Log(string format, params object[] args)
        {
            if (M == null || M.Logger == null)
                return;
            M.Logger.WriteLine(format, args);
        }

        #endregion
    }
}