// This file is part of the Noddybox.Emulation C# suite.
//
// Noddybox.Emulation is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Noddybox.Emulation is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Noddybox.Emulation.  If not, see .
//
// Copyright (c) 2012 Ian Cowburn
//
using System;
namespace Noddybox.Emulation.EightBit.Z80
{
    public partial class Z80Cpu
    {
        #region Fetch and helper operations
        /// 
        /// Fetch the next word from the PC.
        /// 
        /// The word.
        ushort FetchWord()
        {
            Register16 r = new Register16(0);
            r.low = memory.Read(PC++);
            r.high = memory.Read(PC++);
            return r.reg;
        }
        /// 
        /// Swap two 16-bit registers.
        /// 
        /// The first register.
        /// The second register.
        void Swap(ref Register16 a, ref Register16 b)
        {
            Register16 t = a;
            a = b;
            b = t;
        }
        /// 
        /// Fetch the offset if the current opcode is shifted.
        /// 
        /// The offset, or zero if the current opcode is not shifted.
        sbyte Offset()
        {
            if (shift == 0xdd || shift == 0xfd)
            {
                byte b = memory.Read(PC++);
                return (sbyte)b;
            }
            else
            {
                return 0;
            }
        }
        
        #endregion
        #region Status/special register helpers
        
        /// 
        /// Set a flag in the status register.
        /// 
        /// The flag.
        private void SetFlag(Z80Flags flag)
        {
            F |= flag;
        }
        /// 
        /// Clear a flag in the status register.
        /// 
        /// The flag.
        private void ClearFlag(Z80Flags flag)
        {
            F &= ~flag;
        }
        /// 
        /// Add a value to the R (refresh) register.
        /// 
        /// The value.
        private void AddR(byte v)
        {
            R = (byte)((R & 0x80) | (R + v) & 0x7f);
        }
        #endregion
        #region Stack commands
        /// 
        /// Push a value on the stack.
        /// 
        /// The value.
        private void PUSH(ushort val)
        {
            memory.Write(--SP, (byte)(val & 0xff));
            memory.Write(--SP, (byte)(Binary.ShiftRight(val, 8) & 0xff));
        }
        /// 
        /// Pop a value from the stack.
        /// 
        /// 
        private ushort POP()
        {
            SP = (ushort)((SP + 2) & 0xffff);
            return (ushort)(memory.Read((ushort)(SP-2)) |
                             (memory.Read((ushort)(SP - 1)) >> 8));
        }
        #endregion
        #region ALU arithmetic and comparison
        /// 
        /// Add an 8-bit value to the accumulator without carry.
        /// 
        /// The vakue.
        private void ADD8(byte b)
        {
            int w = A + b;
            F = SZtable[w] | H35table[w & 0xff];
            if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry)
            {
                F |= Z80Flags.HalfCarry;
            }
            if (((b ^ A) & (b ^ w) & 0x80) > 0)
            {
                F |= Z80Flags.PV;
            }
            A = (byte)(w & 0xff);
        }
        /// 
        /// Add an 8-bit value to the accumulator with carry.
        /// 
        /// The vakue.
        private void ADC8(byte b)
        {
            int w = A + b + (int)(F & Z80Flags.Carry);
            F = SZtable[w] | H35table[w & 0xff];
            if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry)
            {
                F |= Z80Flags.HalfCarry;
            }
            if (((b ^ A) & (b ^ w) & 0x80) > 0)
            {
                F |= Z80Flags.PV;
            }
            A = (byte)(w & 0xff);
        }
        /// 
        /// Subtract an 8-bit value from the accumulator without carry.
        /// 
        /// The vakue.
        private void SUB8(byte b)
        {
            int w = A - b;
            if (w < 0)
            {
                w += 0x200;
            }
            F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg;
            if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry)
            {
                F |= Z80Flags.HalfCarry;
            }
            if (((b ^ A) & (b ^ w) & 0x80) > 0)
            {
                F |= Z80Flags.PV;
            }
            A = (byte)(w & 0xff);
        }
        /// 
        /// Compare an 8-bit value with the accumulator.
        /// 
        /// The vakue.
        private void CP(byte b)
        {
            int w = A - b;
            if (w < 0)
            {
                w += 0x200;
            }
            F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg;
            if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry)
            {
                F |= Z80Flags.HalfCarry;
            }
            if (((b ^ A) & (b ^ w) & 0x80) > 0)
            {
                F |= Z80Flags.PV;
            }
        }
        /// 
        /// Subtract an 8-bit value from the accumulator with carry.
        /// 
        /// The vakue.
        private void SBC8(byte b)
        {
            int w = A - b - (int)(F & Z80Flags.Carry);
            if (w < 0)
            {
                w += 0x200;
            }
            F = SZtable[w] | H35table[w & 0xff] | Z80Flags.Neg;
            if (((A ^ w ^ b) & (int)Z80Flags.HalfCarry) == (int)Z80Flags.HalfCarry)
            {
                F |= Z80Flags.HalfCarry;
            }
            if (((b ^ A) & (b ^ w) & 0x80) > 0)
            {
                F |= Z80Flags.PV;
            }
            A = (byte)(w & 0xff);
        }
        /// 
        /// Add a 16-bit value to a register without carry.
        /// 
        /// The vakue.
        private void ADD16(ref ushort reg, ushort b)
        {
            int w = reg + b;
            F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
            if (w > 0xffff)
            {
                F |= Z80Flags.Carry;
            }
            if ((reg ^ w ^ b) == 0x1000)
            {
                F |= Z80Flags.HalfCarry;
            }
            reg = (ushort)(w & 0xffff);
            F |= H35table[reg >> 8];
        }
        /// 
        /// Add a 16-bit value to a register with carry.
        /// 
        /// The vakue.
        private void ADC16(ref ushort reg, ushort b)
        {
            int w = reg + b + (int)(F & Z80Flags.Carry);
            F = Z80Flags.None;
            if ((w & 0xffff) == 0)
            {
                F |= Z80Flags.Zero;
            }
            if ((w & 0x8000) == 0x8000)
            {
                F |= Z80Flags.Sign;
            }
            if (w > 0xffff)
            {
                F |= Z80Flags.Carry;
            }
            if (((b ^ reg ^ 0x8000) & ((reg ^ w) & 0x8000)) == 0x8000)
            {
                F |= Z80Flags.PV;
            }
            if ((reg ^ w ^ b) == 0x1000)
            {
                F |= Z80Flags.HalfCarry;
            }
            reg = (ushort)(w & 0xffff);
            F |= H35table[reg >> 8];
        }
        /// 
        /// Subtract a 16-bit value from a register with carry.
        /// 
        /// The vakue.
        private void SBC(ref ushort reg, ushort b)
        {
            int w = reg - b - (int)(F & Z80Flags.Carry);
            F = Z80Flags.Neg;
            if (w < 0)
            {
                w += 0x10000;
                F |= Z80Flags.Carry;
            }
            if ((w & 0xffff) == 0)
            {
                F |= Z80Flags.Zero;
            }
            if ((w & 0x8000) == 0x8000)
            {
                F |= Z80Flags.Sign;
            }
            if (((b ^ reg) & ((reg ^ w) & 0x8000)) == 0x8000)
            {
                F |= Z80Flags.PV;
            }
            if ((reg ^ w ^ b) == 0x1000)
            {
                F |= Z80Flags.HalfCarry;
            }
            reg = (ushort)(w & 0xffff);
            F |= H35table[reg >> 8];
        }
        /// 
        /// Increment an 8-bit register.
        /// 
        /// The register to increment.
        void INC8(ref byte reg)
        {
            reg++;
            F &= Z80Flags.Carry;
            if (reg == 0x80)
            {
                F |= Z80Flags.PV;
            }
            if ((reg & 0x0f) == 0x00)
            {
                F |= Z80Flags.HalfCarry;
            }
            F |= SZtable[reg] | H35table[reg];
        }
        /// 
        /// Decrement an 8-bit register.
        /// 
        /// The register to decrement.
        void DEC8(ref byte reg)
        {
            reg--;
            F &= Z80Flags.Carry;
            F |= Z80Flags.Neg;
            if (reg == 0x7f)
            {
                F |= Z80Flags.PV;
            }
            if ((reg & 0x0f) == 0x0f)
            {
                F |= Z80Flags.HalfCarry;
            }
            F |= SZtable[reg] | H35table[reg];
        }
        /// 
        /// Decimally adjust the accumulator.  A bugger of an opcode.
        /// Based on info from http://www.worldofspectrum.org/faq/reference/z80reference.htm
        /// 
        void DAA()
        {
            byte add = 0;
            Z80Flags carry = Z80Flags.None;
            Z80Flags nf = F & Z80Flags.Neg;
            byte acc = A;
            if (acc>0x99 || (F & Z80Flags.Carry) == Z80Flags.Carry)
            {
                add |= 0x60;
                carry = Z80Flags.Carry;
            }
            if ((acc & 0xf) > 0x9 || (F & Z80Flags.HalfCarry) == Z80Flags.HalfCarry)
            {
                add|=0x06;
            }
            if (nf == Z80Flags.Neg)
            {
                A -= add;
            }
            else
            {
                A += add;
            }
            F = PSZtable[A]
                    | carry
                    | nf |
                    ((Z80Flags)(acc ^ A) & Z80Flags.HalfCarry)
                    | H35table[A];
        }
        #endregion
        #region ALU rotate and shift operations
        /// 
        /// Do RRCA.
        /// 
        private void RRCA()
        {
            F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
            F |= (Z80Flags)(A & 1);
            A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(A, 7));
            F |= H35table[A];
        }
        /// 
        /// Do RRA.
        /// 
        private void RRA()
        {
            byte carry = (byte)(F & Z80Flags.Carry);
            F &= Z80Flags.Sign | Z80Flags.Zero | Z80Flags.PV;
            F |= (Z80Flags)(A & 1);
            A = (byte)(Binary.ShiftRight(A, 1) | Binary.ShiftLeft(carry, 7));
            F |= H35table[A];
        }
        /// 
        /// Do RRC.
        /// 
        /// The register to operate on.
        private void RRC(ref byte reg)
        {
            F = (Z80Flags)(reg & (int)Z80Flags.Carry);
            reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(reg, 7));
            F |= PSZtable[reg] | H35table[reg];
        }
        /// 
        /// Do RR.
        /// 
        /// The register to operate on.
        private void RR(ref byte reg)
        {
            byte carry = (byte)(F & Z80Flags.Carry);
            F = (Z80Flags)(reg & (int)Z80Flags.Carry);
            reg = (byte)(Binary.ShiftRight(reg, 1) | Binary.ShiftLeft(carry, 7));
            F |= PSZtable[reg] | H35table[reg];
        }
        /// 
        /// Do RLCA.
        /// 
        private void RLCA()
        {
            F = (F & Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero)
                     | (Z80Flags)Binary.ShiftRight(A, 7);
            A = (byte)(Binary.ShiftLeft(A, 1) | Binary.ShiftRight(A, 7));
            F |= H35table[A];
        }
        /// 
        /// Do RLA.
        /// 
        private void RLA()
        {
            byte carry = (byte)(F & Z80Flags.Carry);
            F = (F & Z80Flags.PV | Z80Flags.Sign | Z80Flags.Zero)
                     | (Z80Flags)Binary.ShiftRight(A, 7);
            A = (byte)(Binary.ShiftRight(A, 1) | carry);
            F |= H35table[A];
        }
        /// 
        /// Do RLC.
        /// 
        /// The register to operate on.
        private void RLC(ref byte reg)
        {
            byte carry = Binary.ShiftRight(reg, 7);
            reg = (byte)(Binary.ShiftLeft(reg, 1) | carry);
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        /// 
        /// Do RL.
        /// 
        /// The register to operate on.
        private void RL(ref byte reg)
        {
            byte carry = Binary.ShiftRight(reg, 7);
            reg = (byte)(Binary.ShiftLeft(reg, 1) | (int)(F & Z80Flags.Carry));
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        /// 
        /// Do SRL.
        /// 
        /// The register to operate on.
        private void SRL(ref byte reg)
        {
            byte carry = (byte)(reg & 1);
            reg = Binary.ShiftLeft(reg, 1);
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        /// 
        /// Do SRA.
        /// 
        /// The register to operate on.
        private void SRA(ref byte reg)
        {
            byte carry = (byte)(reg & 1);
            reg = (byte)(Binary.ShiftLeft(reg, 1) | (reg & 0x80));
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        /// 
        /// Do SLL.
        /// 
        /// The register to operate on.
        private void SLL(ref byte reg)
        {
            byte carry = Binary.ShiftRight(reg, 7);
            reg = (byte)(Binary.ShiftRight(reg, 1) | 0x01);
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        /// 
        /// Do SLA.
        /// 
        /// The register to operate on.
        private void SLA(ref byte reg)
        {
            byte carry = Binary.ShiftRight(reg, 7);
            reg = Binary.ShiftRight(reg, 1);
            F = PSZtable[reg] | (Z80Flags)carry | H35table[reg];
        }
        #endregion
        #region ALU boolean operations
        /// 
        /// AND a value with the accumulator.
        /// 
        /// The value.
        void AND(byte val)
        {
            A &= val;
            F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A];
        }
        /// 
        /// OR a value with the accumulator.
        /// 
        /// The value.
        void OR(byte val)
        {
            A |= val;
            F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A];
        }
        /// 
        /// XOR a value with the accumulator.
        /// 
        /// The value.
        void XOR(byte val)
        {
            A ^= val;
            F = PSZtable[A] | Z80Flags.HalfCarry | H35table[A];
        }
        /// 
        /// Perform the BIT operation.
        /// 
        /// The register to operate on.
        /// The bit to test.
        void BIT(ref byte reg, int bit)
        {
            F &= Z80Flags.Carry;
            F |= Z80Flags.HalfCarry;
            if ((reg & (1 << bit)) != 0)
            {
                if (bit == 7 && (reg & (int)Z80Flags.Sign) != 0)
                {
                    F |= Z80Flags.Sign;
                }
                if (bit == 5 && (reg & (int)Z80Flags.Hidden5) != 0)
                {
                    F |= Z80Flags.Hidden5;
                }
                if (bit == 3 && (reg & (int)Z80Flags.Hidden3) != 0)
                {
                    F |= Z80Flags.Hidden3;
                }
            }
            else
            {
                F |= Z80Flags.Zero | Z80Flags.PV;
            }
        }
        /// 
        /// Perform the bit set operation.
        /// 
        /// The register to operate on.
        /// The bit to test.
        void BIT_SET(ref byte reg, int bit)
        {
            reg |= (byte)(1 << bit);
        }
        /// 
        /// Perform the bit clear operation.
        /// 
        /// The register to operate on.
        /// The bit to test.
        void BIT_RES(ref byte reg, int bit)
        {
            reg &= (byte)~(1 << bit);
        }
        #endregion
        #region Jump operations
        /// 
        /// The call operation.
        /// 
        private void CALL()
        {
            PUSH((ushort)(PC + 2));
            PC = (ushort)(memory.Read(PC) | memory.Read((ushort)(PC+1)) >> 8);
        }
        /// 
        /// The jump operation.
        /// 
        private void JP()
        {
            PC = (ushort)(memory.Read(PC) | memory.Read((ushort)(PC+1)) >> 8);
        }
        /// 
        /// The jump relative operation.
        /// 
        private void JR()
        {
            PC = (ushort)(PC + (sbyte)memory.Read(PC) + 1);
        }
        /// 
        /// Jump relative if the passed condition flag ANDed with the flag
        /// register equals the passed check value.
        /// 
        /// The condition flag.
        /// The check value.
        private void JR_COND(Z80Flags cond, Z80Flags val)
        {
            if ((F & cond) == val)
            {
                clock.Add(12);
                JR();
            }
            else
            {
                clock.Add(7);
                PC++;
            }
        }
        /// 
        /// Jump if the passed condition flag ANDed with the flag
        /// register equals the passed check value.
        /// 
        /// The condition flag.
        /// The check value.
        private void JP_COND(Z80Flags cond, Z80Flags val)
        {
            clock.Add(10);
            if ((F & cond) == val)
            {
                JP();
            }
            else
            {
                PC+=2;
            }
        }
        /// 
        /// Call if the passed condition flag ANDed with the flag
        /// register equals the passed check value.
        /// 
        /// The condition flag.
        /// The check value.
        private void CALL_COND(Z80Flags cond, Z80Flags val)
        {
            if ((F & cond) == val)
            {
                clock.Add(17);
                CALL();
            }
            else
            {
                clock.Add(10);
                PC+=2;
            }
        }
        /// 
        /// Return if the passed condition flag ANDed with the flag
        /// register equals the passed check value.
        /// 
        /// The condition flag.
        /// The check value.
        private void RET_COND(Z80Flags cond, Z80Flags val)
        {
            if ((F & cond) == val)
            {
                clock.Add(11);
                PC = POP();
            }
            else
            {
                clock.Add(5);
            }
        }
        /// 
        /// Reset the PC to an address.
        /// 
        /// The address.
        private void RST(ushort addr)
        {
            clock.Add(11);
            PUSH(PC);
            PC = addr;
        }
        #endregion
        #region Block operations
        /// 
        /// LDI instruction.
        /// 
        private void LDI()
        {
            byte b = memory.Read(HL.reg);
            memory.Write(DE.reg, b);
            DE.reg++;
            HL.reg++;
            BC.reg--;
            if (BC.reg != 0)
            {
                ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg);
                SetFlag(Z80Flags.PV);
            }
            else
            {
                ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.PV);
            }
            F |= H35table[A + b];
        }
        /// 
        /// LDD instruction.
        /// 
        private void LDD()
        {
            byte b = memory.Read(HL.reg);
            memory.Write(DE.reg, b);
            DE.reg--;
            HL.reg--;
            BC.reg--;
            if (BC.reg != 0)
            {
                ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg);
                SetFlag(Z80Flags.PV);
            }
            else
            {
                ClearFlag(Z80Flags.HalfCarry | Z80Flags.Neg | Z80Flags.PV);
            }
            F |= H35table[A + b];
        }
        /// 
        /// CPI instruction.
        /// 
        private void CPI()
        {
            Z80Flags c = F & Z80Flags.Carry;
            byte b = memory.Read(HL.reg);
            CP(b);
            F |= c;
            HL.reg++;
            BC.reg--;
            if (BC.reg != 0)
            {
                SetFlag(Z80Flags.PV);
            }
            else
            {
                ClearFlag(Z80Flags.PV);
            }
        }
        /// 
        /// CPD instruction.
        /// 
        private void CPD()
        {
            Z80Flags c = F & Z80Flags.Carry;
            byte b = memory.Read(HL.reg);
            CP(b);
            F |= c;
            HL.reg--;
            BC.reg--;
            if (BC.reg != 0)
            {
                SetFlag(Z80Flags.PV);
            }
            else
            {
                ClearFlag(Z80Flags.PV);
            }
        }
        /// 
        /// INI instruction.
        /// 
        private void INI()
        {
            int w;
            byte b = device.Read(BC.reg);
            memory.Write(HL.reg, b);
            BC.high--;
            HL.reg++;
            F = SZtable[BC.high] | H35table[BC.high];
            w = BC.low + b;
            if ((w & 0x80) == 0x80)
            {
                SetFlag(Z80Flags.Neg);
            }
            if ((w & 0x100) == 0x100)
            {
                SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
            }
            else
            {
                ClearFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
            }
        }
        /// 
        /// IND instruction.
        /// 
        private void IND()
        {
            int w;
            byte b = device.Read(BC.reg);
            memory.Write(HL.reg, b);
            BC.high--;
            HL.reg--;
            F = SZtable[BC.high] | H35table[BC.high];
            w = BC.low + b;
            if ((w & 0x80) == 0x80)
            {
                SetFlag(Z80Flags.Neg);
            }
            if ((w & 0x100) == 0x100)
            {
                SetFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
            }
            else
            {
                ClearFlag(Z80Flags.Carry | Z80Flags.HalfCarry);
            }
        }
        /// 
        /// OUTI instruction.
        /// 
        private void OUTI()
        {
            device.Write(BC.reg, memory.Read(HL.reg));
            HL.reg++;
            BC.high--;
            F = SZtable[BC.high] | H35table[BC.high];
        }
        /// 
        /// OUTD instruction.
        /// 
        private void OUTD()
        {
            device.Write(BC.reg, memory.Read(HL.reg));
            HL.reg--;
            BC.high--;
            F = SZtable[BC.high] | H35table[BC.high] | Z80Flags.Neg;
        }
        #endregion
    }
}