use bit::BitIndex;

use super::{Core, CpuError, CpuResult, REG_PC};

#[derive(Debug, Primitive, PartialEq)]
pub enum AluOpCode {
    AND = 0b0000,
    EOR = 0b0001,
    SUB = 0b0010,
    RSB = 0b0011,
    ADD = 0b0100,
    ADC = 0b0101,
    SBC = 0b0110,
    RSC = 0b0111,
    TST = 0b1000,
    TEQ = 0b1001,
    CMP = 0b1010,
    CMN = 0b1011,
    ORR = 0b1100,
    MOV = 0b1101,
    BIC = 0b1110,
    MVN = 0b1111,
}

impl AluOpCode {
    pub fn is_setting_flags(&self) -> bool {
        use AluOpCode::*;
        match self {
            TST | TEQ | CMP | CMN => true,
            _ => false,
        }
    }

    pub fn is_logical(&self) -> bool {
        use AluOpCode::*;
        match self {
            MOV | MVN | ORR | EOR | AND | BIC | TST | TEQ => true,
            _ => false,
        }
    }
    pub fn is_arithmetic(&self) -> bool {
        use AluOpCode::*;
        match self {
            ADD | ADC | SUB | SBC | RSB | RSC | CMP | CMN => true,
            _ => false,
        }
    }
}

#[derive(Debug, PartialEq, Primitive)]
pub enum BarrelShiftOpCode {
    LSL = 0,
    LSR = 1,
    ASR = 2,
    ROR = 3,
}

#[derive(Debug, PartialEq)]
pub enum ShiftRegisterBy {
    ByAmount(u32),
    ByRegister(usize),
}

#[derive(Debug, PartialEq)]
pub struct ShiftedRegister {
    pub reg: usize,
    pub shift_by: ShiftRegisterBy,
    pub bs_op: BarrelShiftOpCode,
    pub added: Option<bool>,
}

#[derive(Debug, PartialEq)]
pub enum BarrelShifterValue {
    ImmediateValue(u32),
    RotatedImmediate(u32, u32),
    ShiftedRegister(ShiftedRegister),
}

impl BarrelShifterValue {
    /// Decode operand2 as an immediate value
    pub fn decode_rotated_immediate(&self) -> Option<u32> {
        if let BarrelShifterValue::RotatedImmediate(immediate, rotate) = self {
            return Some(immediate.rotate_right(*rotate) as u32);
        }
        None
    }
    pub fn shifted_register(
        reg: usize,
        shift_by: ShiftRegisterBy,
        bs_op: BarrelShiftOpCode,
        added: Option<bool>,
    ) -> BarrelShifterValue {
        let shft_reg = ShiftedRegister {
            reg,
            shift_by,
            bs_op,
            added,
        };
        BarrelShifterValue::ShiftedRegister(shft_reg)
    }
}

impl Core {
    pub fn lsl(&mut self, val: u32, amount: u32, carry_in: bool) -> u32 {
        match amount {
            0 => {
                self.bs_carry_out = carry_in;
                val
            }
            x if x < 32 => {
                self.bs_carry_out = val.wrapping_shr(32 - x) & 1 == 1;
                val << x
            }
            32 => {
                self.bs_carry_out = val & 1 == 1;
                0
            }
            _ => {
                self.bs_carry_out = false;
                0
            }
        }
    }

    pub fn lsr(&mut self, val: u32, amount: u32, carry_in: bool, immediate: bool) -> u32 {
        if amount != 0 {
            match amount {
                x if x < 32 => {
                    self.bs_carry_out = (val >> (amount - 1) & 1) == 1;
                    val >> amount
                }
                _ => {
                    self.bs_carry_out = false;
                    0
                }
            }
        } else if immediate {
            self.bs_carry_out = val.bit(31);
            0
        } else {
            self.bs_carry_out = carry_in;
            val
        }
    }

    pub fn asr(&mut self, val: u32, amount: u32, carry_in: bool, immediate: bool) -> u32 {
        let amount = if immediate && amount == 0 { 32 } else { amount };
        match amount {
            0 => {
                self.bs_carry_out = carry_in;
                val
            }
            x if x < 32 => {
                self.bs_carry_out = val.wrapping_shr(amount - 1) & 1 == 1;
                (val as i32).wrapping_shr(amount) as u32
            }
            _ => {
                let bit31 = val.bit(31);
                self.bs_carry_out = bit31;
                if bit31 {
                    0xffffffff
                } else {
                    0
                }
            }
        }
    }

    pub fn rrx(&mut self, val: u32, carry_in: bool) -> u32 {
        let old_c = carry_in as i32;
        self.bs_carry_out = val & 0b1 != 0;
        (((val as u32) >> 1) as i32 | (old_c << 31)) as u32
    }

    pub fn ror(
        &mut self,
        val: u32,
        amount: u32,
        carry_in: bool,
        immediate: bool,
        rrx: bool,
    ) -> u32 {
        match amount {
            0 => {
                if immediate & rrx {
                    self.rrx(val, carry_in)
                } else {
                    val
                }
            }
            _ => {
                let amount = amount % 32;
                let val = if amount != 0 {
                    val.rotate_right(amount)
                } else {
                    val
                };
                self.bs_carry_out = (val as u32).bit(31);
                val
            }
        }
    }

    /// Performs a generic barrel shifter operation
    pub fn barrel_shift_op(
        &mut self,
        shift: BarrelShiftOpCode,
        val: u32,
        amount: u32,
        carry_in: bool,
        immediate: bool,
    ) -> u32 {
        //
        // From GBATEK:
        // Zero Shift Amount (Shift Register by Immediate, with Immediate=0)
        //  LSL#0: No shift performed, ie. directly Op2=Rm, the C flag is NOT affected.
        //  LSR#0: Interpreted as LSR#32, ie. Op2 becomes zero, C becomes Bit 31 of Rm.
        //  ASR#0: Interpreted as ASR#32, ie. Op2 and C are filled by Bit 31 of Rm.
        //  ROR#0: Interpreted as RRX#1 (RCR), like ROR#1, but Op2 Bit 31 set to old C.
        //
        // From ARM7TDMI Datasheet:
        // 1 LSL by 32 has result zero, carry out equal to bit 0 of Rm.
        // 2 LSL by more than 32 has result zero, carry out zero.
        // 3 LSR by 32 has result zero, carry out equal to bit 31 of Rm.
        // 4 LSR by more than 32 has result zero, carry out zero.
        // 5 ASR by 32 or more has result filled with and carry out equal to bit 31 of Rm.
        // 6 ROR by 32 has result equal to Rm, carry out equal to bit 31 of Rm.
        // 7 ROR by n where n is greater than 32 will give the same result and carry out
        //   as ROR by n-32; therefore repeatedly subtract 32 from n until the amount is
        //   in the range 1 to 32 and see above.
        //
        match shift {
            BarrelShiftOpCode::LSL => self.lsl(val, amount, carry_in),
            BarrelShiftOpCode::LSR => self.lsr(val, amount, carry_in, immediate),
            BarrelShiftOpCode::ASR => self.asr(val, amount, carry_in, immediate),
            BarrelShiftOpCode::ROR => self.ror(val, amount, carry_in, immediate, true),
        }
    }

    pub fn register_shift(&mut self, shift: ShiftedRegister) -> CpuResult<u32> {
        let mut val = self.get_reg(shift.reg);
        let carry = self.cpsr.C();
        match shift.shift_by {
            ShiftRegisterBy::ByAmount(amount) => {
                let result = self.barrel_shift_op(shift.bs_op, val, amount, carry, true);
                Ok(result)
            }
            ShiftRegisterBy::ByRegister(rs) => {
                self.add_cycle(); // +1I
                if shift.reg == REG_PC {
                    val = val + 4; // PC prefetching
                }
                if rs != REG_PC {
                    let amount = self.get_reg(rs) & 0xff;
                    let result = self.barrel_shift_op(shift.bs_op, val, amount, carry, false);
                    Ok(result)
                } else {
                    Err(CpuError::IllegalInstruction)
                }
            }
        }
    }

    pub fn get_barrel_shifted_value(&mut self, sval: BarrelShifterValue) -> u32 {
        // TODO decide if error handling or panic here
        match sval {
            BarrelShifterValue::ImmediateValue(offset) => offset as u32,
            BarrelShifterValue::ShiftedRegister(shifted_reg) => {
                let added = shifted_reg.added.unwrap_or(true);
                let abs = self.register_shift(shifted_reg).unwrap() as u32;
                if added {
                    abs as u32
                } else {
                    (-(abs as i32)) as u32
                }
            }
            _ => panic!("bad barrel shift"),
        }
    }

    pub(super) fn alu_sub_flags(
        &self,
        a: u32,
        b: u32,
        carry: &mut bool,
        overflow: &mut bool,
    ) -> u32 {
        let res = a.wrapping_sub(b);
        *carry = b <= a;
        *overflow = (a as i32).overflowing_sub(b as i32).1;
        res
    }

    pub(super) fn alu_add_flags(
        &self,
        a: u32,
        b: u32,
        carry: &mut bool,
        overflow: &mut bool,
    ) -> u32 {
        let res = a.wrapping_add(b);
        *carry = add_carry_result(a as u64, b as u64);
        *overflow = (a as i32).overflowing_add(b as i32).1;
        res
    }

    pub(super) fn alu_adc_flags(
        &self,
        a: u32,
        b: u32,
        carry: &mut bool,
        overflow: &mut bool,
    ) -> u32 {
        let c = self.cpsr.C() as u64;
        let res = (a as u64) + (b as u64) + c;
        *carry = res > 0xffffffff;
        *overflow = (!(a ^ b) & (b ^ (res as u32))).bit(31);
        res as u32
    }

    pub(super) fn alu_sbc_flags(
        &self,
        a: u32,
        b: u32,
        carry: &mut bool,
        overflow: &mut bool,
    ) -> u32 {
        self.alu_adc_flags(a, !b, carry, overflow)
    }

    pub fn alu_update_flags(&mut self, result: u32, is_arithmetic: bool, c: bool, v: bool) {
        self.cpsr.set_N((result as i32) < 0);
        self.cpsr.set_Z(result == 0);
        self.cpsr.set_C(c);
        self.cpsr.set_V(v);
    }
}

#[inline]
fn add_carry_result(a: u64, b: u64) -> bool {
    a.wrapping_add(b) > 0xffffffff
}