use super::prelude::*;
use crate::arch::lldb::Encoding;
use crate::arch::lldb::Format;
use crate::arch::lldb::Generic;
use crate::arch::lldb::Register;
use crate::arch::lldb::RegisterInfo as LLDBRegisterInfo;
use crate::arch::Arch;
use crate::protocol::commands::ext::LldbRegisterInfo;

impl<T: Target, C: Connection> GdbStubImpl<T, C> {
    pub(crate) fn handle_lldb_register_info(
        &mut self,
        res: &mut ResponseWriter<'_, C>,
        target: &mut T,
        command: LldbRegisterInfo,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        if !target.use_lldb_register_info() {
            return Ok(HandlerStatus::Handled);
        }

        let handler_status = match command {
            LldbRegisterInfo::qRegisterInfo(cmd) => {
                let mut err = Ok(());
                let cb = &mut |reg: Option<Register<'_>>| {
                    let res = match reg {
                        // TODO: replace this with a try block (once stabilized)
                        Some(reg) => (|| {
                            res.write_str("name:")?;
                            res.write_str(reg.name)?;
                            if let Some(alt_name) = reg.alt_name {
                                res.write_str(";alt-name:")?;
                                res.write_str(alt_name)?;
                            }
                            res.write_str(";bitsize:")?;
                            res.write_dec(reg.bitsize)?;
                            res.write_str(";offset:")?;
                            res.write_dec(reg.offset)?;
                            res.write_str(";encoding:")?;
                            res.write_str(match reg.encoding {
                                Encoding::Uint => "uint",
                                Encoding::Sint => "sint",
                                Encoding::IEEE754 => "ieee754",
                                Encoding::Vector => "vector",
                            })?;
                            res.write_str(";format:")?;
                            res.write_str(match reg.format {
                                Format::Binary => "binary",
                                Format::Decimal => "decimal",
                                Format::Hex => "hex",
                                Format::Float => "float",
                                Format::VectorSInt8 => "vector-sint8",
                                Format::VectorUInt8 => "vector-uint8",
                                Format::VectorSInt16 => "vector-sint16",
                                Format::VectorUInt16 => "vector-uint16",
                                Format::VectorSInt32 => "vector-sint32",
                                Format::VectorUInt32 => "vector-uint32",
                                Format::VectorFloat32 => "vector-float32",
                                Format::VectorUInt128 => "vector-uint128",
                            })?;
                            res.write_str(";set:")?;
                            res.write_str(reg.set)?;
                            if let Some(gcc) = reg.gcc {
                                res.write_str(";gcc:")?;
                                res.write_dec(gcc)?;
                            }
                            if let Some(dwarf) = reg.dwarf {
                                res.write_str(";dwarf:")?;
                                res.write_dec(dwarf)?;
                            }
                            if let Some(generic) = reg.generic {
                                res.write_str(";generic:")?;
                                res.write_str(match generic {
                                    Generic::Pc => "pc",
                                    Generic::Sp => "sp",
                                    Generic::Fp => "fp",
                                    Generic::Ra => "ra",
                                    Generic::Flags => "flags",
                                    Generic::Arg1 => "arg1",
                                    Generic::Arg2 => "arg2",
                                    Generic::Arg3 => "arg3",
                                    Generic::Arg4 => "arg4",
                                    Generic::Arg5 => "arg5",
                                    Generic::Arg6 => "arg6",
                                    Generic::Arg7 => "arg7",
                                    Generic::Arg8 => "arg8",
                                })?;
                            }
                            if let Some(c_regs) = reg.container_regs {
                                res.write_str(";container-regs:")?;
                                res.write_num(c_regs[0])?;
                                for reg in c_regs.iter().skip(1) {
                                    res.write_str(",")?;
                                    res.write_num(*reg)?;
                                }
                            }
                            if let Some(i_regs) = reg.invalidate_regs {
                                res.write_str(";invalidate-regs:")?;
                                res.write_num(i_regs[0])?;
                                for reg in i_regs.iter().skip(1) {
                                    res.write_str(",")?;
                                    res.write_num(*reg)?;
                                }
                            }
                            res.write_str(";")
                        })(),
                        // In fact, this doesn't has to be E45! It could equally well be any
                        // other error code or even an eOk, eAck or eNack! It turns out that
                        // 0x45 == 69, so presumably the LLDB people were just having some fun
                        // here. For a little discussion on this and LLDB source code pointers,
                        // see https://github.com/daniel5151/gdbstub/pull/103#discussion_r888590197
                        _ => res.write_str("E45"),
                    };
                    if let Err(e) = res {
                        err = Err(e);
                    }
                };
                if let Some(ops) = target.support_lldb_register_info_override() {
                    use crate::target::ext::lldb_register_info_override::Callback;
                    use crate::target::ext::lldb_register_info_override::CallbackToken;

                    ops.lldb_register_info(
                        cmd.reg_id,
                        Callback {
                            cb,
                            token: CallbackToken(core::marker::PhantomData),
                        },
                    )
                    .map_err(Error::TargetError)?;
                    err?;
                } else if let Some(reg) = T::Arch::lldb_register_info(cmd.reg_id) {
                    match reg {
                        LLDBRegisterInfo::Register(reg) => cb(Some(reg)),
                        LLDBRegisterInfo::Done => cb(None),
                    };
                }
                HandlerStatus::Handled
            }
        };

        Ok(handler_status)
    }
}
