use std::fmt::{self, Display};

use iced_x86::{Decoder, DecoderOptions, Formatter, GasFormatter, Instruction};
use yggdrasil_abi::arch::SavedFrame;

use crate::{debugger::SymbolResolver, InstructionFormatter, Target};

#[cfg(any(target_pointer_width = "32", rust_analyzer))]
const BITNESS: u32 = 32;
#[cfg(any(target_pointer_width = "64", rust_analyzer))]
const BITNESS: u32 = 64;

pub struct FlagsDisplay(usize);

pub struct TargetImpl;

pub struct NoopFormatter;

impl fmt::Display for FlagsDisplay {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        const FLAGS: &[(usize, &str)] = &[
            (0, "CF"),
            (2, "PF"),
            (4, "AF"),
            (5, "ZF"),
            (6, "SF"),
            (9, "IF"),
            (10, "DF"),
            (11, "OF"),
        ];
        write!(f, " {: <#8x} [ ", self.0)?;
        for (bit, flag) in FLAGS {
            if self.0 & (1 << bit) != 0 {
                write!(f, "{} ", flag)?;
            }
        }
        write!(f, "]")
    }
}

impl InstructionFormatter<Instruction> for GasFormatter {
    fn format_instruction(&mut self, insn: &Instruction, out: &mut String) {
        self.format(insn, out)
    }
}

impl iced_x86::SymbolResolver for SymbolResolver {
    fn symbol(
        &mut self,
        _instruction: &Instruction,
        _operand: u32,
        _instruction_operand: Option<u32>,
        address: u64,
        _address_size: u32,
    ) -> Option<iced_x86::SymbolResult<'_>> {
        let image_addr = self.to_image_address(address)?;
        let (symbol, offset) = self.resolve_symbol(image_addr)?;

        let string = if offset == 0 {
            format!("{:#x} <{}>", address, symbol)
        } else {
            format!("{:#x} <{}+{:#x}>", address, symbol, offset)
        };

        Some(iced_x86::SymbolResult::with_string(address, string))
    }
}

impl Target for TargetImpl {
    type Instruction = Instruction;
    type InstructionFormatter = GasFormatter;
    type Register = usize;

    fn disassemble(
        window: &[u8],
        ip: usize,
        limit: usize,
    ) -> Result<Vec<(usize, Self::Instruction)>, crate::Error> {
        let mut instructions = vec![];
        let mut decoder = Decoder::with_ip(BITNESS, window, ip as _, DecoderOptions::NONE);

        while decoder.can_decode() && instructions.len() < limit {
            let insn = decoder.decode();
            instructions.push((insn.ip() as usize, insn));
        }

        Ok(instructions)
    }

    fn new_instruction_formatter(resolver: SymbolResolver) -> Self::InstructionFormatter {
        let mut formatter = GasFormatter::with_options(Some(Box::new(resolver)), None);
        formatter.options_mut().set_uppercase_hex(false);
        formatter.options_mut().set_branch_leading_zeros(false);
        formatter
    }

    fn register_list(frame: &SavedFrame, out: &mut Vec<(String, Self::Register)>) {
        #[cfg(any(target_arch = "x86", rust_analyzer))]
        {
            out.push(("eax".into(), frame.eax as _));
            out.push(("ecx".into(), frame.ecx as _));
            out.push(("edx".into(), frame.edx as _));
            out.push(("ebx".into(), frame.ebx as _));
            out.push(("esp".into(), frame.user_sp as _));
            out.push(("ebp".into(), frame.ebp as _));
            out.push(("esi".into(), frame.esi as _));
            out.push(("edi".into(), frame.edi as _));
        }
        #[cfg(any(target_arch = "x86_64", rust_analyzer))]
        {
            out.push(("rax".into(), frame.rax as _));
            out.push(("rcx".into(), frame.rcx as _));
            out.push(("rdx".into(), frame.rdx as _));
            out.push(("rbx".into(), frame.rbx as _));
            out.push(("rdi".into(), frame.rdi as _));
            out.push(("rsi".into(), frame.rsi as _));
            out.push(("rsp".into(), frame.user_sp as _));
            out.push(("rbp".into(), frame.rbp as _));
            out.push(("r8".into(), frame.r8 as _));
            out.push(("r9".into(), frame.r9 as _));
            out.push(("r10".into(), frame.r10 as _));
            out.push(("r11".into(), frame.r11 as _));
            out.push(("r12".into(), frame.r12 as _));
            out.push(("r13".into(), frame.r13 as _));
            out.push(("r14".into(), frame.r14 as _));
            out.push(("r15".into(), frame.r15 as _));
        }
    }

    fn flags_register_as_display(frame: &SavedFrame) -> impl Display {
        #[cfg(any(target_arch = "x86_64", rust_analyzer))]
        {
            FlagsDisplay(frame.rflags as _)
        }
        #[cfg(any(target_arch = "x86", rust_analyzer))]
        {
            FlagsDisplay(frame.eflags as _)
        }
    }

    fn real_ip(frame: &SavedFrame) -> usize {
        frame.user_ip as _
    }
}
