use crate::mos6502flags::Mos6502Flag::{
    Break, Carry, Decimal, Interrupt, Negative, Overflow, Zero,
};

pub const BIT_NEGATIVE: u8 = 7;
pub const BIT_OVERFLOW: u8 = 6;
pub const BIT_BREAK: u8 = 4;
pub const BIT_DECIMAL: u8 = 3;
pub const BIT_INTERRUPT: u8 = 2;
pub const BIT_ZERO: u8 = 1;
pub const BIT_CARRY: u8 = 0;
/// Represents the status flags in the 6502 processor's status register (P).
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Mos6502Flag {
    /// Carry Flag (C)
    ///
    /// Set if an arithmetic operation results in a carry out of the most significant bit (for addition),
    /// or a borrow (for subtraction). Also used for bit shifts and rotates.
    Carry,

    /// Zero Flag (Z)
    ///
    /// Set if the result of an operation is zero.
    Zero,

    /// Interrupt Disable Flag (I)
    ///
    /// When set, disables maskable interrupts (IRQ).
    Interrupt,

    /// Decimal Mode Flag (D)
    ///
    /// When set, arithmetic operations use Binary-Coded Decimal (BCD) mode.
    /// Note: Not supported on all 6502 variants (e.g., not on the NES CPU).
    Decimal,

    /// Break Command Flag (B)
    ///
    /// Set when a BRK (break) instruction is executed.
    /// Used to distinguish software interrupts from hardware ones.
    Break,

    /// Overflow Flag (V)
    ///
    /// Set when an arithmetic operation results in a signed overflow.
    /// For example, adding two positive numbers results in a negative.
    Overflow,

    /// Negative Flag (N)
    ///
    /// Set if the result of an operation has bit 7 set (i.e., the result is negative in two's complement).
    Negative,
}

impl Mos6502Flag {
    pub fn index(&self) -> u8 {
        match self {
            Carry => BIT_CARRY,
            Zero => BIT_ZERO,
            Interrupt => BIT_INTERRUPT,
            Decimal => BIT_DECIMAL,
            Break => BIT_BREAK,
            Overflow => BIT_OVERFLOW,
            Negative => BIT_NEGATIVE,
        }
    }
}

#[derive(Default, PartialEq, Debug)]
pub struct Mos6502Flags {
    carry: bool,
    zero: bool,
    interrupt: bool,
    decimal: bool,
    break_flag: bool,
    overflow: bool,
    negative: bool,
}

impl Mos6502Flags {
    pub fn dump(&self) -> String {
        format!(
            "{}{}{}{}{}{}{}",
            if self.carry { 'C' } else { 'c' },
            if self.zero { 'Z' } else { 'z' },
            if self.interrupt { 'I' } else { 'i' },
            if self.decimal { 'D' } else { 'd' },
            if self.break_flag { 'B' } else { 'b' },
            if self.overflow { 'O' } else { 'o' },
            if self.negative { 'N' } else { 'n' }
        )
    }
}

impl Mos6502Flags {
    pub fn set_flag(&mut self, flag_to_set: Mos6502Flag) {
        println!("Setting {flag_to_set:?} flag");
        match flag_to_set {
            Carry => self.carry = true,
            Zero => self.zero = true,
            Interrupt => self.interrupt = true,
            Decimal => self.decimal = true,
            Break => self.break_flag = true,
            Overflow => self.overflow = true,
            Negative => self.negative = true,
        }
    }

    pub fn clear_flag(&mut self, flag_to_clear: Mos6502Flag) {
        println!("Clearing {flag_to_clear:?} flag");
        match flag_to_clear {
            Carry => self.carry = false,
            Zero => self.zero = false,
            Interrupt => self.interrupt = false,
            Decimal => self.decimal = false,
            Break => self.break_flag = false,
            Overflow => self.overflow = false,
            Negative => self.negative = false,
        }
    }

    fn change_flag(&mut self, flag_to_change: Mos6502Flag, new_value: bool) {
        if new_value {
            self.set_flag(flag_to_change);
        } else {
            self.clear_flag(flag_to_change);
        }
    }

    pub fn flag(&self, flag_to_read: Mos6502Flag) -> bool {
        match flag_to_read {
            Mos6502Flag::Negative => self.negative,
            Mos6502Flag::Overflow => self.overflow,
            // 5
            Mos6502Flag::Break => self.break_flag,
            Mos6502Flag::Decimal => self.decimal,
            Mos6502Flag::Interrupt => self.interrupt,
            Mos6502Flag::Zero => self.zero,
            Mos6502Flag::Carry => self.carry,
        }
    }

    pub fn as_byte(&self) -> u8 {
        let mut working = 0x00;

        if self.flag(Negative) {
            working += 1 << Negative.index();
        }
        if self.flag(Overflow) {
            working += 1 << Overflow.index();
        }
        working += 1 << 5; // Always Set
        if self.flag(Break) {
            working += 1 << Break.index();
        }
        if self.flag(Decimal) {
            working += 1 << Decimal.index();
        }
        if self.flag(Interrupt) {
            working += 1 << Interrupt.index();
        }
        if self.flag(Zero) {
            working += 1 << Zero.index();
        }
        if self.flag(Carry) {
            working += 1 << Carry.index();
        }

        working
    }

    pub fn from_byte(src: u8) -> Self {
        let mut working = Self::default();

        working.change_flag(Negative, Self::bit(src, Negative.index()));
        working.change_flag(Overflow, Self::bit(src, Overflow.index()));
        working.change_flag(Break, Self::bit(src, Break.index()));
        working.change_flag(Decimal, Self::bit(src, Decimal.index()));
        working.change_flag(Interrupt, Self::bit(src, Interrupt.index()));
        working.change_flag(Zero, Self::bit(src, Zero.index()));
        working.change_flag(Carry, Self::bit(src, Carry.index()));

        working
    }

    /// bit
    ///
    /// src -> Source byte to check in
    /// pos -> Which bit to check
    ///
    /// returns bool
    ///
    /// True if the bit is set.
    /// False if the bit is not set
    #[inline]
    fn bit(src: u8, pos: u8) -> bool {
        (src >> pos) & 1 != 0
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn smoke() {
        assert!(true);
    }

    #[test]
    fn sanity() {
        let f = Mos6502Flags::default();
        let magic_byte = 0b1110_1101;
        let magic_flags = Mos6502Flags {
            carry: true,
            zero: false,
            interrupt: true,
            decimal: true,
            break_flag: false,
            overflow: true,
            negative: true,
        };

        assert_eq!(magic_flags, Mos6502Flags::from_byte(magic_byte));
    }
}
