use gdbstub::common::Pid;
use gdbstub::target;
use gdbstub::target::ext::extended_mode::{Args, ShouldTerminate};
use gdbstub::target::TargetResult;

use crate::emu::Emu;

/*=====================================
=            Extended Mode            =
=====================================*/

// This is a stub implementation of GDB's Extended Mode extensions.
//
// Truth be told, this particular emulator is _not_ very well suited to running
// in extended mode, as it doesn't technically spawn/attach to any process.
// Nonetheless, it's useful to have a stubbed implementation in-tree which can
// be used for basic usability / regression testing.
//
// If you happen to implement a "proper" extended mode gdbstub, feel free to
// file an issue / open a PR that links to your project!

impl target::ext::extended_mode::ExtendedMode for Emu {
    fn kill(&mut self, pid: Option<Pid>) -> TargetResult<ShouldTerminate, Self> {
        eprintln!("GDB sent a kill request for pid {:?}", pid);
        Ok(ShouldTerminate::No)
    }

    fn restart(&mut self) -> Result<(), Self::Error> {
        eprintln!("GDB sent a restart request");
        Ok(())
    }

    fn attach(&mut self, pid: Pid) -> TargetResult<(), Self> {
        eprintln!("GDB tried to attach to a process with PID {}", pid);
        Err(().into()) // non-specific failure
    }

    fn run(&mut self, filename: Option<&[u8]>, args: Args) -> TargetResult<Pid, Self> {
        // simplified example: assume UTF-8 filenames / args
        //
        // To be 100% pedantically correct, consider converting to an `OsStr` in the
        // least lossy way possible (e.g: using the `from_bytes` extension from
        // `std::os::unix::ffi::OsStrExt`).

        let filename = match filename {
            None => None,
            Some(raw) => Some(core::str::from_utf8(raw).map_err(drop)?),
        };
        let args = args
            .map(|raw| core::str::from_utf8(raw).map_err(drop))
            .collect::<Result<Vec<_>, _>>()?;

        eprintln!(
            "GDB tried to run a new process with filename {:?}, and args {:?}",
            filename, args
        );

        self.reset();

        // when running in single-threaded mode, this PID can be anything
        Ok(Pid::new(1337).unwrap())
    }

    fn configure_aslr(&mut self) -> Option<target::ext::extended_mode::ConfigureASLROps<Self>> {
        Some(self)
    }

    fn configure_env(&mut self) -> Option<target::ext::extended_mode::ConfigureEnvOps<Self>> {
        Some(self)
    }

    fn configure_startup_shell(
        &mut self,
    ) -> Option<target::ext::extended_mode::ConfigureStartupShellOps<Self>> {
        Some(self)
    }

    fn configure_working_dir(
        &mut self,
    ) -> Option<target::ext::extended_mode::ConfigureWorkingDirOps<Self>> {
        Some(self)
    }
}

impl target::ext::extended_mode::ConfigureASLR for Emu {
    fn cfg_aslr(&mut self, enabled: bool) -> TargetResult<(), Self> {
        eprintln!("GDB {} ASLR", if enabled { "enabled" } else { "disabled" });
        Ok(())
    }
}

impl target::ext::extended_mode::ConfigureEnv for Emu {
    fn set_env(&mut self, key: &[u8], val: Option<&[u8]>) -> TargetResult<(), Self> {
        // simplified example: assume UTF-8 key/val env vars
        let key = core::str::from_utf8(key).map_err(drop)?;
        let val = match val {
            None => None,
            Some(raw) => Some(core::str::from_utf8(raw).map_err(drop)?),
        };

        eprintln!("GDB tried to set a new env var: {:?}={:?}", key, val);

        Ok(())
    }

    fn remove_env(&mut self, key: &[u8]) -> TargetResult<(), Self> {
        let key = core::str::from_utf8(key).map_err(drop)?;
        eprintln!("GDB tried to set remove a env var: {:?}", key);

        Ok(())
    }

    fn reset_env(&mut self) -> TargetResult<(), Self> {
        eprintln!("GDB tried to reset env vars");

        Ok(())
    }
}

impl target::ext::extended_mode::ConfigureStartupShell for Emu {
    fn cfg_startup_with_shell(&mut self, enabled: bool) -> TargetResult<(), Self> {
        eprintln!(
            "GDB {} startup with shell",
            if enabled { "enabled" } else { "disabled" }
        );
        Ok(())
    }
}

impl target::ext::extended_mode::ConfigureWorkingDir for Emu {
    fn cfg_working_dir(&mut self, dir: Option<&[u8]>) -> TargetResult<(), Self> {
        let dir = match dir {
            None => None,
            Some(raw) => Some(core::str::from_utf8(raw).map_err(drop)?),
        };

        match dir {
            None => eprintln!("GDB reset the working directory"),
            Some(dir) => eprintln!("GDB set the working directory to {:?}", dir),
        }

        Ok(())
    }
}
