use std::fmt;

#[cfg(feature = "debugger")]
use crate::bit::BitIndex;

#[cfg(feature = "debugger")]
use super::{ArmFormat, ArmInstruction};

use super::{AluOpCode, ArmCond, ArmHalfwordTransferType};
use crate::arm7tdmi::*;

impl fmt::Display for ArmCond {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use ArmCond::*;
        match self {
            EQ => write!(f, "eq"),
            NE => write!(f, "ne"),
            HS => write!(f, "cs"),
            LO => write!(f, "cc"),
            MI => write!(f, "mi"),
            PL => write!(f, "pl"),
            VS => write!(f, "vs"),
            VC => write!(f, "vc"),
            HI => write!(f, "hi"),
            LS => write!(f, "ls"),
            GE => write!(f, "ge"),
            LT => write!(f, "lt"),
            GT => write!(f, "gt"),
            LE => write!(f, "le"),
            AL => write!(f, ""), // the dissasembly should ignore this
        }
    }
}

impl fmt::Display for AluOpCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use AluOpCode::*;
        match self {
            AND => write!(f, "and"),
            EOR => write!(f, "eor"),
            SUB => write!(f, "sub"),
            RSB => write!(f, "rsb"),
            ADD => write!(f, "add"),
            ADC => write!(f, "adc"),
            SBC => write!(f, "sbc"),
            RSC => write!(f, "rsc"),
            TST => write!(f, "tst"),
            TEQ => write!(f, "teq"),
            CMP => write!(f, "cmp"),
            CMN => write!(f, "cmn"),
            ORR => write!(f, "orr"),
            MOV => write!(f, "mov"),
            BIC => write!(f, "bic"),
            MVN => write!(f, "mvn"),
        }
    }
}

impl fmt::Display for BarrelShiftOpCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use BarrelShiftOpCode::*;
        match self {
            LSL => write!(f, "lsl"),
            LSR => write!(f, "lsr"),
            ASR => write!(f, "asr"),
            ROR => write!(f, "ror"),
        }
    }
}

impl fmt::Display for ArmHalfwordTransferType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use ArmHalfwordTransferType::*;
        match self {
            UnsignedHalfwords => write!(f, "h"),
            SignedHalfwords => write!(f, "sh"),
            SignedByte => write!(f, "sb"),
        }
    }
}

fn is_lsl0(shift: &ShiftedRegister) -> bool {
    if let ShiftRegisterBy::ByAmount(val) = shift.shift_by {
        return !(val == 0 && shift.bs_op == BarrelShiftOpCode::LSL);
    }
    true
}

impl fmt::Display for ShiftedRegister {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let reg = reg_string(self.reg).to_string();
        if !is_lsl0(&self) {
            write!(f, "{}", reg)
        } else {
            match self.shift_by {
                ShiftRegisterBy::ByAmount(imm) => write!(f, "{}, {} #{}", reg, self.bs_op, imm),
                ShiftRegisterBy::ByRegister(rs) => {
                    write!(f, "{}, {} {}", reg, self.bs_op, reg_string(rs))
                }
            }
        }
    }
}

#[cfg(feature = "debugger")]
impl ArmInstruction {
    fn fmt_bx(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "bx\t{Rn}", Rn = reg_string(self.rn()))
    }

    fn fmt_branch(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "b{link}{cond}\t{ofs:#x}",
            link = if self.link_flag() { "l" } else { "" },
            cond = self.cond(),
            ofs = 8 + self.pc.wrapping_add(self.branch_offset() as Addr)
        )
    }

    fn set_cond_mark(&self) -> &str {
        if self.set_cond_flag() {
            "s"
        } else {
            ""
        }
    }

    fn fmt_operand2(&self, f: &mut fmt::Formatter<'_>) -> Result<Option<u32>, fmt::Error> {
        let operand2 = self.operand2();
        match operand2 {
            BarrelShifterValue::RotatedImmediate(_, _) => {
                let value = operand2.decode_rotated_immediate().unwrap();
                write!(f, "#{}\t; {:#x}", value, value)?;
                Ok(Some(value as u32))
            }
            BarrelShifterValue::ShiftedRegister(shift) => {
                write!(f, "{}", shift)?;
                Ok(None)
            }
            _ => panic!("invalid operand2"),
        }
    }

    fn fmt_data_processing(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use AluOpCode::*;

        let opcode = self.opcode();

        match opcode {
            MOV | MVN => write!(
                f,
                "{opcode}{S}{cond}\t{Rd}, ",
                opcode = opcode,
                cond = self.cond(),
                S = self.set_cond_mark(),
                Rd = reg_string(self.rd())
            ),
            CMP | CMN | TEQ | TST => write!(
                f,
                "{opcode}{cond}\t{Rn}, ",
                opcode = opcode,
                cond = self.cond(),
                Rn = reg_string(self.rn())
            ),
            _ => write!(
                f,
                "{opcode}{S}{cond}\t{Rd}, {Rn}, ",
                opcode = opcode,
                cond = self.cond(),
                S = self.set_cond_mark(),
                Rd = reg_string(self.rd()),
                Rn = reg_string(self.rn())
            ),
        }?;

        self.fmt_operand2(f).unwrap();
        Ok(())
    }

    fn auto_incremenet_mark(&self) -> &str {
        if self.write_back_flag() {
            "!"
        } else {
            ""
        }
    }

    fn fmt_rn_offset(&self, f: &mut fmt::Formatter<'_>, offset: BarrelShifterValue) -> fmt::Result {
        write!(f, "[{Rn}", Rn = reg_string(self.rn()))?;
        let (ofs_string, comment) = match offset {
            BarrelShifterValue::ImmediateValue(value) => {
                let value_for_commnet = if self.rn() == REG_PC {
                    value + self.pc + 8 // account for pipelining
                } else {
                    value
                };
                (
                    format!("#{}", value),
                    Some(format!("\t; {:#x}", value_for_commnet)),
                )
            }
            BarrelShifterValue::ShiftedRegister(shift) => (
                format!(
                    "{}{}",
                    if shift.added.unwrap_or(true) { "" } else { "-" },
                    shift
                ),
                None,
            ),
            _ => panic!("bad barrel shifter"),
        };

        if self.pre_index_flag() {
            write!(f, ", {}]{}", ofs_string, self.auto_incremenet_mark())?;
        } else {
            write!(f, "], {}", ofs_string)?;
        }

        if let Some(comment) = comment {
            write!(f, "{}", comment)
        } else {
            Ok(())
        }
    }

    fn fmt_ldr_str(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{mnem}{B}{T}{cond}\t{Rd}, ",
            mnem = if self.load_flag() { "ldr" } else { "str" },
            B = if self.transfer_size() == 1 { "b" } else { "" },
            cond = self.cond(),
            T = if !self.pre_index_flag() && self.write_back_flag() {
                "t"
            } else {
                ""
            },
            Rd = reg_string(self.rd()),
        )?;

        self.fmt_rn_offset(f, self.ldr_str_offset())
    }

    fn fmt_ldm_stm(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{mnem}{inc_dec}{pre_post}{cond}\t{Rn}{auto_inc}, {{",
            mnem = if self.load_flag() { "ldm" } else { "stm" },
            inc_dec = if self.add_offset_flag() { 'i' } else { 'd' },
            pre_post = if self.pre_index_flag() { 'b' } else { 'a' },
            cond = self.cond(),
            Rn = reg_string(self.rn()),
            auto_inc = if self.write_back_flag() { "!" } else { "" }
        )?;

        let register_list = self.register_list();
        let mut has_first = false;
        for i in 0..16 {
            if register_list.bit(i) {
                if has_first {
                    write!(f, ", {}", reg_string(i))?;
                } else {
                    write!(f, "{}", reg_string(i))?;
                    has_first = true;
                }
            }
        }

        write!(
            f,
            "}}{}",
            if self.psr_and_force_user_flag() {
                "^"
            } else {
                ""
            }
        )
    }

    /// MRS - transfer PSR contents to a register
    fn fmt_mrs(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "mrs{cond}\t{Rd}, {psr}",
            cond = self.cond(),
            Rd = reg_string(self.rd()),
            psr = if self.spsr_flag() { "SPSR" } else { "CPSR" }
        )
    }

    /// MSR - transfer register/immediate contents to PSR
    fn fmt_msr(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "msr{cond}\t{psr}, ",
            cond = self.cond(),
            psr = if self.spsr_flag() { "SPSR" } else { "CPSR" },
        )?;
        self.fmt_operand2(f).unwrap();
        Ok(())
    }

    fn fmt_msr_flags(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "msr{cond}\t{psr}, ",
            cond = self.cond(),
            psr = if self.spsr_flag() { "SPSR_f" } else { "CPSR_f" },
        )?;
        if let Ok(Some(op)) = self.fmt_operand2(f) {
            let psr = RegPSR::new(op & 0xf000_0000);
            write!(
                f,
                "\t; N={} Z={} C={} V={}",
                psr.N(),
                psr.Z(),
                psr.C(),
                psr.V()
            )?;
        }
        Ok(())
    }

    fn fmt_mul_mla(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.accumulate_flag() {
            write!(
                f,
                "mla{S}{cond}\t{Rd}, {Rm}, {Rs}, {Rn}",
                S = self.set_cond_mark(),
                cond = self.cond(),
                Rd = reg_string(self.rd()),
                Rm = reg_string(self.rm()),
                Rs = reg_string(self.rs()),
                Rn = reg_string(self.rn()),
            )
        } else {
            write!(
                f,
                "mul{S}{cond}\t{Rd}, {Rm}, {Rs}",
                S = self.set_cond_mark(),
                cond = self.cond(),
                Rd = reg_string(self.rd()),
                Rm = reg_string(self.rm()),
                Rs = reg_string(self.rs()),
            )
        }
    }

    fn sign_mark(&self) -> &str {
        if self.u_flag() {
            "s"
        } else {
            "u"
        }
    }

    fn fmt_mull_mlal(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{sign}{mnem}{S}{cond}\t{RdLo}, {RdHi}, {Rm}, {Rs}",
            sign = self.sign_mark(),
            mnem = if self.accumulate_flag() {
                "mlal"
            } else {
                "mull"
            },
            S = self.set_cond_mark(),
            cond = self.cond(),
            RdLo = reg_string(self.rd_lo()),
            RdHi = reg_string(self.rd_hi()),
            Rm = reg_string(self.rm()),
            Rs = reg_string(self.rs()),
        )
    }

    fn fmt_ldr_str_hs(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Ok(transfer_type) = self.halfword_data_transfer_type() {
            write!(
                f,
                "{mnem}{type}{cond}\t{Rd}, ",
                mnem = if self.load_flag() { "ldr" } else { "str" },
                cond = self.cond(),
                type = transfer_type,
                Rd = reg_string(self.rd()),
            )?;
            self.fmt_rn_offset(f, self.ldr_str_hs_offset().unwrap())
        } else {
            write!(f, "<undefined>")
        }
    }

    fn fmt_swi(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "swi{cond}\t#{comm:#x}",
            cond = self.cond(),
            comm = self.swi_comment()
        )
    }

    fn fmt_swp(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "swp{B}{cond}\t{Rd}, {Rm}, [{Rn}]",
            B = if self.transfer_size() == 1 { "b" } else { "" },
            cond = self.cond(),
            Rd = reg_string(self.rd()),
            Rm = reg_string(self.rm()),
            Rn = reg_string(self.rn()),
        )
    }
}

#[cfg(feature = "debugger")]
impl fmt::Display for ArmInstruction {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use ArmFormat::*;
        match self.fmt {
            BranchExchange => self.fmt_bx(f),
            BranchLink => self.fmt_branch(f),
            DataProcessing => self.fmt_data_processing(f),
            SingleDataTransfer => self.fmt_ldr_str(f),
            BlockDataTransfer => self.fmt_ldm_stm(f),
            MoveFromStatus => self.fmt_mrs(f),
            MoveToStatus => self.fmt_msr(f),
            MoveToFlags => self.fmt_msr_flags(f),
            Multiply => self.fmt_mul_mla(f),
            MultiplyLong => self.fmt_mull_mlal(f),
            HalfwordDataTransferImmediateOffset => self.fmt_ldr_str_hs(f),
            HalfwordDataTransferRegOffset => self.fmt_ldr_str_hs(f),
            SoftwareInterrupt => self.fmt_swi(f),
            SingleDataSwap => self.fmt_swp(f),
            Undefined => write!(f, "<Undefined>"),
        }
    }
}
