use core::marker::PhantomData;

#[cfg(feature = "alloc")]
use alloc::collections::BTreeMap;

use managed::ManagedSlice;

use crate::common::*;
use crate::{
    arch::{Arch, RegId, Registers},
    connection::Connection,
    internal::*,
    protocol::{
        commands::{ext, Command},
        ConsoleOutput, IdKind, Packet, ResponseWriter, ThreadId,
    },
    target::ext::base::multithread::{Actions, ResumeAction, ThreadStopReason, TidSelector},
    target::ext::base::BaseOps,
    target::Target,
    util::managed_vec::ManagedVec,
    FAKE_PID, SINGLE_THREAD_TID,
};

mod builder;
mod error;
mod target_result_ext;

pub use builder::{GdbStubBuilder, GdbStubBuilderError};
pub use error::GdbStubError;

use target_result_ext::TargetResultExt;

use GdbStubError as Error;

/// Describes why the GDB session ended.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DisconnectReason {
    /// Target Halted
    TargetHalted,
    /// GDB issued a disconnect command
    Disconnect,
    /// GDB issued a kill command
    Kill,
}

/// Debug a [`Target`] using the GDB Remote Serial Protocol over a given
/// [`Connection`].
pub struct GdbStub<'a, T: Target, C: Connection> {
    conn: C,
    packet_buffer: ManagedSlice<'a, u8>,
    state: GdbStubImpl<T, C>,
}

impl<'a, T: Target, C: Connection> GdbStub<'a, T, C> {
    /// Create a [`GdbStubBuilder`] using the provided Connection.
    pub fn builder(conn: C) -> GdbStubBuilder<'a, T, C> {
        GdbStubBuilder::new(conn)
    }

    /// Create a new `GdbStub` using the provided connection.
    ///
    /// For fine-grained control over various `GdbStub` options, use the
    /// [`builder()`](GdbStub::builder) method instead.
    ///
    /// _Note:_ `new` is only available when the `alloc` feature is enabled.
    #[cfg(feature = "alloc")]
    pub fn new(conn: C) -> GdbStub<'a, T, C> {
        GdbStubBuilder::new(conn).build().unwrap()
    }

    /// Starts a GDB remote debugging session.
    ///
    /// Returns once the GDB client closes the debugging session, or if the
    /// target halts.
    pub fn run(&mut self, target: &mut T) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
        self.state
            .run(target, &mut self.conn, &mut self.packet_buffer)
    }
}

struct GdbStubImpl<T: Target, C: Connection> {
    _target: PhantomData<T>,
    _connection: PhantomData<C>,

    packet_buffer_len: usize,
    current_mem_tid: Tid,
    current_resume_tid: TidSelector,
    no_ack_mode: bool,

    // Used to track which Pids were attached to / spawned when running in extended mode.
    //
    // An empty `BTreeMap<Pid, bool>` is only 24 bytes (on 64-bit systems), and doesn't allocate
    // until the first element is inserted, so it should be fine to include it as part of the main
    // state structure whether or not extended mode is actually being used.
    #[cfg(feature = "alloc")]
    attached_pids: BTreeMap<Pid, bool>,
}

enum HandlerStatus {
    Handled,
    NeedsOK,
    Disconnect(DisconnectReason),
}

impl<T: Target, C: Connection> GdbStubImpl<T, C> {
    fn new(packet_buffer_len: usize) -> GdbStubImpl<T, C> {
        GdbStubImpl {
            _target: PhantomData,
            _connection: PhantomData,

            packet_buffer_len,
            // HACK: current_mem_tid is immediately updated with valid value once `run` is called.
            // While the more idiomatic way to handle this would be to use an Option, given that
            // it's only ever unset prior to the start of `run`, it's probably okay leaving it as-is
            // for code-clarity purposes.
            current_mem_tid: SINGLE_THREAD_TID,
            current_resume_tid: TidSelector::All,
            no_ack_mode: false,

            #[cfg(feature = "alloc")]
            attached_pids: BTreeMap::new(),
        }
    }

    fn run(
        &mut self,
        target: &mut T,
        conn: &mut C,
        packet_buffer: &mut ManagedSlice<u8>,
    ) -> Result<DisconnectReason, Error<T::Error, C::Error>> {
        conn.on_session_start().map_err(Error::ConnectionRead)?;

        // before even accepting packets, we query the target to get a sane value for
        // `self.current_mem_tid`.
        // NOTE: this will break if extended mode is ever implemented...

        self.current_mem_tid = match target.base_ops() {
            BaseOps::SingleThread(_) => SINGLE_THREAD_TID,
            BaseOps::MultiThread(ops) => {
                let mut first_tid = None;
                ops.list_active_threads(&mut |tid| {
                    if first_tid.is_none() {
                        first_tid = Some(tid);
                    }
                })
                .map_err(Error::TargetError)?;
                first_tid.ok_or(Error::NoActiveThreads)?
            }
        };

        loop {
            match Self::recv_packet(conn, target, packet_buffer)? {
                Packet::Ack => {}
                Packet::Nack => return Err(Error::ClientSentNack),
                Packet::Interrupt => {
                    debug!("<-- interrupt packet");
                    let mut res = ResponseWriter::new(conn);
                    res.write_str("S05")?;
                    res.flush()?;
                }
                Packet::Command(command) => {
                    // Acknowledge the command
                    if !self.no_ack_mode {
                        conn.write(b'+').map_err(Error::ConnectionRead)?;
                    }

                    let mut res = ResponseWriter::new(conn);
                    let disconnect = match self.handle_command(&mut res, target, command) {
                        Ok(HandlerStatus::Handled) => None,
                        Ok(HandlerStatus::NeedsOK) => {
                            res.write_str("OK")?;
                            None
                        }
                        Ok(HandlerStatus::Disconnect(reason)) => Some(reason),
                        // HACK: handling this "dummy" error is required as part of the
                        // `TargetResultExt::handle_error()` machinery.
                        Err(Error::NonFatalError(code)) => {
                            res.write_str("E")?;
                            res.write_num(code)?;
                            None
                        }
                        Err(Error::TargetError(e)) => {
                            // unlike all other errors which are "unrecoverable" in the sense that
                            // the GDB session cannot continue, there's still a chance that a target
                            // might want to keep the debugging session alive to do a "post-mortem"
                            // analysis. As such, we simply report a standard TRAP stop reason.
                            let mut res = ResponseWriter::new(conn);
                            res.write_str("S05")?;
                            res.flush()?;
                            return Err(Error::TargetError(e));
                        }
                        Err(e) => return Err(e),
                    };

                    // HACK: this could be more elegant...
                    if disconnect != Some(DisconnectReason::Kill) {
                        res.flush()?;
                    }

                    if let Some(disconnect_reason) = disconnect {
                        return Ok(disconnect_reason);
                    }
                }
            };
        }
    }

    fn recv_packet<'a>(
        conn: &mut C,
        target: &mut T,
        pkt_buf: &'a mut ManagedSlice<u8>,
    ) -> Result<Packet<'a>, Error<T::Error, C::Error>> {
        let header_byte = conn.read().map_err(Error::ConnectionRead)?;

        // Wrap the buf in a `ManagedVec` to keep the code readable.
        let mut buf = ManagedVec::new(pkt_buf);

        buf.clear();
        buf.push(header_byte)?;
        if header_byte == b'$' {
            // read the packet body
            loop {
                let c = conn.read().map_err(Error::ConnectionRead)?;
                buf.push(c)?;
                if c == b'#' {
                    break;
                }
            }
            // read the checksum as well
            buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
            buf.push(conn.read().map_err(Error::ConnectionRead)?)?;
        }

        match Packet::from_buf(target, pkt_buf.as_mut()) {
            Ok(packet) => Ok(packet),
            Err(e) => Err(Error::PacketParse(e)),
        }
    }

    fn handle_command(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        cmd: Command<'_>,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        match cmd {
            Command::Unknown(cmd) => {
                info!("Unknown command: {}", cmd);
                Ok(HandlerStatus::Handled)
            }
            Command::Base(cmd) => self.handle_base(res, target, cmd),
            Command::ExtendedMode(cmd) => self.handle_extended_mode(res, target, cmd),
            Command::MonitorCmd(cmd) => self.handle_monitor_cmd(res, target, cmd),
            Command::SectionOffsets(cmd) => self.handle_section_offsets(res, target, cmd),
        }
    }

    fn handle_base<'a>(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        command: ext::Base<'a>,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        let handler_status = match command {
            // ------------------ Handshaking and Queries ------------------- //
            ext::Base::qSupported(cmd) => {
                // XXX: actually read what the client supports, and enable/disable features
                // appropriately
                let _features = cmd.features.into_iter();

                res.write_str("PacketSize=")?;
                res.write_num(self.packet_buffer_len)?;

                res.write_str(";vContSupported+")?;
                res.write_str(";multiprocess+")?;
                res.write_str(";QStartNoAckMode+")?;

                if let Some(ops) = target.extended_mode() {
                    if ops.configure_aslr().is_some() {
                        res.write_str(";QDisableRandomization+")?;
                    }

                    if ops.configure_env().is_some() {
                        res.write_str(";QEnvironmentHexEncoded+")?;
                        res.write_str(";QEnvironmentUnset+")?;
                        res.write_str(";QEnvironmentReset+")?;
                    }

                    if ops.configure_startup_shell().is_some() {
                        res.write_str(";QStartupWithShell+")?;
                    }

                    if ops.configure_working_dir().is_some() {
                        res.write_str(";QSetWorkingDir+")?;
                    }
                }

                res.write_str(";swbreak+")?;
                if target.hw_breakpoint().is_some() || target.hw_watchpoint().is_some() {
                    res.write_str(";hwbreak+")?;
                }

                // TODO: implement conditional breakpoint support (since that's kool).
                // res.write_str("ConditionalBreakpoints+;")?;

                if T::Arch::target_description_xml().is_some() {
                    res.write_str(";qXfer:features:read+")?;
                }

                HandlerStatus::Handled
            }
            ext::Base::QStartNoAckMode(_) => {
                self.no_ack_mode = true;
                HandlerStatus::NeedsOK
            }
            ext::Base::qXferFeaturesRead(cmd) => {
                match T::Arch::target_description_xml() {
                    Some(xml) => {
                        let xml = xml.trim();
                        if cmd.offset >= xml.len() {
                            // no more data
                            res.write_str("l")?;
                        } else if cmd.offset + cmd.len >= xml.len() {
                            // last little bit of data
                            res.write_str("l")?;
                            res.write_binary(&xml.as_bytes()[cmd.offset..])?
                        } else {
                            // still more data
                            res.write_str("m")?;
                            res.write_binary(&xml.as_bytes()[cmd.offset..(cmd.offset + cmd.len)])?
                        }
                    }
                    // If the target hasn't provided their own XML, then the initial response to
                    // "qSupported" wouldn't have included  "qXfer:features:read", and gdb wouldn't
                    // send this packet unless it was explicitly marked as supported.
                    None => return Err(Error::PacketUnexpected),
                }
                HandlerStatus::Handled
            }

            // -------------------- "Core" Functionality -------------------- //
            // TODO: Improve the '?' response based on last-sent stop reason.
            ext::Base::QuestionMark(_) => {
                res.write_str("S05")?;
                HandlerStatus::Handled
            }
            ext::Base::qAttached(cmd) => {
                let is_attached = match target.extended_mode() {
                    // when _not_ running in extended mode, just report that we're attaching to an
                    // existing process.
                    None => true, // assume attached to an existing process
                    // When running in extended mode, we must defer to the target
                    Some(ops) => {
                        let pid: Pid = cmd.pid.ok_or(Error::PacketUnexpected)?;

                        #[cfg(feature = "alloc")]
                        {
                            let _ = ops; // doesn't actually query the target
                            *self.attached_pids.get(&pid).unwrap_or(&true)
                        }

                        #[cfg(not(feature = "alloc"))]
                        {
                            ops.query_if_attached(pid).handle_error()?.was_attached()
                        }
                    }
                };
                res.write_str(if is_attached { "1" } else { "0" })?;
                HandlerStatus::Handled
            }
            ext::Base::g(_) => {
                let mut regs: <T::Arch as Arch>::Registers = Default::default();
                match target.base_ops() {
                    BaseOps::SingleThread(ops) => ops.read_registers(&mut regs),
                    BaseOps::MultiThread(ops) => {
                        ops.read_registers(&mut regs, self.current_mem_tid)
                    }
                }
                .handle_error()?;

                let mut err = Ok(());
                regs.gdb_serialize(|val| {
                    let res = match val {
                        Some(b) => res.write_hex_buf(&[b]),
                        None => res.write_str("xx"),
                    };
                    if let Err(e) = res {
                        err = Err(e);
                    }
                });
                err?;
                HandlerStatus::Handled
            }
            ext::Base::G(cmd) => {
                let mut regs: <T::Arch as Arch>::Registers = Default::default();
                regs.gdb_deserialize(cmd.vals)
                    .map_err(|_| Error::TargetMismatch)?;

                match target.base_ops() {
                    BaseOps::SingleThread(ops) => ops.write_registers(&regs),
                    BaseOps::MultiThread(ops) => ops.write_registers(&regs, self.current_mem_tid),
                }
                .handle_error()?;

                HandlerStatus::NeedsOK
            }
            ext::Base::m(cmd) => {
                let buf = cmd.buf;
                let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
                    .ok_or(Error::TargetMismatch)?;

                let mut i = 0;
                let mut n = cmd.len;
                while n != 0 {
                    let chunk_size = n.min(buf.len());

                    use num_traits::NumCast;

                    let addr = addr + NumCast::from(i).ok_or(Error::TargetMismatch)?;
                    let data = &mut buf[..chunk_size];
                    match target.base_ops() {
                        BaseOps::SingleThread(ops) => ops.read_addrs(addr, data),
                        BaseOps::MultiThread(ops) => {
                            ops.read_addrs(addr, data, self.current_mem_tid)
                        }
                    }
                    .handle_error()?;

                    n -= chunk_size;
                    i += chunk_size;

                    res.write_hex_buf(data)?;
                }
                HandlerStatus::Handled
            }
            ext::Base::M(cmd) => {
                let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
                    .ok_or(Error::TargetMismatch)?;

                match target.base_ops() {
                    BaseOps::SingleThread(ops) => ops.write_addrs(addr, cmd.val),
                    BaseOps::MultiThread(ops) => {
                        ops.write_addrs(addr, cmd.val, self.current_mem_tid)
                    }
                }
                .handle_error()?;

                HandlerStatus::NeedsOK
            }
            ext::Base::k(_) | ext::Base::vKill(_) => {
                match target.extended_mode() {
                    // When not running in extended mode, stop the `GdbStub` and disconnect.
                    None => HandlerStatus::Disconnect(DisconnectReason::Kill),

                    // When running in extended mode, a kill command does not necessarily result in
                    // a disconnect...
                    Some(ops) => {
                        let pid = match command {
                            ext::Base::vKill(cmd) => Some(cmd.pid),
                            _ => None,
                        };

                        let should_terminate = ops.kill(pid).handle_error()?;
                        if should_terminate.into() {
                            // manually write OK, since we need to return a DisconnectReason
                            res.write_str("OK")?;
                            HandlerStatus::Disconnect(DisconnectReason::Kill)
                        } else {
                            HandlerStatus::NeedsOK
                        }
                    }
                }
            }
            ext::Base::D(_) => {
                // TODO: plumb-through Pid when exposing full multiprocess + extended mode
                res.write_str("OK")?; // manually write OK, since we need to return a DisconnectReason
                HandlerStatus::Disconnect(DisconnectReason::Disconnect)
            }
            ext::Base::Z(cmd) => {
                let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
                    .ok_or(Error::TargetMismatch)?;

                use crate::target::ext::breakpoints::WatchKind::*;
                let supported = match cmd.type_ {
                    0 => (target.sw_breakpoint()).map(|op| op.add_sw_breakpoint(addr)),
                    1 => (target.hw_breakpoint()).map(|op| op.add_hw_breakpoint(addr)),
                    2 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Write)),
                    3 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, Read)),
                    4 => (target.hw_watchpoint()).map(|op| op.add_hw_watchpoint(addr, ReadWrite)),
                    // only 5 types in the protocol
                    _ => None,
                };

                match supported {
                    None => HandlerStatus::Handled,
                    Some(Err(e)) => {
                        Err(e).handle_error()?;
                        HandlerStatus::Handled
                    }
                    Some(Ok(true)) => HandlerStatus::NeedsOK,
                    Some(Ok(false)) => return Err(Error::NonFatalError(22)),
                }
            }
            ext::Base::z(cmd) => {
                let addr = <T::Arch as Arch>::Usize::from_be_bytes(cmd.addr)
                    .ok_or(Error::TargetMismatch)?;

                use crate::target::ext::breakpoints::WatchKind::*;
                let supported = match cmd.type_ {
                    0 => (target.sw_breakpoint()).map(|op| op.remove_sw_breakpoint(addr)),
                    1 => (target.hw_breakpoint()).map(|op| op.remove_hw_breakpoint(addr)),
                    2 => (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Write)),
                    3 => (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, Read)),
                    4 => {
                        (target.hw_watchpoint()).map(|op| op.remove_hw_watchpoint(addr, ReadWrite))
                    }
                    // only 5 types in the protocol
                    _ => None,
                };

                match supported {
                    None => HandlerStatus::Handled,
                    Some(Err(e)) => {
                        Err(e).handle_error()?;
                        HandlerStatus::Handled
                    }
                    Some(Ok(true)) => HandlerStatus::NeedsOK,
                    Some(Ok(false)) => return Err(Error::NonFatalError(22)),
                }
            }
            ext::Base::p(p) => {
                let mut dst = [0u8; 32]; // enough for 256-bit registers
                let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
                let (reg_id, reg_size) = match reg {
                    Some(v) => v,
                    // empty packet indicates unrecognized query
                    None => return Ok(HandlerStatus::Handled),
                };
                let dst = &mut dst[0..reg_size];
                match target.base_ops() {
                    BaseOps::SingleThread(ops) => ops.read_register(reg_id, dst),
                    BaseOps::MultiThread(ops) => {
                        ops.read_register(reg_id, dst, self.current_mem_tid)
                    }
                }
                .handle_error()?;

                res.write_hex_buf(dst)?;
                HandlerStatus::Handled
            }
            ext::Base::P(p) => {
                let reg = <T::Arch as Arch>::RegId::from_raw_id(p.reg_id);
                match reg {
                    None => return Err(Error::NonFatalError(22)),
                    Some((reg_id, _)) => match target.base_ops() {
                        BaseOps::SingleThread(ops) => ops.write_register(reg_id, p.val),
                        BaseOps::MultiThread(ops) => {
                            ops.write_register(reg_id, p.val, self.current_mem_tid)
                        }
                    }
                    .handle_error()?,
                }
                HandlerStatus::NeedsOK
            }
            ext::Base::vCont(cmd) => {
                use crate::protocol::commands::_vCont::{vCont, VContKind};

                let actions = match cmd {
                    vCont::Query => {
                        res.write_str("vCont;c;C;s;S")?;
                        return Ok(HandlerStatus::Handled);
                    }
                    vCont::Actions(actions) => actions,
                };

                // map raw vCont action iterator to a format the `Target` expects
                let mut err = Ok(());
                let mut actions = actions.into_iter().filter_map(|action| {
                    let action = match action {
                        Some(action) => action,
                        None => {
                            err = Err(Error::PacketParse(
                                crate::protocol::PacketParseError::MalformedCommand,
                            ));
                            return None;
                        }
                    };

                    let resume_action = match action.kind {
                        VContKind::Step => ResumeAction::Step,
                        VContKind::Continue => ResumeAction::Continue,
                        _ => {
                            // there seems to be a GDB bug where it doesn't use `vCont` unless
                            // `vCont?` returns support for resuming with a signal.
                            //
                            // This error case can be removed once "Resume with Signal" is
                            // implemented
                            err = Err(Error::ResumeWithSignalUnimplemented);
                            return None;
                        }
                    };

                    let tid = match action.thread {
                        Some(thread) => match thread.tid {
                            IdKind::Any => {
                                err = Err(Error::PacketUnexpected);
                                return None;
                            }
                            IdKind::All => TidSelector::All,
                            IdKind::WithID(tid) => TidSelector::WithID(tid),
                        },
                        // An action with no thread-id matches all threads
                        None => TidSelector::All,
                    };

                    Some((tid, resume_action))
                });

                let ret = match self.do_vcont(res, target, &mut actions) {
                    Ok(None) => HandlerStatus::Handled,
                    Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
                    Err(e) => return Err(e),
                };
                err?;
                ret
            }
            // TODO?: support custom resume addr in 'c' and 's'
            ext::Base::c(_) => {
                match self.do_vcont(
                    res,
                    target,
                    &mut core::iter::once((self.current_resume_tid, ResumeAction::Continue)),
                ) {
                    Ok(None) => HandlerStatus::Handled,
                    Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
                    Err(e) => return Err(e),
                }
            }
            ext::Base::s(_) => {
                match self.do_vcont(
                    res,
                    target,
                    &mut core::iter::once((self.current_resume_tid, ResumeAction::Step)),
                ) {
                    Ok(None) => HandlerStatus::Handled,
                    Ok(Some(dc)) => HandlerStatus::Disconnect(dc),
                    Err(e) => return Err(e),
                }
            }

            // ------------------- Multi-threading Support ------------------ //
            ext::Base::H(cmd) => {
                use crate::protocol::commands::_h_upcase::Op;
                match cmd.kind {
                    Op::Other => match cmd.thread.tid {
                        IdKind::Any => {} // reuse old tid
                        // "All" threads doesn't make sense for memory accesses
                        IdKind::All => return Err(Error::PacketUnexpected),
                        IdKind::WithID(tid) => self.current_mem_tid = tid,
                    },
                    // technically, this variant is deprecated in favor of vCont...
                    Op::StepContinue => match cmd.thread.tid {
                        IdKind::Any => {} // reuse old tid
                        IdKind::All => self.current_resume_tid = TidSelector::All,
                        IdKind::WithID(tid) => self.current_resume_tid = TidSelector::WithID(tid),
                    },
                }
                HandlerStatus::NeedsOK
            }
            ext::Base::qfThreadInfo(_) => {
                res.write_str("m")?;

                match target.base_ops() {
                    BaseOps::SingleThread(_) => res.write_thread_id(ThreadId {
                        pid: Some(IdKind::WithID(FAKE_PID)),
                        tid: IdKind::WithID(SINGLE_THREAD_TID),
                    })?,
                    BaseOps::MultiThread(ops) => {
                        let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
                        let mut first = true;
                        ops.list_active_threads(&mut |tid| {
                            // TODO: replace this with a try block (once stabilized)
                            let e = (|| {
                                if !first {
                                    res.write_str(",")?
                                }
                                first = false;
                                res.write_thread_id(ThreadId {
                                    pid: Some(IdKind::WithID(FAKE_PID)),
                                    tid: IdKind::WithID(tid),
                                })?;
                                Ok(())
                            })();

                            if let Err(e) = e {
                                err = Err(e)
                            }
                        })
                        .map_err(Error::TargetError)?;
                        err?;
                    }
                }

                HandlerStatus::Handled
            }
            ext::Base::qsThreadInfo(_) => {
                res.write_str("l")?;
                HandlerStatus::Handled
            }
            ext::Base::T(cmd) => {
                let alive = match cmd.thread.tid {
                    IdKind::WithID(tid) => match target.base_ops() {
                        BaseOps::SingleThread(_) => tid == SINGLE_THREAD_TID,
                        BaseOps::MultiThread(ops) => {
                            ops.is_thread_alive(tid).map_err(Error::TargetError)?
                        }
                    },
                    // TODO: double-check if GDB ever sends other variants
                    // Even after ample testing, this arm has never been hit...
                    _ => return Err(Error::PacketUnexpected),
                };
                if alive {
                    HandlerStatus::NeedsOK
                } else {
                    // any error code will do
                    return Err(Error::NonFatalError(1));
                }
            }
        };
        Ok(handler_status)
    }

    fn handle_monitor_cmd<'a>(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        command: ext::MonitorCmd<'a>,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        let ops = match target.monitor_cmd() {
            Some(ops) => ops,
            None => return Ok(HandlerStatus::Handled),
        };

        let handler_status = match command {
            ext::MonitorCmd::qRcmd(cmd) => {
                crate::__dead_code_marker!("qRcmd", "impl");

                let mut err: Result<_, Error<T::Error, C::Error>> = Ok(());
                let mut callback = |msg: &[u8]| {
                    // TODO: replace this with a try block (once stabilized)
                    let e = (|| {
                        let mut res = ResponseWriter::new(res.as_conn());
                        res.write_str("O")?;
                        res.write_hex_buf(msg)?;
                        res.flush()?;
                        Ok(())
                    })();

                    if let Err(e) = e {
                        err = Err(e)
                    }
                };

                ops.handle_monitor_cmd(cmd.hex_cmd, ConsoleOutput::new(&mut callback))
                    .map_err(Error::TargetError)?;
                err?;

                HandlerStatus::NeedsOK
            }
        };

        Ok(handler_status)
    }

    fn handle_section_offsets(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        command: ext::SectionOffsets,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        let ops = match target.section_offsets() {
            Some(ops) => ops,
            None => return Ok(HandlerStatus::Handled),
        };

        let handler_status = match command {
            ext::SectionOffsets::qOffsets(_cmd) => {
                use crate::target::ext::section_offsets::Offsets;

                crate::__dead_code_marker!("qOffsets", "impl");

                match ops.get_section_offsets().map_err(Error::TargetError)? {
                    Offsets::Sections { text, data, bss } => {
                        res.write_str("Text=")?;
                        res.write_num(text)?;

                        res.write_str(";Data=")?;
                        res.write_num(data)?;

                        // "Note: while a Bss offset may be included in the response,
                        // GDB ignores this and instead applies the Data offset to the Bss section."
                        //
                        // While this would suggest that it's OK to omit `Bss=` entirely, recent
                        // versions of GDB seem to require that `Bss=` is present.
                        //
                        // See https://github.com/bminor/binutils-gdb/blob/master/gdb/remote.c#L4149-L4159
                        let bss = bss.unwrap_or(data);
                        res.write_str(";Bss=")?;
                        res.write_num(bss)?;
                    }
                    Offsets::Segments { text_seg, data_seg } => {
                        res.write_str("TextSeg=")?;
                        res.write_num(text_seg)?;

                        if let Some(data) = data_seg {
                            res.write_str(";DataSeg=")?;
                            res.write_num(data)?;
                        }
                    }
                }
                HandlerStatus::Handled
            }
        };

        Ok(handler_status)
    }

    fn handle_extended_mode<'a>(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        command: ext::ExtendedMode<'a>,
    ) -> Result<HandlerStatus, Error<T::Error, C::Error>> {
        let ops = match target.extended_mode() {
            Some(ops) => ops,
            None => return Ok(HandlerStatus::Handled),
        };

        let handler_status = match command {
            ext::ExtendedMode::ExclamationMark(_cmd) => {
                ops.on_start().map_err(Error::TargetError)?;
                HandlerStatus::NeedsOK
            }
            ext::ExtendedMode::R(_cmd) => {
                ops.restart().map_err(Error::TargetError)?;
                HandlerStatus::Handled
            }
            ext::ExtendedMode::vAttach(cmd) => {
                ops.attach(cmd.pid).handle_error()?;

                #[cfg(feature = "alloc")]
                self.attached_pids.insert(cmd.pid, true);

                // TODO: sends OK when running in Non-Stop mode
                HandlerStatus::Handled
            }
            ext::ExtendedMode::vRun(cmd) => {
                use crate::target::ext::extended_mode::Args;

                let mut pid = ops
                    .run(cmd.filename, Args::new(&mut cmd.args.into_iter()))
                    .handle_error()?;

                // on single-threaded systems, we'll ignore the provided PID and keep
                // using the FAKE_PID.
                if let BaseOps::SingleThread(_) = target.base_ops() {
                    pid = FAKE_PID;
                }

                let _ = pid; // squelch warning on no_std targets
                #[cfg(feature = "alloc")]
                self.attached_pids.insert(pid, false);

                // TODO: send a more descriptive stop packet?
                res.write_str("S05")?;
                HandlerStatus::Handled
            }
            // --------- ASLR --------- //
            ext::ExtendedMode::QDisableRandomization(cmd) if ops.configure_aslr().is_some() => {
                let ops = ops.configure_aslr().unwrap();
                ops.cfg_aslr(cmd.value).handle_error()?;
                HandlerStatus::NeedsOK
            }
            // --------- Environment --------- //
            ext::ExtendedMode::QEnvironmentHexEncoded(cmd) if ops.configure_env().is_some() => {
                let ops = ops.configure_env().unwrap();
                ops.set_env(cmd.key, cmd.value).handle_error()?;
                HandlerStatus::NeedsOK
            }
            ext::ExtendedMode::QEnvironmentUnset(cmd) if ops.configure_env().is_some() => {
                let ops = ops.configure_env().unwrap();
                ops.remove_env(cmd.key).handle_error()?;
                HandlerStatus::NeedsOK
            }
            ext::ExtendedMode::QEnvironmentReset(_cmd) if ops.configure_env().is_some() => {
                let ops = ops.configure_env().unwrap();
                ops.reset_env().handle_error()?;
                HandlerStatus::NeedsOK
            }
            // --------- Working Dir --------- //
            ext::ExtendedMode::QSetWorkingDir(cmd) if ops.configure_working_dir().is_some() => {
                let ops = ops.configure_working_dir().unwrap();
                ops.cfg_working_dir(cmd.dir).handle_error()?;
                HandlerStatus::NeedsOK
            }
            // --------- Startup Shell --------- //
            ext::ExtendedMode::QStartupWithShell(cmd)
                if ops.configure_startup_shell().is_some() =>
            {
                let ops = ops.configure_startup_shell().unwrap();
                ops.cfg_startup_with_shell(cmd.value).handle_error()?;
                HandlerStatus::NeedsOK
            }
            _ => HandlerStatus::Handled,
        };

        Ok(handler_status)
    }

    fn do_vcont(
        &mut self,
        res: &mut ResponseWriter<C>,
        target: &mut T,
        actions: &mut dyn Iterator<Item = (TidSelector, ResumeAction)>,
    ) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
        let mut err = Ok(());

        let mut check_gdb_interrupt = || match res.as_conn().peek() {
            Ok(Some(0x03)) => true, // 0x03 is the interrupt byte
            Ok(Some(_)) => false,   // it's nothing that can't wait...
            Ok(None) => false,
            Err(e) => {
                err = Err(Error::ConnectionRead(e));
                true // break ASAP if a connection error occurred
            }
        };

        let stop_reason = match target.base_ops() {
            BaseOps::SingleThread(ops) => ops
                .resume(
                    // TODO?: add a more descriptive error if vcont has multiple threads in
                    // single-threaded mode?
                    actions.next().ok_or(Error::PacketUnexpected)?.1,
                    &mut check_gdb_interrupt,
                )
                .map_err(Error::TargetError)?
                .into(),
            BaseOps::MultiThread(ops) => ops
                .resume(Actions::new(actions), &mut check_gdb_interrupt)
                .map_err(Error::TargetError)?,
        };

        err?;

        self.finish_vcont(stop_reason, res)
    }

    // DEVNOTE: `do_vcont` and `finish_vcont` could be merged into a single
    // function, at the expense of slightly larger code. In the future, if the
    // `vCont` machinery is re-written, there's no reason why the two functions
    // couldn't be re-merged.

    fn finish_vcont(
        &mut self,
        stop_reason: ThreadStopReason<<T::Arch as Arch>::Usize>,
        res: &mut ResponseWriter<C>,
    ) -> Result<Option<DisconnectReason>, Error<T::Error, C::Error>> {
        match stop_reason {
            ThreadStopReason::DoneStep | ThreadStopReason::GdbInterrupt => {
                res.write_str("S05")?;
                Ok(None)
            }
            ThreadStopReason::Signal(code) => {
                res.write_str("S")?;
                res.write_num(code)?;
                Ok(None)
            }
            ThreadStopReason::Halted => {
                res.write_str("W19")?; // SIGSTOP
                Ok(Some(DisconnectReason::TargetHalted))
            }
            ThreadStopReason::SwBreak(tid)
            | ThreadStopReason::HwBreak(tid)
            | ThreadStopReason::Watch { tid, .. } => {
                self.current_mem_tid = tid;
                self.current_resume_tid = TidSelector::WithID(tid);

                res.write_str("T05")?;

                res.write_str("thread:")?;
                res.write_thread_id(ThreadId {
                    pid: Some(IdKind::WithID(FAKE_PID)),
                    tid: IdKind::WithID(tid),
                })?;
                res.write_str(";")?;

                match stop_reason {
                    // don't include addr on sw/hw break
                    ThreadStopReason::SwBreak(_) => res.write_str("swbreak:")?,
                    ThreadStopReason::HwBreak(_) => res.write_str("hwbreak:")?,
                    ThreadStopReason::Watch { kind, addr, .. } => {
                        use crate::target::ext::breakpoints::WatchKind;
                        match kind {
                            WatchKind::Write => res.write_str("watch:")?,
                            WatchKind::Read => res.write_str("rwatch:")?,
                            WatchKind::ReadWrite => res.write_str("awatch:")?,
                        }
                        res.write_num(addr)?;
                    }
                    _ => unreachable!(),
                };

                res.write_str(";")?;
                Ok(None)
            }
        }
    }
}

use crate::target::ext::base::singlethread::StopReason;
impl<U> From<StopReason<U>> for ThreadStopReason<U> {
    fn from(st_stop_reason: StopReason<U>) -> ThreadStopReason<U> {
        match st_stop_reason {
            StopReason::DoneStep => ThreadStopReason::DoneStep,
            StopReason::GdbInterrupt => ThreadStopReason::GdbInterrupt,
            StopReason::Halted => ThreadStopReason::Halted,
            StopReason::SwBreak => ThreadStopReason::SwBreak(SINGLE_THREAD_TID),
            StopReason::HwBreak => ThreadStopReason::HwBreak(SINGLE_THREAD_TID),
            StopReason::Watch { kind, addr } => ThreadStopReason::Watch {
                tid: SINGLE_THREAD_TID,
                kind,
                addr,
            },
            StopReason::Signal(sig) => ThreadStopReason::Signal(sig),
        }
    }
}
