use crate::relocation::{TrapCode, TrapData};
use crate::signal::{CallProtError, HandlerData};
use crate::trampoline::Trampoline;
use std::cell::Cell;
use std::ffi::c_void;
use std::ptr::{self, NonNull};
use wasmer_runtime_core::typed_func::WasmTrapInfo;
use wasmer_runtime_core::vm::Ctx;
use wasmer_runtime_core::vm::Func;
use wasmer_win_exception_handler::CallProtectedData;
pub use wasmer_win_exception_handler::_call_protected;
use winapi::shared::minwindef::DWORD;
use winapi::um::minwinbase::{
    EXCEPTION_ACCESS_VIOLATION, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_BREAKPOINT,
    EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_FLT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO,
    EXCEPTION_FLT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW,
    EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW, EXCEPTION_GUARD_PAGE,
    EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW,
    EXCEPTION_INVALID_HANDLE, EXCEPTION_IN_PAGE_ERROR, EXCEPTION_NONCONTINUABLE_EXCEPTION,
    EXCEPTION_POSSIBLE_DEADLOCK, EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_SINGLE_STEP,
    EXCEPTION_STACK_OVERFLOW,
};

thread_local! {
    pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
}

pub fn call_protected(
    handler_data: &HandlerData,
    trampoline: Trampoline,
    ctx: *mut Ctx,
    func: NonNull<Func>,
    param_vec: *const u64,
    return_vec: *mut u64,
) -> Result<(), CallProtError> {
    // TODO: trap early
    // user code error
    //    if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
    //        return Err(RuntimeError::User { msg });
    //    }

    let result = _call_protected(trampoline, ctx, func, param_vec, return_vec);

    if let Ok(_) = result {
        return Ok(());
    }

    let CallProtectedData {
        code,
        exception_address,
        instruction_pointer,
    } = result.unwrap_err();

    if let Some(TrapData {
        trapcode,
        srcloc: _,
    }) = handler_data.lookup(instruction_pointer as _)
    {
        Err(CallProtError::Trap(match code as DWORD {
            EXCEPTION_ACCESS_VIOLATION => WasmTrapInfo::MemoryOutOfBounds,
            EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode {
                TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature,
                TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB,
                TrapCode::HeapOutOfBounds => WasmTrapInfo::MemoryOutOfBounds,
                TrapCode::TableOutOfBounds => WasmTrapInfo::CallIndirectOOB,
                TrapCode::UnreachableCodeReached => WasmTrapInfo::Unreachable,
                _ => WasmTrapInfo::Unknown,
            },
            EXCEPTION_STACK_OVERFLOW => WasmTrapInfo::Unknown,
            EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => {
                WasmTrapInfo::IllegalArithmetic
            }
            _ => WasmTrapInfo::Unknown,
        }))
    } else {
        let signal = match code as DWORD {
            EXCEPTION_FLT_DENORMAL_OPERAND
            | EXCEPTION_FLT_DIVIDE_BY_ZERO
            | EXCEPTION_FLT_INEXACT_RESULT
            | EXCEPTION_FLT_INVALID_OPERATION
            | EXCEPTION_FLT_OVERFLOW
            | EXCEPTION_FLT_STACK_CHECK
            | EXCEPTION_FLT_UNDERFLOW => "floating-point exception",
            EXCEPTION_ILLEGAL_INSTRUCTION => "illegal instruction",
            EXCEPTION_ACCESS_VIOLATION => "segmentation violation",
            EXCEPTION_DATATYPE_MISALIGNMENT => "datatype misalignment",
            EXCEPTION_BREAKPOINT => "breakpoint",
            EXCEPTION_SINGLE_STEP => "single step",
            EXCEPTION_ARRAY_BOUNDS_EXCEEDED => "array bounds exceeded",
            EXCEPTION_INT_DIVIDE_BY_ZERO => "int div by zero",
            EXCEPTION_INT_OVERFLOW => "int overflow",
            EXCEPTION_PRIV_INSTRUCTION => "priv instruction",
            EXCEPTION_IN_PAGE_ERROR => "in page error",
            EXCEPTION_NONCONTINUABLE_EXCEPTION => "non continuable exception",
            EXCEPTION_STACK_OVERFLOW => "stack overflow",
            EXCEPTION_GUARD_PAGE => "guard page",
            EXCEPTION_INVALID_HANDLE => "invalid handle",
            EXCEPTION_POSSIBLE_DEADLOCK => "possible deadlock",
            _ => "unknown exception code",
        };

        let s = format!(
            "unhandled trap at {:x} - code #{:x}: {}",
            exception_address, code, signal,
        );

        Err(CallProtError::Error(Box::new(s)))
    }
}

pub unsafe fn trigger_trap() -> ! {
    // TODO
    unimplemented!();
}
