use std::{path::PathBuf, process::Command};

use crate::{Architecture, IntoArgs};

#[derive(Debug)]
pub enum Machine {
    Virt { virtualize: bool },
    Raspi4b,
}

#[derive(Debug)]
pub enum Cpu {
    CortexA57,
    Max,
}

#[derive(Debug)]
pub enum Image {
    Kernel {
        kernel: PathBuf,
        initrd: Option<PathBuf>,
    },
}

#[derive(Debug)]
pub struct QemuAArch64;

impl IntoArgs for Machine {
    fn add_args(&self, command: &mut Command) {
        command.arg("-M");
        match self {
            &Self::Virt { virtualize } => {
                let mut arg = "virt".to_owned();
                if virtualize {
                    arg.push_str(",virtualization=on");
                }
                command.arg(arg);
            }
            &Self::Raspi4b => {
                command.arg("raspi4b");
            }
        }
    }
}

impl IntoArgs for Cpu {
    fn add_args(&self, command: &mut Command) {
        command.arg("-cpu");
        match self {
            Self::CortexA57 => {
                command.arg("cortex-a57");
            }
            Self::Max => {
                command.arg("max");
            }
        }
    }
}

impl IntoArgs for Image {
    fn add_args(&self, command: &mut Command) {
        match self {
            Self::Kernel { kernel, initrd } => {
                command.arg("-kernel").arg(kernel);
                if let Some(initrd) = initrd {
                    command.arg("-initrd").arg(initrd);
                }
            }
        }
    }
}

impl IntoArgs for QemuAArch64 {
    fn add_args(&self, _command: &mut Command) {}
}

impl Architecture for QemuAArch64 {
    type MachineType = Machine;
    type CpuType = Cpu;
    type ImageType = Image;

    const DEFAULT_COMMAND: &'static str = "qemu-system-aarch64";
}

#[cfg(test)]
mod tests {
    use crate::{aarch64::Machine, device::QemuSerialTarget, Qemu};

    use super::{Cpu, Image};

    #[test]
    fn boot_options() {
        let mut qemu = Qemu::new_aarch64();
        qemu.with_boot_image(Image::Kernel {
            kernel: "my-kernel.bin".into(),
            initrd: Some("my-initrd.img".into()),
        });
        let command = qemu.into_command();

        assert_eq!(
            command.get_args().collect::<Vec<_>>(),
            vec!["-kernel", "my-kernel.bin", "-initrd", "my-initrd.img"]
        );

        let mut qemu = Qemu::new_aarch64();
        qemu.with_boot_image(Image::Kernel {
            kernel: "my-kernel.bin".into(),
            initrd: None,
        });
        let command = qemu.into_command();

        assert_eq!(
            command.get_args().collect::<Vec<_>>(),
            vec!["-kernel", "my-kernel.bin"]
        );
    }

    #[test]
    fn machine_options() {
        let mut qemu = Qemu::new_aarch64();
        qemu.with_machine(Machine::Virt { virtualize: true });

        let command = qemu.into_command();

        assert_eq!(
            command.get_args().collect::<Vec<_>>(),
            vec!["-M", "virt,virtualization=on"]
        );
    }

    #[test]
    fn full_commands() {
        let mut qemu = Qemu::new_aarch64();
        qemu.override_qemu("my-qemu");
        qemu.with_boot_image(Image::Kernel {
            kernel: "my-kernel.bin".into(),
            initrd: Some("my-initrd.img".into()),
        });
        qemu.with_machine(Machine::Virt { virtualize: false });
        qemu.with_cpu(Cpu::CortexA57);
        qemu.with_serial(QemuSerialTarget::MonStdio);

        let command = qemu.into_command();

        assert_eq!(
            command.get_args().collect::<Vec<_>>(),
            vec![
                "-serial",
                "mon:stdio",
                "-M",
                "virt",
                "-cpu",
                "cortex-a57",
                "-kernel",
                "my-kernel.bin",
                "-initrd",
                "my-initrd.img"
            ]
        );
    }
}
