use crate::mmu::Mmu;

pub const PREFIX: u8 = 0xcb;

pub const INSTRUCTIONS: [(fn(&mut Cpu), u8, &'static str); 256] = [
    // 0x0 opcodes
    (nop, 4, "NOP"),
    (ld_bc_u16, 12, "LD BC, u16"),
    (ld_mbc_a, 8, "LD [BC], A"),
    (inc_bc, 8, "INC BC"),
    (inc_b, 4, "INC B"),
    (dec_b, 4, "DEC B"),
    (ld_b_u8, 8, "LD B, u8"),
    (rlca, 4, "RLCA"),
    (ld_mu16_sp, 20, "LD [u16], SP"),
    (add_hl_bc, 8, "ADD HL, BC"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (inc_c, 4, "INC C"),
    (nop, 4, "NOP"),
    (ld_c_u8, 8, "LD C, u8"),
    (nop, 4, "NOP"),
    // 0x1 opcodes
    (nop, 4, "NOP"),
    (ld_de_u16, 12, "LD DE, u16"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (ld_a_mde, 8, "LD A, [DE]"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x2 opcodes
    (jr_nz_i8, 8, "JR NZ, i8"),
    (ld_hl_u16, 12, "LD HL, u16"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x3 opcodes
    (nop, 4, "NOP"),
    (ld_sp_u16, 12, "LD SP, u16"),
    (ld_mhld_a, 8, "LD [HL]-, A"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (ld_a_u8, 8, "LD A, u8"),
    (nop, 4, "NOP"),
    // 0x4 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x5 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x6 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x7 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x8 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x9 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xa opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (xor_a_a, 4, "XOR A, A"),
    // 0xb opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xc opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (call_u16, 24, "CALL u16"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xd opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xe opcodes
    (ld_mff00u8_a, 12, "LD [FF00+u8], A"),
    (nop, 4, "NOP"),
    (ld_mff00c_a, 8, "LD [FF00+C], A"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xf opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
];

pub const BITWISE: [(fn(&mut Cpu), u8, &'static str); 176] = [
    // 0x0 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x1 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x2 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x3 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x4 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x5 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x6 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x7 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (bit_7_h, 8, "BIT 7, H"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x8 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0x9 opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    // 0xa opcodes
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
    (nop, 4, "NOP"),
];

pub struct Cpu {
    pc: u16,
    sp: u16,
    a: u8,
    b: u8,
    c: u8,
    d: u8,
    e: u8,
    h: u8,
    l: u8,
    zero: bool,
    sub: bool,
    half_carry: bool,
    carry: bool,
    mmu: Mmu,
    ticks: u32,
}

impl Cpu {
    pub fn new(mmu: Mmu) -> Cpu {
        Cpu {
            pc: 0x0,
            sp: 0x0,
            a: 0x0,
            b: 0x0,
            c: 0x0,
            d: 0x0,
            e: 0x0,
            h: 0x0,
            l: 0x0,
            zero: false,
            sub: false,
            half_carry: false,
            carry: false,
            mmu: mmu,
            ticks: 0,
        }
    }

    pub fn clock(&mut self) {
        let pc = self.pc;

        // fetches the current instruction and increments
        // the PC (program counter) accordingly
        let mut opcode = self.mmu.read(self.pc);
        self.pc = self.pc.wrapping_add(1);

        let instruction: &(fn(&mut Cpu), u8, &str);

        if opcode == PREFIX {
            opcode = self.mmu.read(self.pc);
            self.pc = self.pc.wrapping_add(1);
            instruction = &BITWISE[opcode as usize];
        } else {
            instruction = &INSTRUCTIONS[opcode as usize];
        }

        let (instruction_fn, instruction_size, instruction_str) = instruction;

        println!("{}\t(0x{:02x})\t${:04x}", instruction_str, opcode, pc);

        instruction_fn(self);
        self.ticks = self.ticks.wrapping_add(*instruction_size as u32);
    }

    #[inline(always)]
    pub fn mmu(&mut self) -> &mut Mmu {
        &mut self.mmu
    }

    #[inline(always)]
    pub fn pc(&self) -> u16 {
        self.pc
    }

    #[inline(always)]
    fn af(&self) -> u16 {
        (self.a as u16) << 8 | self.f() as u16
    }

    #[inline(always)]
    fn bc(&self) -> u16 {
        (self.b as u16) << 8 | self.c as u16
    }

    #[inline(always)]
    fn f(&self) -> u8 {
        let mut f = 0x0u8;
        if self.zero {
            f |= 0x80;
        }
        if self.sub {
            f |= 0x40;
        }
        if self.half_carry {
            f |= 0x20;
        }
        if self.carry {
            f |= 0x10;
        }
        f
    }

    #[inline(always)]
    fn set_bc(&mut self, value: u16) {
        self.b = (value >> 8) as u8;
        self.c = value as u8;
    }

    #[inline(always)]
    fn de(&self) -> u16 {
        (self.d as u16) << 8 | self.e as u16
    }

    #[inline(always)]
    fn set_de(&mut self, value: u16) {
        self.d = (value >> 8) as u8;
        self.e = value as u8;
    }

    #[inline(always)]
    fn hl(&self) -> u16 {
        (self.h as u16) << 8 | self.l as u16
    }

    #[inline(always)]
    fn set_hl(&mut self, value: u16) {
        self.h = (value >> 8) as u8;
        self.l = value as u8;
    }

    #[inline(always)]
    fn read_u8(&mut self) -> u8 {
        let byte = self.mmu.read(self.pc);
        self.pc = self.pc.wrapping_add(1);
        byte
    }

    #[inline(always)]
    fn read_u16(&mut self) -> u16 {
        let byte1 = self.read_u8();
        let byte2 = self.read_u8();
        let word = byte1 as u16 | ((byte2 as u16) << 8);
        word
    }

    #[inline(always)]
    fn get_zero(&self) -> bool {
        self.zero
    }

    #[inline(always)]
    fn set_zero(&mut self, value: bool) {
        self.zero = value
    }

    #[inline(always)]
    fn get_sub(&self) -> bool {
        self.sub
    }

    #[inline(always)]
    fn set_sub(&mut self, value: bool) {
        self.sub = value;
    }

    #[inline(always)]
    fn get_half_carry(&self) -> bool {
        self.half_carry
    }

    #[inline(always)]
    fn set_half_carry(&mut self, value: bool) {
        self.half_carry = value
    }

    #[inline(always)]
    fn get_carry(&self) -> bool {
        self.carry
    }

    #[inline(always)]
    fn set_carry(&mut self, value: bool) {
        self.carry = value;
    }
}

fn nop(_cpu: &mut Cpu) {}

fn ld_bc_u16(cpu: &mut Cpu) {
    let word = cpu.read_u16();
    cpu.set_bc(word);
}

fn ld_mbc_a(cpu: &mut Cpu) {
    cpu.mmu.write(cpu.bc(), cpu.a);
}

fn inc_bc(cpu: &mut Cpu) {
    cpu.set_bc(cpu.bc().wrapping_add(1));
}

fn inc_b(cpu: &mut Cpu) {
    let value = cpu.b.wrapping_add(1);

    cpu.set_sub(false);
    cpu.set_zero(value == 0);
    cpu.set_half_carry((value & 0xf) == 0xf);

    cpu.b = value;
}

fn dec_b(cpu: &mut Cpu) {
    let value = cpu.b.wrapping_sub(1);

    cpu.set_sub(true);
    cpu.set_zero(value == 0);
    cpu.set_half_carry((value & 0xf) == 0xf);

    cpu.b = value;
}

fn ld_b_u8(cpu: &mut Cpu) {
    let byte = cpu.read_u8();
    cpu.b = byte;
}

fn rlca(cpu: &mut Cpu) {
    let carry = cpu.a >> 7;

    cpu.a = cpu.a << 1 | carry;

    cpu.set_sub(false);
    cpu.set_zero(false);
    cpu.set_half_carry(false);
    cpu.set_carry(carry == 1);
}

fn ld_mu16_sp(cpu: &mut Cpu) {
    let word = cpu.read_u16();
    cpu.mmu.write(word, cpu.sp as u8);
    cpu.mmu.write(word + 1, (cpu.sp >> 8) as u8);
}

fn add_hl_bc(cpu: &mut Cpu) {
    let value = add_u16_u16(cpu, cpu.hl(), cpu.bc());
    cpu.set_hl(value);
}

fn inc_c(cpu: &mut Cpu) {
    let value = cpu.c.wrapping_add(1);

    cpu.set_sub(false);
    cpu.set_zero(value == 0);
    cpu.set_half_carry((value & 0xf) == 0xf);

    cpu.c = value;
}

fn ld_c_u8(cpu: &mut Cpu) {
    let byte = cpu.read_u8();
    cpu.c = byte;
}

fn ld_de_u16(cpu: &mut Cpu) {
    let word = cpu.read_u16();
    cpu.set_de(word);
}

fn ld_a_mde(cpu: &mut Cpu) {
    let byte = cpu.mmu.read(cpu.de());
    cpu.a = byte;
}

fn jr_nz_i8(cpu: &mut Cpu) {
    let byte = cpu.read_u8() as i8;

    if cpu.get_zero() {
        return;
    }

    cpu.pc = (cpu.pc as i16).wrapping_add(byte as i16) as u16;
    cpu.ticks = cpu.ticks.wrapping_add(4);
}

fn ld_hl_u16(cpu: &mut Cpu) {
    let word = cpu.read_u16();
    cpu.set_hl(word);
}

fn ld_sp_u16(cpu: &mut Cpu) {
    cpu.sp = cpu.read_u16();
}

fn ld_mhld_a(cpu: &mut Cpu) {
    cpu.mmu.write(cpu.hl(), cpu.a);
    cpu.set_hl(cpu.hl().wrapping_sub(1));
}

fn ld_a_u8(cpu: &mut Cpu) {
    let byte = cpu.read_u8();
    cpu.a = byte;
}

fn xor_a_a(cpu: &mut Cpu) {
    cpu.a ^= cpu.a;

    cpu.set_sub(false);
    cpu.set_zero(cpu.a == 0);
    cpu.set_half_carry(false);
    cpu.set_carry(false);
}

fn call_u16(cpu: &mut Cpu) {
    // @todo push stack and set the current PC
    // to be able to jump
    cpu.sp += 1;
}

fn ld_mff00u8_a(cpu: &mut Cpu) {
    let byte = cpu.read_u8();
    cpu.mmu.write(0xff0c + byte as u16, cpu.a);
}

fn ld_mff00c_a(cpu: &mut Cpu) {
    cpu.mmu.write(0xff0c + cpu.c as u16, cpu.a);
}

fn bit_7_h(cpu: &mut Cpu) {
    bit_h(cpu, 7);
}

/// Helper function to test one bit in a u8.
/// Returns true if bit is 0.
fn bit_zero(val: u8, bit: u8) -> bool {
    (val & (1u8 << (bit as usize))) == 0
}

fn bit_h(cpu: &mut Cpu, bit: u8) {
    println!("{}", cpu.h);
    cpu.set_sub(false);
    cpu.set_zero(bit_zero(cpu.h, bit));
    cpu.set_half_carry(true);
}

fn add_u16_u16(cpu: &mut Cpu, first: u16, second: u16) -> u16 {
    let first = first as u32;
    let second = second as u32;
    let value = first.wrapping_add(second);

    cpu.set_sub(false);
    cpu.set_carry(value & 0x1000 == 0x1000);
    cpu.set_half_carry((first ^ second ^ value) & 0x1000 == 0x1000);

    value as u16
}