use super::vmcs::{VmcsFields, VmxExitReason};
use super::vmx_asm_wrapper::{vmx_vmread, vmx_vmwrite};

use crate::virt::kvm::vm;
use core::arch::asm;
use log::debug;
use system_error::SystemError;
use x86::vmx::vmcs::ro::GUEST_PHYSICAL_ADDR_FULL;

#[derive(FromPrimitive)]
#[allow(non_camel_case_types)]
pub enum APICExceptionVectors {
    EXCEPTION_DIVIDE_ERROR,
    EXCEPTION_DEBUG_BREAKPOINT,
    EXCEPTION_NMI,
    EXCEPTION_BREAKPOINT,
    EXCEPTION_OVERFLOW,
    EXCEPTION_BOUND_RANGE_EXCEEDED,
    EXCEPTION_UNDEFINED_OPCODE,
    EXCEPTION_NO_MATH_COPROCESSOR,
    EXCEPTION_DOUBLE_FAULT,
    EXCEPTION_RESERVED0,
    EXCEPTION_INVALID_TASK_SEGMENT_SELECTOR,
    EXCEPTION_SEGMENT_NOT_PRESENT,
    EXCEPTION_STACK_SEGMENT_FAULT,
    EXCEPTION_GENERAL_PROTECTION_FAULT,
    EXCEPTION_PAGE_FAULT,
    EXCEPTION_RESERVED1,
    EXCEPTION_MATH_FAULT,
    EXCEPTION_ALIGNMENT_CHECK,
    EXCEPTION_MACHINE_CHECK,
    EXCEPTION_SIMD_FLOATING_POINT_NUMERIC_ERROR,
    EXCEPTION_VIRTUAL_EXCEPTION,
    EXCEPTION_RESERVED2,
    EXCEPTION_RESERVED3,
    EXCEPTION_RESERVED4,
    EXCEPTION_RESERVED5,
    EXCEPTION_RESERVED6,
    EXCEPTION_RESERVED7,
    EXCEPTION_RESERVED8,
    EXCEPTION_RESERVED9,
    EXCEPTION_RESERVED10,
    EXCEPTION_RESERVED11,
    EXCEPTION_RESERVED12,
}

#[derive(FromPrimitive)]
#[allow(non_camel_case_types)]
pub enum InterruptType {
    INTERRUPT_TYPE_EXTERNAL_INTERRUPT = 0,
    INTERRUPT_TYPE_RESERVED = 1,
    INTERRUPT_TYPE_NMI = 2,
    INTERRUPT_TYPE_HARDWARE_EXCEPTION = 3,
    INTERRUPT_TYPE_SOFTWARE_INTERRUPT = 4,
    INTERRUPT_TYPE_PRIVILEGED_SOFTWARE_INTERRUPT = 5,
    INTERRUPT_TYPE_SOFTWARE_EXCEPTION = 6,
    INTERRUPT_TYPE_OTHER_EVENT = 7,
}

pub fn vmexit_vmx_instruction_executed() -> Result<(), SystemError> {
    let valid: u32 = 1;
    let vector: u32 = APICExceptionVectors::EXCEPTION_UNDEFINED_OPCODE as u32;
    let interrupt_type = InterruptType::INTERRUPT_TYPE_HARDWARE_EXCEPTION as u32;
    let deliver_code: u32 = 0;
    let interrupt_info = valid << 31 | interrupt_type << 8 | deliver_code << 11 | vector;
    vmx_vmwrite(
        VmcsFields::CTRL_VM_ENTRY_INTR_INFO_FIELD as u32,
        interrupt_info as u64,
    )?;
    vmx_vmwrite(VmcsFields::CTRL_VM_ENTRY_INSTR_LEN as u32, 0)?;
    let rflags: u64 = vmx_vmread(VmcsFields::GUEST_RFLAGS as u32).unwrap() | 0x0001_0000; // set RF flags
    vmx_vmwrite(VmcsFields::GUEST_RFLAGS as u32, rflags)?;
    Ok(())
}

// pub fn vmexit_cpuid_handler(guest_cpu_context: &mut GuestCpuContext) -> Result<(), SystemError>{
//     let rax = guest_cpu_context.rax;
//     let rcx = guest_cpu_context.rcx;
//     // let rdx = guest_cpu_context.rdx;
//     // let rbx = guest_cpu_context.rbx;
//     cpuid!(rax, rcx);
//     unsafe{asm!("mov {}, rax", out(reg) guest_cpu_context.rax)};
//     unsafe{asm!("mov {}, rcx", out(reg) guest_cpu_context.rcx)};
//     unsafe{asm!("mov {}, rdx", out(reg) guest_cpu_context.rdx)};
//     unsafe{asm!("mov {}, rbx", out(reg) guest_cpu_context.rbx)};
//     Ok(())
// }

unsafe fn save_rpg() {
    asm!(
        "push    rax",
        "push    rcx",
        "push    rdx",
        "push    rbx",
        "push    rbp",
        "push    rsi",
        "push    rdi",
        "push    r8",
        "push    r9",
        "push    r10",
        "push    r11",
        "push    r12",
        "push    r13",
        "push    r14",
        "push    r15",
    );
}

unsafe fn restore_rpg() {
    asm!(
        "pop    r15",
        "pop    r14",
        "pop    r13",
        "pop    r12",
        "pop    r11",
        "pop    r10",
        "pop    r9",
        "pop    r8",
        "pop    rdi",
        "pop    rsi",
        "pop    rbp",
        "pop    rbx",
        "pop    rdx",
        "pop    rcx",
        "pop    rax",
    );
}

#[repr(C)]
#[allow(dead_code)]
pub struct GuestCpuContext {
    pub r15: u64,
    pub r14: u64,
    pub r13: u64,
    pub r12: u64,
    pub r11: u64,
    pub r10: u64,
    pub r9: u64,
    pub r8: u64,
    pub rdi: u64,
    pub rsi: u64,
    pub rbp: u64,
    pub rbx: u64,
    pub rdx: u64,
    pub rcx: u64,
    pub rax: u64,
}

#[no_mangle]
pub extern "C" fn vmx_return() {
    debug!("vmx_return!");
    unsafe { save_rpg() };
    vmexit_handler();
    // XMM registers are vector registers. They're renamed onto the FP/SIMD register file
    // unsafe {asm!(
    //     "sub     rsp, 60h",
    //     "movaps  xmmword ptr [rsp +  0h], xmm0",
    //     "movaps  xmmword ptr [rsp + 10h], xmm1",
    //     "movaps  xmmword ptr [rsp + 20h], xmm2",
    //     "movaps  xmmword ptr [rsp + 30h], xmm3",
    //     "movaps  xmmword ptr [rsp + 40h], xmm4",
    //     "movaps  xmmword ptr [rsp + 50h], xmm5",

    //     "mov     rdi, rsp",
    //     "sub     rsp, 20h",
    //     "call vmexit_handler",
    //     "add     rsp, 20h",

    //     "movaps  xmm0, xmmword ptr [rsp +  0h]",
    //     "movaps  xmm1, xmmword ptr [rsp + 10h]",
    //     "movaps  xmm2, xmmword ptr [rsp + 20h]",
    //     "movaps  xmm3, xmmword ptr [rsp + 30h]",
    //     "movaps  xmm4, xmmword ptr [rsp + 40h]",
    //     "movaps  xmm5, xmmword ptr [rsp + 50h]",
    //     "add     rsp, 60h",
    // clobber_abi("C"),
    // )};
    unsafe { restore_rpg() };
    unsafe { asm!("vmresume",) };
}

#[no_mangle]
extern "C" fn vmexit_handler() {
    // let guest_cpu_context = unsafe { guest_cpu_context_ptr.as_mut().unwrap() };
    // debug!("guest_cpu_context_ptr={:p}",guest_cpu_context_ptr);
    debug!("vmexit handler!");

    let exit_reason = vmx_vmread(VmcsFields::VMEXIT_EXIT_REASON as u32).unwrap() as u32;
    let exit_basic_reason = exit_reason & 0x0000_ffff;
    let guest_rip = vmx_vmread(VmcsFields::GUEST_RIP as u32).unwrap();
    // let guest_rsp = vmx_vmread(VmcsFields::GUEST_RSP as u32).unwrap();
    debug!("guest_rip={:x}", guest_rip);
    let _guest_rflags = vmx_vmread(VmcsFields::GUEST_RFLAGS as u32).unwrap();

    match VmxExitReason::from(exit_basic_reason as i32) {
        VmxExitReason::VMCALL
        | VmxExitReason::VMCLEAR
        | VmxExitReason::VMLAUNCH
        | VmxExitReason::VMPTRLD
        | VmxExitReason::VMPTRST
        | VmxExitReason::VMREAD
        | VmxExitReason::VMRESUME
        | VmxExitReason::VMWRITE
        | VmxExitReason::VMXOFF
        | VmxExitReason::VMXON
        | VmxExitReason::VMFUNC
        | VmxExitReason::INVEPT
        | VmxExitReason::INVVPID => {
            debug!("vmexit handler: vmx instruction!");
            vmexit_vmx_instruction_executed().expect("previledge instruction handle error");
        }
        VmxExitReason::CPUID => {
            debug!("vmexit handler: cpuid instruction!");
            // vmexit_cpuid_handler(guest_cpu_context);
            adjust_rip(guest_rip).unwrap();
        }
        VmxExitReason::RDMSR => {
            debug!("vmexit handler: rdmsr instruction!");
            adjust_rip(guest_rip).unwrap();
        }
        VmxExitReason::WRMSR => {
            debug!("vmexit handler: wrmsr instruction!");
            adjust_rip(guest_rip).unwrap();
        }
        VmxExitReason::TRIPLE_FAULT => {
            debug!("vmexit handler: triple fault!");
            adjust_rip(guest_rip).unwrap();
        }
        VmxExitReason::EPT_VIOLATION => {
            debug!("vmexit handler: ept violation!");
            let gpa = vmx_vmread(GUEST_PHYSICAL_ADDR_FULL).unwrap();
            let exit_qualification = vmx_vmread(VmcsFields::VMEXIT_QUALIFICATION as u32).unwrap();
            /* It is a write fault? */
            let mut error_code = exit_qualification & (1 << 1);
            /* It is a fetch fault? */
            error_code |= (exit_qualification << 2) & (1 << 4);
            /* ept page table is present? */
            error_code |= (exit_qualification >> 3) & (1 << 0);

            let kvm = vm(0).unwrap();
            let vcpu = kvm.vcpu[0].clone();
            // Use the data
            let kvm_ept_page_fault = vcpu.lock().mmu.page_fault.unwrap();
            kvm_ept_page_fault(&mut vcpu.lock(), gpa, error_code as u32, false)
                .expect("ept page fault error");
        }
        _ => {
            debug!(
                "vmexit handler: unhandled vmexit reason: {}!",
                exit_basic_reason
            );

            let info = vmx_vmread(VmcsFields::VMEXIT_INSTR_LEN as u32).unwrap() as u32;
            debug!("vmexit handler: VMEXIT_INSTR_LEN: {}!", info);
            let info = vmx_vmread(VmcsFields::VMEXIT_INSTR_INFO as u32).unwrap() as u32;
            debug!("vmexit handler: VMEXIT_INSTR_INFO: {}!", info);
            let info = vmx_vmread(VmcsFields::CTRL_EXPECTION_BITMAP as u32).unwrap() as u32;
            debug!("vmexit handler: CTRL_EXPECTION_BITMAP: {}!", info);

            adjust_rip(guest_rip).unwrap();
            // panic!();
        }
    }
}

#[no_mangle]
fn adjust_rip(rip: u64) -> Result<(), SystemError> {
    let instruction_length = vmx_vmread(VmcsFields::VMEXIT_INSTR_LEN as u32)?;
    vmx_vmwrite(VmcsFields::GUEST_RIP as u32, rip + instruction_length)?;
    Ok(())
}
