use core::arch::asm;
use core::marker::PhantomData;

const HC_MAGIC: usize = 0x1337c0d3;

type ChannelId = u32;

#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum HcCmd {
    Start = 1,        /* start new action */
    Stop,             /* stop action */
    Read,             /* read buffer from hypervisor */
    Write,            /* write buffer TO hypervisor*/
    Error,            /* report error to hypervisor*/
    GetManager,       /* returns unique chanenl ID to manager from plugin */
    GetChannelByName, /* returns existing channel mapped to unique name */
}

pub fn start() -> bool {
    HyperCall::new(HcCmd::Start).call() == !HC_MAGIC
}

pub fn stop() {
    HyperCall::new(HcCmd::Stop).call();
}

pub struct HyperCall<'a> {
    cmd: HcCmd,
    cid: ChannelId,
    args: Vec<usize>,
    lifetime: PhantomData<&'a ()>,
}

impl HyperCall<'static> {
    pub fn new(cmd: HcCmd) -> Self {
        Self {
            cmd,
            cid: 0, // assume channel ID 0
            args: vec![0; 2],
            lifetime: PhantomData,
        }
    }
}

#[allow(dead_code, unused_unsafe)]
impl<'a> HyperCall<'a> {
    pub fn arg(&mut self, arg: usize) -> &mut Self {
        self.args.push(arg);
        self
    }

    pub fn from_string(command: HcCmd, channel_id: ChannelId, s: &'a str) -> HyperCall<'a> {
        Self {
            cmd: command,
            cid: channel_id,
            args: vec![s.as_ptr() as usize, s.len()],
            lifetime: PhantomData,
        }
    }

    pub fn from_buf(command: HcCmd, channel_id: ChannelId, buf: &'a [u8]) -> HyperCall<'a> {
        Self {
            cmd: command,
            cid: channel_id,
            args: vec![buf.as_ptr() as usize, buf.len()],
            lifetime: PhantomData,
        }
    }

    pub fn from_mut_buf(command: HcCmd, channel_id: ChannelId, buf: &'a mut [u8]) -> HyperCall<'a> {
        Self {
            cmd: command,
            cid: channel_id,
            args: vec![buf.as_ptr() as usize, buf.len()],
            lifetime: PhantomData,
        }
    }

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    #[inline(never)]
    pub fn call(&mut self) -> usize {
        let ret_val;

        while self.args.len() < 2 {
            self.args.push(0);
        }

        unsafe {
            asm!(
                "mov eax, 0x1337c0d3",
                "mov ebx, {command:e}",
                "cpuid",
                command = in(reg) self.cmd as u32,
                in("ecx") self.cid,
                in("edx") self.args[0],
                in("edi") self.args[1],
                out("eax") ret_val,
            );
        }

        ret_val
    }

    #[cfg(any(target_arch = "mips", target_arch = "mipsel"))]
    pub fn call(&mut self) -> usize {
        unsafe { self.mips_hypcall() }
    }

    #[cfg(any(target_arch = "mips", target_arch = "mipsel"))]
    #[inline(never)]
    unsafe fn mips_hypcall(&mut self) -> usize {
        let ret_val;

        while self.args.len() < 2 {
            self.args.push(0);
        }

        unsafe {
            asm!(
                "movz $0, $1, $2",

                /* v0 */ inout("$2") HC_MAGIC => ret_val,
                /* a0 */ in("$4") self.cmd as u32,
                /* a1 */ in("$5") self.cid,
                /* a2 */ in("$6") self.args[0],
                /* a3 */ in("$7") self.args[1],
            );
        }

        ret_val
    }

    #[cfg(target_arch = "arm")]
    #[inline(never)]
    pub fn call(&mut self) -> usize {
        let ret_val;

        while self.args.len() < 2 {
            self.args.push(0);
        }

        unsafe {
            asm!(
                "mcr p7, 0, r0, c0, c0, 0",

                inout("r0") HC_MAGIC => ret_val,
                in("r1") self.cmd as u32,
                in("r2") self.cid,
                in("r3") self.args[0],
                in("r4") self.args[1],
            );
        }

        ret_val
    }

    #[cfg(target_arch = "mips64")]
    #[inline(never)]
    pub fn call(&mut self) -> usize {
        todo!()
    }

    #[cfg(target_arch = "aarch64")]
    #[inline(never)]
    pub fn call(&mut self) -> usize {
        todo!()
    }
}
