
use std::ffi::CStr;
use std::os::raw::c_char;

use thiserror::Error;

use crate::utils::ffi;

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DarraKernelStatus {

    Ok = 0,

    NotInstalled = 1,

    Stopped = 2,

    AccessDenied = 3,

    SignatureFail = 4,

    Blocked = 5,

    DeviceMissing = 6,

    Disabled = 7,

    UnknownError = 99,
}

impl DarraKernelStatus {

    pub fn from_i32(code: i32) -> Self {
        match code {
            0 => DarraKernelStatus::Ok,
            1 => DarraKernelStatus::NotInstalled,
            2 => DarraKernelStatus::Stopped,
            3 => DarraKernelStatus::AccessDenied,
            4 => DarraKernelStatus::SignatureFail,
            5 => DarraKernelStatus::Blocked,
            6 => DarraKernelStatus::DeviceMissing,
            7 => DarraKernelStatus::Disabled,
            _ => DarraKernelStatus::UnknownError,
        }
    }

    pub fn as_i32(self) -> i32 {
        self as i32
    }
}

impl std::fmt::Display for DarraKernelStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

        write!(f, "{}", get_status_message(*self))
    }
}

#[derive(Error, Debug, Clone)]
#[error("EtherCAT 内核驱动不可用 ({status:?}): {message}\n安装链接: {installer_url}")]
pub struct DarraKernelNotAvailableError {

    pub status: DarraKernelStatus,

    pub message: String,

    pub installer_url: String,
}

fn cstr_to_string(p: *const c_char, fallback: &str) -> String {
    if p.is_null() {
        return fallback.to_string();
    }
    unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
}

pub fn probe() -> DarraKernelStatus {

    let code = unsafe { ffi::DarraEcat_KernelProbe() };
    DarraKernelStatus::from_i32(code as i32)
}

pub fn get_status_message(status: DarraKernelStatus) -> String {
    let p = unsafe { ffi::DarraEcat_KernelStatusMessage(status.as_i32()) };
    cstr_to_string(p, "EtherCAT 内核驱动状态未知")
}

pub fn get_installer_url() -> String {
    let p = unsafe { ffi::DarraEcat_KernelInstallerUrl() };
    cstr_to_string(p, "https://www.darrart.com/downloads/drivers")
}

pub fn assert_available() -> Result<(), DarraKernelNotAvailableError> {
    let status = probe();
    if status == DarraKernelStatus::Ok {
        return Ok(());
    }
    let message = get_status_message(status);
    let installer_url = get_installer_url();
    Err(DarraKernelNotAvailableError {
        status,
        message,
        installer_url,
    })
}

pub fn open_installer_website() -> bool {
    let url = get_installer_url();
    if url.is_empty() {
        return false;
    }
    open_url(&url)
}

#[cfg(target_os = "windows")]
fn open_url(url: &str) -> bool {
    use std::process::Command;

    Command::new("cmd")
        .args(["/C", "start", "", url])
        .spawn()
        .is_ok()
}

#[cfg(target_os = "macos")]
fn open_url(url: &str) -> bool {
    use std::process::Command;
    Command::new("open").arg(url).spawn().is_ok()
}

#[cfg(all(unix, not(target_os = "macos")))]
fn open_url(url: &str) -> bool {
    use std::process::Command;
    Command::new("xdg-open").arg(url).spawn().is_ok()
}

#[cfg(not(any(target_os = "windows", target_os = "macos", unix)))]
fn open_url(_url: &str) -> bool {
    false
}
