use crate::address_mode::AddressMode;
use crate::constants::constants_isa_op::ISA_OP_NOP;
use crate::constants::constants_system::*;
use crate::instruction::Instruction;
use crate::instruction_table::INSTRUCTION_TABLE;
use crate::mos6502flags::Mos6502Flag::*;
use crate::mos6502flags::{Mos6502Flag, Mos6502Flags};
use crate::op_info::OpInfo;
use crate::operand::Operand;
use crate::operation::Operation;
use log::trace;
use crate::mos6502cpu::Mos6502Cpu;
use crate::mos6502cpu::tick_stages::Mos6502TickStates;
use crate::mos6502cpu::tick_stages::Mos6502TickStates::*;

enum Mos6502Registers {
    A,
    X,
    Y
}

impl Mos6502Cpu {
    //
    // fn read_word(&self, offset: &u16) -> u16 {
    //     println!("READING OFFSET 0x{offset:04x} and 0x{:04x}", offset + 1);
    //     let low = self.memory[*offset as usize];
    //     let high = self.memory[*offset as usize + 1];
    //     println!("LOW = 0x{low:02x} HIGH = 0x{high:02x}");
    //     let result = (high as u16) << 8 | low as u16;
    //     // println!("MEMORY: {:?}", self.memory);
    //     println!("READ {result:04x}");
    //     result
    // }

    pub fn peek_register(&self, register_to_peek: Mos6502Registers) -> u8 {
        match register_to_peek {
            Mos6502Registers::A => self.a,
            Mos6502Registers::X => self.x,
            Mos6502Registers::Y => self.y
        }
    }

    pub fn poke_register(&mut self, register_to_poke: Mos6502Registers, new_value: u8) {
        match register_to_poke {
            Mos6502Registers::A => self.a = new_value,
            Mos6502Registers::X => self.x = new_value,
            Mos6502Registers::Y => self.y = new_value
        }
    }

    pub fn peek_flag(&self, flag_to_read: Mos6502Flag) -> bool {
        self.flags.flag(flag_to_read)
    }
    pub fn poke_flag(&mut self, flag_to_set: Mos6502Flag, new_value: bool) {
        if new_value {
            self.flags.set_flag(flag_to_set)
        } else {
            self.flags.clear_flag(flag_to_set)
        }
    }

    pub fn peek_memory(&self, offset: u16) -> u8 {
        self.memory[offset as usize]
    }

    pub fn poke_memory(&mut self, offset: u16, value: u8) {
        println!("Setting memory at {offset:04x} to {value:02x}");
        self.memory[offset as usize] = value
    }

    fn advance_pc(&mut self, how_far: u16) {
        self.pc += how_far;
    }

    fn set_pc_to(&mut self, new_pc: u16) {
        self.pc = new_pc;
    }

    /// Ticks the CPU
    pub fn tick(&mut self) {
        println!("PREPARiNG TO TICK CPU AT PC 0x{:04x}", self.pc);
        match self.tick_stage {
            LoadingInstruction => {
                println!("Loading instruction from data bus -> {}", self.data_bus);

                let instruction = INSTRUCTION_TABLE[self.data_bus as usize].clone();

                if let Some(inst) = instruction {
                    println!("DECODED INSTRUCTION [{:?}]/[{:?}]", inst.operation, inst.mode);
                    match inst.mode {
                        AddressMode::Absolute | AddressMode::AbsoluteX | AddressMode::AbsoluteY => {
                            println!("NEED TO LOAD a 16bit VALUE FOR INSTRUCTION");
                            self.tick_stage = Loading16BitParameter1;
                        }
                        AddressMode::Immediate => {
                            println!("LOADING A 8BIT VALUE FOR INSTRUCTION");
                            self.tick_stage = Loading8BitParameter;
                        }
                        _ => {}
                    }
                } else {
                    println!("INVALID DECODE OF [${:02x}", self.data_bus);
                }

            }
            Loading8BitParameter => {
                println!("Loading parameter for 8bit ");
            },
            Loading16BitParameter1 => {
                println!("Loading high bits of parameter");
            },
            Loading16BitParameter2 => {
                println!("Loading low bits of parameter");
            },
            Stall(length) => {
                println!("PREPARING TO STALL FOR {} CYCLES", length);
            },
            Waiting => {
                println!("CPU IS WAITING.");
            }
        }

        if self.microcode_step == 0 {
            println!("OUT OF MICROSTEPS.  Decoding the next instruction");
            let offset = self.pc as usize;
            // TODO: this calls opinfo 2x
            self.oi = Instruction::opinfo(&self.memory[offset..offset + 4]).unwrap();
            self.ir = Instruction::decode(&self.memory[offset..offset + 4]).unwrap();
            self.microcode_step = self.oi.cycles;
            println!("Decoded [[{:?}]]", self.ir);
            self.advance_pc(self.oi.length as u16);
            // load the microstep buffer with what steps to run
            // set the counter to the number of steps left
        } else {
            // run 1 microcode step
            println!(
                "Microstep {}/{} for {:?}",
                self.microcode_step, self.oi.cycles, self.ir.op
            );
            match self.ir.op {
                Operation::ADC => match self.microcode_step {
                    1 => match self.ir.mode {
                        AddressMode::Immediate => {}
                        AddressMode::ZeroPage => {}
                        AddressMode::ZeroPageX => {}
                        AddressMode::Absolute => {}
                        AddressMode::AbsoluteX => {}
                        AddressMode::AbsoluteY => {}
                        AddressMode::Indirect => {}
                        AddressMode::IndirectX => {}
                        AddressMode::IndirectY => {}
                        _ => {}
                    },
                    2 => {}
                    _ => {}
                },
                Operation::AND => {}
                Operation::ASL => {}
                Operation::BCC => {}
                Operation::BCS => {}
                Operation::BEQ => {}
                Operation::BIT => {}
                Operation::BMI => {}
                Operation::BNE => {}
                Operation::BPL => {}
                Operation::BRK => {}
                Operation::BVC => {}
                Operation::BVS => {}
                Operation::CLC => {
                    self.flags.clear_flag(Carry);
                }
                Operation::CLD => {
                    self.flags.clear_flag(Decimal);
                }
                Operation::CLI => {
                    self.flags.clear_flag(Interrupt);
                }
                Operation::CLV => {
                    self.flags.clear_flag(Overflow);
                }
                Operation::CMP => {}
                Operation::CPX => {}
                Operation::CPY => {}
                Operation::DEC => {
                    match self.microcode_step {
                        // DEC Step 1
                        1 => {
                            let working_value = match self.oi.mode {
                                AddressMode::ZeroPage => {
                                    // read from
                                    let offset = match self.ir.operand {
                                        Operand::Byte(z) => z,
                                        _ => 0x00,
                                    };
                                    trace!("READING FROM MEMORY AT 0x{offset:04x}");
                                    self.memory[offset as usize]
                                    // self.peek(offset);
                                }
                                AddressMode::ZeroPageX => {
                                    let offset = match self.ir.operand {
                                        Operand::Byte(z) => z,
                                        _ => 0x00,
                                    };
                                    // self.memory.peek(offset + self.x);
                                    self.memory[offset as usize]
                                }
                                AddressMode::Absolute => {
                                    let offset = match self.ir.operand {
                                        Operand::Word(offset) => offset,
                                        _ => 0x00,
                                    };
                                    // self.memory.peek(offset)
                                    self.memory[offset as usize]
                                }
                                AddressMode::AbsoluteX => {
                                    let offset = match self.ir.operand {
                                        Operand::Word(offset) => offset,
                                        _ => 0x00,
                                    };
                                    // self.memory.peek(offset + self.x);
                                    self.memory[offset as usize]
                                }
                                _ => 0x00,
                            };
                        }
                        // DEC write memory
                        2 => {
                            self.a = self.cycle_carry as u8;
                        }
                        _ => {}
                    }
                }
                Operation::DEX => {
                    if self.microcode_step == 1 {
                        let (new_x, new_carry) = self.x.overflowing_sub(1);
                        self.poke_register(Mos6502Registers::X, new_x);
                        self.poke_flag(Carry, new_carry);
                    }
                }
                Operation::DEY => {
                    if self.microcode_step == 1 {
                        (self.y, _) = self.y.overflowing_sub(1);
                    }
                }
                Operation::EOR => {}
                Operation::INC => {}
                Operation::INX => {
                    if self.microcode_step == 1 {
                        let (new_x, new_carry) = self.x.overflowing_add(1);
                        self.poke_register(Mos6502Registers::X, new_x);
                        self.poke_flag(Carry, new_carry);
                        self.address_bus = self.pc;
                        self.data_bus = 0x00;
                    }
                }
                Operation::INY => {
                    if self.microcode_step == 1 {
                        let (new_y, new_carry) = self.y.overflowing_add(1);
                        self.poke_register(Mos6502Registers::Y, new_y);
                        self.poke_flag(Carry, new_carry);
                        self.address_bus = self.pc;
                        self.data_bus = 0x00;
                    }
                }
                Operation::JMP => match self.ir.operand {
                    Operand::Word(offset) => {
                        self.pc = offset;
                        self.address_bus = self.pc;
                        self.data_bus = 0x00;
                    }
                    _ => {}
                },
                Operation::JSR => {
                    // push pc to stack.
                    // jump to the subroutine.
                }
                Operation::LDA => match self.oi.mode {
                    AddressMode::Immediate => match self.ir.operand {
                        Operand::Byte(value) => {
                            println!("Loading 0x{value:02x} ({value}) into A");
                            self.a = value;
                        }
                        _ => {}
                    },
                    AddressMode::ZeroPage => match self.ir.operand {
                        Operand::Byte(value) => {
                            println!("Loading from zero page at 0x{value:02x} ({value})");
                            self.a = self.memory[value as usize];
                        }
                        _ => {}
                    },
                    AddressMode::ZeroPageX => match self.ir.operand {
                        Operand::Byte(value) => {
                            let x_offset = self.x;
                            self.a = self.memory[(value + x_offset) as usize];
                        }
                        _ => {}
                    },
                    AddressMode::Absolute => {
                        if let Operand::Word(offset) = self.ir.operand {
                            println!("Loading from absolute address 0x{offset:04x}");
                            self.a = self.memory[offset as usize];
                        }
                    }
                    AddressMode::AbsoluteX => {
                        if let Operand::Word(offset) = self.ir.operand {
                            self.a = self.memory[(offset + self.x as u16) as usize];
                        }
                    }
                    AddressMode::AbsoluteY => {
                        if let Operand::Word(offset) = self.ir.operand {
                            let real_offset = offset + self.y as u16;
                            println!("offset: {offset:04x} + {:02x}", self.y);
                            self.a = self.memory[(offset + self.y as u16) as usize];
                        }
                    }
                    AddressMode::Indirect => {}
                    AddressMode::IndirectX => {}
                    AddressMode::IndirectY => {}
                    _ => {
                        println!("INVALID ADDRESS MODE FOR LDA");
                    }
                },
                Operation::LDX => {}
                Operation::LDY => {}
                Operation::LSR => {}
                Operation::NOP => {
                    // do nothing.
                }
                Operation::ORA => {}
                Operation::PHA => {}
                Operation::PHP => {}
                Operation::PLA => {}
                Operation::PLP => {}
                Operation::ROL => {
                    if self.microcode_step == 1 {
                        self.a = self.a.rotate_left(1);
                    }
                }
                Operation::ROR => {
                    // rotate A
                    if self.microcode_step == 1 {
                        self.a = self.a.rotate_right(1);
                    }
                }
                Operation::RTI => {}
                Operation::RTS => {}
                Operation::SBC => {}
                Operation::SEC => {
                    self.flags.set_flag(Carry);
                }
                Operation::SED => {
                    self.flags.set_flag(Decimal);
                }
                Operation::SEI => {
                    self.flags.set_flag(Interrupt);
                }
                Operation::STA => {
                    match self.oi.mode {
                        AddressMode::ZeroPage => {
                            // write to the zero page.
                            match self.ir.operand {
                                Operand::Byte(target) => {
                                    self.memory[target as usize] = self.a;
                                }
                                _ => {
                                    // Invalid parameter
                                }
                            }
                        }
                        AddressMode::ZeroPageX => {
                            match self.ir.operand {
                                Operand::Byte(target) => {
                                    let x = self.x;
                                    self.memory[(x + target) as usize] = self.a;
                                }
                                _ => {
                                    // Invalid Parameter
                                }
                            }
                        }
                        AddressMode::Absolute => {
                            // write from A to the specified memory location
                            match self.ir.operand {
                                Operand::Word(offset) => {
                                    self.memory[offset as usize] = self.a;
                                }
                                _ => {
                                    // Invalid Parameter
                                }
                            }
                        }
                        AddressMode::AbsoluteX => {
                            match self.ir.operand {
                                Operand::Word(offset) => {
                                    self.memory[(offset + self.x as u16) as usize] = self.a;
                                }
                                _ => {
                                    // Invalid Parameter
                                }
                            }
                        }
                        AddressMode::AbsoluteY => {
                            match self.ir.operand {
                                Operand::Word(offset) => {
                                    self.memory[(offset + self.y as u16) as usize] = self.a;
                                }
                                _ => {
                                    // Invalid Parameter
                                }
                            }
                        }
                        AddressMode::IndirectX => {}
                        AddressMode::IndirectY => {}
                        _ => {
                            // invalid memory mode
                        }
                    }
                }
                Operation::STX => {}
                Operation::STY => {}
                Operation::TAX => {
                    self.x = self.a;
                }
                Operation::TAY => {
                    self.y = self.a;
                }
                Operation::TSX => {}
                Operation::TXA => {
                    self.a = self.x;
                }
                Operation::TXS => {}
                Operation::TYA => {
                    self.y = self.a;
                }
            }
            self.microcode_step -= 1;
        }
    }
}
/*
#[cfg(test)]
mod test {
    use super::*;
    use crate::constants::constants_isa_op::*;
    use crate::instruction_table::{instruction_cycles, INSTRUCTION_TABLE};

    #[test]
    fn clc() {
        // setup the CPU for our test
        let mut cpu = Mos6502Cpu::default();

        // tick through the reset cycle
        while !cpu.has_reset {
            cpu.tick();
        }
        println!("DONE RESET TICKS");
        cpu.flags.set_flag(Carry);
        // Load our 'test program'
        cpu.memory[0x6000] = ISA_OP_CLC;
        // Start the PC at our program
        cpu.pc = 0x6000;

        // Tick the CPU through the instruction
        for _ in 0..instruction_cycles(ISA_OP_CLC) {
            cpu.tick();
        }

        assert!(!cpu.peek_flag(Carry));
    }

    #[test]
    fn cld() {
        let mut cpu = Mos6502Cpu::default();
        cpu.flags.set_flag(Decimal);
        cpu.memory[0x6000] = ISA_OP_CLD;
        cpu.pc = 0x6000;

        for _ in 0..instruction_cycles(ISA_OP_CLD) {
            cpu.tick();
        }

        assert!(!cpu.peek_flag(Decimal));
    }

    #[test]
    fn cli() {
        let mut cpu = Mos6502Cpu::default();
        cpu.flags.set_flag(Interrupt);
        cpu.memory[0x6000] = ISA_OP_CLI;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_CLI) {
            cpu.tick();
        }

        assert!(!cpu.peek_flag(Interrupt));
    }

    #[test]
    fn clv() {
        let mut cpu = Mos6502Cpu::default();
        cpu.flags.set_flag(Overflow);
        cpu.memory[0x6000] = ISA_OP_CLV;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_CLV) {
            cpu.tick();
        }

        assert!(!cpu.peek_flag(Overflow));
    }

    #[test]
    fn lda_immediate() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x6000] = ISA_OP_LDA_I;
        cpu.memory[0x6001] = 0xab;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_LDA_I) {
            cpu.tick();
        }

        assert_eq!(cpu.a, 0xab);
    }

    #[test]
    fn lda_zx() {
        let mut cpu = Mos6502Cpu::default();
        cpu.poke_x(1);
        cpu.memory[0x6000] = ISA_OP_LDA_ZX;
        cpu.memory[0x6001] = 0xab;
        cpu.memory[0x00ac] = 0xbe;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_LDA_ZX) {
            cpu.tick();
        }

        // println!("MEMORY AT 0x00aa, ab, ac, ad, ae -> {:02x} {:02x} {:02x} {:02x} {:02x}", cpu.memory[0x00aa], cpu.memory[0x00ab], cpu.memory[0x00ac], cpu.memory[0x00ad], cpu.memory[0x00ae]);
        // cpu.dump();
        assert_eq!(cpu.peek_a(), 0xbe);
        assert!(!cpu.peek_flag(Zero));
        assert!(!cpu.peek_flag(Carry));
        assert!(!cpu.peek_flag(Negative));
    }

    #[test]
    fn lda_zeropage() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x6000] = ISA_OP_LDA_Z;
        cpu.memory[0x6001] = 0xab;
        // Load ZeroPage
        cpu.memory[0x00ab] = 0xbe;
        cpu.pc = 0x6000;

        for _ in 0..instruction_cycles(ISA_OP_LDA_Z) {
            cpu.tick();
        }

        assert_eq!(cpu.a, 0xbe);
    }

    #[test]
    fn lda_absolute() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x6000] = ISA_OP_LDA_ABS;
        cpu.memory[0x6001] = 0xef;
        cpu.memory[0x6002] = 0x0e;
        cpu.memory[0x0eef] = 0xab;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_LDA_ABS) {
            cpu.tick();
        }

        assert_eq!(cpu.a, 0xab);
    }

    #[test]
    fn lda_absolutex() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x6000] = ISA_OP_LDA_ABSX;
        cpu.memory[0x6001] = 0xef;
        cpu.memory[0x6002] = 0x0e;
        cpu.poke_x(0x01);
        cpu.memory[0x0ef0] = 0xab;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_LDA_ABSX) {
            cpu.tick();
        }

        assert_eq!(cpu.a, 0xab);
    }

    #[test]
    fn lda_absolutey() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x6000] = ISA_OP_LDA_ABSY;
        cpu.memory[0x6001] = 0xef;
        cpu.memory[0x6002] = 0x0e;
        cpu.poke_y(0x01);
        cpu.memory[0x0ef0] = 0xab;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_LDA_ABSY) {
            cpu.tick();
        }

        assert_eq!(cpu.a, 0xab);
    }

    #[test]
    fn dex_inx() {
        let mut cpu = Mos6502Cpu::default();
        cpu.x = 0xab;
        cpu.memory[0x6000] = ISA_OP_DEX;
        cpu.memory[0x6001] = ISA_OP_INX;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_DEX) {
            cpu.tick();
        }
        assert_eq!(0xaa, cpu.x);
        for _ in 0..=instruction_cycles(ISA_OP_INX) {
            cpu.tick();
        }
        assert_eq!(0xab, cpu.x);
    }

    #[test]
    fn dey_iny() {
        let mut cpu = Mos6502Cpu::default();
        cpu.poke_y(0xab);
        cpu.memory[0x6000] = ISA_OP_DEY;
        cpu.memory[0x6001] = ISA_OP_INY;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_DEY) {
            cpu.tick();
        }
        assert_eq!(0xaa, cpu.peek_y());
        for _ in 0..=instruction_cycles(ISA_OP_INY) {
            cpu.tick();
        }
        assert_eq!(0xab, cpu.peek_y());
    }

    #[test]
    fn rol_a_ror_a() {
        let mut cpu = Mos6502Cpu::default();
        cpu.poke_a(0b1010_1010); // 0xaa
        cpu.memory[0x6000] = ISA_OP_ROL_A;
        cpu.memory[0x6001] = ISA_OP_ROR_A;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_ROL_A) {
            cpu.tick();
        }
        assert_eq!(cpu.peek_a(), 0b0101_0101);
        for _ in 0..=instruction_cycles(ISA_OP_ROR_A) {
            cpu.tick();
        }
        assert_eq!(cpu.peek_a(), 0b1010_1010);
    }

    #[test]
    fn rol_zp_ror_zp() {
        let mut cpu = Mos6502Cpu::default();
        cpu.memory[0x00ab] = 0b0101_0101;
        cpu.memory[0x6000] = ISA_OP_ROL_ZP;
        cpu.memory[0x6001] = 0xab;
        cpu.pc = 0x6000;

        for _ in 0..=instruction_cycles(ISA_OP_ROL_ZP) {
            cpu.tick();
        }

        assert_eq!(cpu.memory[0xab], 0b1010_1010);;
    }
}
*/
