//! FFI bindings to CoolProp C++ library.
//!
//! This module provides low-level FFI bindings to the CoolProp library.
//! All functions are unsafe and require proper error handling.

#![allow(dead_code)]

use libc::{c_char, c_double, c_int};
use std::ffi::CString;

/// Error codes returned by CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropError {
    /// No error occurred
    NoError = 0,
    /// Input error code
    InputError = 1,
    /// Library not loaded
    LibraryNotLoaded = 2,
    /// Unknown property value
    UnknownPropertyValue = 3,
    /// Unknown fluid
    UnknownFluid = 4,
    /// Unknown parameter
    UnknownParameter = 5,
    /// Not implemented
    NotImplemented = 6,
    /// Invalid number of parameters
    InvalidNumber = 7,
    /// Could not load library
    CouldNotLoadLibrary = 8,
    /// Invalid fluid pair
    InvalidFluidPair = 9,
    /// Version mismatch
    VersionMismatch = 10,
    /// Internal error
    InternalError = 11,
}

impl CoolPropError {
    /// Convert CoolProp error code to Rust result
    pub fn from_code(code: i32) -> Result<(), CoolPropError> {
        match code {
            0 => Ok(()),
            _ => Err(match code {
                1 => CoolPropError::InputError,
                2 => CoolPropError::LibraryNotLoaded,
                3 => CoolPropError::UnknownPropertyValue,
                4 => CoolPropError::UnknownFluid,
                5 => CoolPropError::UnknownParameter,
                6 => CoolPropError::NotImplemented,
                7 => CoolPropError::InvalidNumber,
                8 => CoolPropError::CouldNotLoadLibrary,
                9 => CoolPropError::InvalidFluidPair,
                10 => CoolPropError::VersionMismatch,
                _ => CoolPropError::InternalError,
            }),
        }
    }
}

/// Output parameters for CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropParam {
    /// Nothing
    Nothing = 0,
    /// Pressure [Pa]
    Pressure = 1,
    /// Temperature [K]
    Temperature = 2,
    /// Density [kg/m³]
    Density = 3,
    /// Specific enthalpy [J/kg]
    Enthalpy = 4,
    /// Specific entropy [J/kg/K]
    Entropy = 5,
    /// Specific internal energy [J/kg]
    InternalEnergy = 6,
    /// Specific heat at constant pressure [J/kg/K]
    Cv = 7,
    /// Specific heat at constant pressure [J/kg/K]
    Cp = 8,
    /// Quality [-]
    Quality = 9,
    /// Viscosity [Pa·s]
    Viscosity = 10,
    /// Thermal conductivity [W/m/K]
    Conductivity = 11,
    /// Surface tension [N/m]
    SurfaceTension = 12,
    /// Prandtl number [-]
    Prandtl = 13,
}

/// Input parameters for CoolProp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum CoolPropInputPair {
    /// No input
    None = 0,
    /// Pressure & Temperature
    PT = 1,
    /// Pressure & Density
    PD = 2,
    /// Pressure & Enthalpy
    PH = 3,
    /// Pressure & Entropy
    PS = 4,
    /// Pressure & Internal Energy
    PU = 5,
    /// Temperature & Density
    TD = 6,
    /// Temperature & Enthalpy
    TH = 7,
    /// Temperature & Entropy
    TS = 8,
    /// Temperature & Internal Energy
    TU = 9,
    /// Enthalpy & Entropy
    HS = 10,
    /// Density & Internal Energy
    DU = 11,
    /// Pressure & Quality
    PQ = 12,
    /// Temperature & Quality
    TQ = 13,
}

// CoolProp C functions
extern "C" {
    /// Get a property value using pressure and temperature
    #[cfg_attr(target_os = "macos", link_name = "\x01__Z7PropsSIPKcS0_dS0_dS0_")]
    #[cfg_attr(
        all(not(target_os = "macos"), not(target_os = "windows")),
        link_name = "_Z7PropsSIPKcS0_dS0_dS0_"
    )]
    #[cfg_attr(target_os = "windows", link_name = "?PropsSI@@YANPEBD0N0N0@Z")]
    fn PropsSI(
        Output: *const c_char,
        Name1: *const c_char,
        Value1: c_double,
        Name2: *const c_char,
        Value2: c_double,
        Fluid: *const c_char,
    ) -> c_double;

    /// Get a property value using input pair
    #[cfg_attr(target_os = "macos", link_name = "\x01__Z8Props1SIPKcS0_")]
    #[cfg_attr(
        all(not(target_os = "macos"), not(target_os = "windows")),
        link_name = "_Z8Props1SIPKcS0_"
    )]
    #[cfg_attr(target_os = "windows", link_name = "?Props1SI@@YANPEBD0@Z")]
    fn Props1SI(Fluid: *const c_char, Output: *const c_char) -> c_double;

    /// Get CoolProp version string
    #[cfg_attr(
        target_os = "macos",
        link_name = "\x01__Z23get_global_param_stringPKcPci"
    )]
    #[cfg_attr(
        all(not(target_os = "macos"), not(target_os = "windows")),
        link_name = "get_global_param_string"
    )]
    #[cfg_attr(
        target_os = "windows",
        link_name = "?get_global_param_string@@YAJPEBDPEADH@Z"
    )]
    fn get_global_param_string(
        Param: *const c_char,
        Output: *mut c_char,
        OutputLength: c_int,
    ) -> c_int;

    /// Get fluid info
    #[cfg_attr(
        target_os = "macos",
        link_name = "\x01__Z22get_fluid_param_stringPKcS0_Pci"
    )]
    #[cfg_attr(
        all(not(target_os = "macos"), not(target_os = "windows")),
        link_name = "get_fluid_param_string"
    )]
    #[cfg_attr(
        target_os = "windows",
        link_name = "?get_fluid_param_string@@YAJPEBD0PEADH@Z"
    )]
    fn get_fluid_param_string(
        Fluid: *const c_char,
        Param: *const c_char,
        Output: *mut c_char,
        OutputLength: c_int,
    ) -> c_int;

    // Check if fluid exists
    // CoolProp doesn't have a direct C isfluid function. We usually just try to fetch a string or param or we can map it downstream
    // But let's see if we can just dummy it or use get_fluid_param_string

    // There is no C CriticalPoint, it's just Props1SI("Tcrit", "Water")

}

/// Get a thermodynamic property using pressure and temperature.
///
/// # Arguments
/// * `property` - The property to retrieve (e.g., "D" for density, "H" for enthalpy)
/// * `p` - Pressure in Pa
/// * `t` - Temperature in K
/// * `fluid` - Fluid name (e.g., "R134a")
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is properly null-terminated if needed and valid.
pub unsafe fn props_si_pt(property: &str, p: f64, t: f64, fluid: &str) -> f64 {
    let prop_c = std::ffi::CString::new(property).unwrap();
    let fluid_c = CString::new(fluid).unwrap();

    PropsSI(
        prop_c.as_ptr(),
        c"P".as_ptr(),
        p,
        c"T".as_ptr(),
        t,
        fluid_c.as_ptr(),
    )
}

/// Get a thermodynamic property using pressure and enthalpy.
///
/// # Arguments
/// * `property` - The property to retrieve
/// * `p` - Pressure in Pa
/// * `h` - Specific enthalpy in J/kg
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn props_si_ph(property: &str, p: f64, h: f64, fluid: &str) -> f64 {
    let prop_c = std::ffi::CString::new(property).unwrap();
    let fluid_c = CString::new(fluid).unwrap();

    PropsSI(
        prop_c.as_ptr(),
        c"P".as_ptr(),
        p,
        c"H".as_ptr(),
        h,
        fluid_c.as_ptr(),
    )
}

/// Get a thermodynamic property using temperature and quality (saturation).
///
/// # Arguments
/// * `property` - The property to retrieve (D, H, S, P, etc.)
/// * `t` - Temperature in K
/// * `q` - Quality (0 = saturated liquid, 1 = saturated vapor)
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn props_si_tq(property: &str, t: f64, q: f64, fluid: &str) -> f64 {
    let prop_c = std::ffi::CString::new(property).unwrap();
    let fluid_c = CString::new(fluid).unwrap();

    PropsSI(
        prop_c.as_ptr(),
        c"T".as_ptr(),
        t,
        c"Q".as_ptr(),
        q,
        fluid_c.as_ptr(),
    )
}

/// Get a thermodynamic property using pressure and quality.
///
/// # Arguments
/// * `property` - The property to retrieve
/// * `p` - Pressure in Pa
/// * `x` - Quality (0-1)
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn props_si_px(property: &str, p: f64, x: f64, fluid: &str) -> f64 {
    let prop_c = std::ffi::CString::new(property).unwrap();
    let fluid_c = CString::new(fluid).unwrap();

    PropsSI(
        prop_c.as_ptr(),
        c"P".as_ptr(),
        p,
        c"Q".as_ptr(), // Q for quality
        x,
        fluid_c.as_ptr(),
    )
}

/// Get a thermodynamic property using pressure and specific entropy.
///
/// # Arguments
/// * `property` - The property to retrieve (e.g., "H" for enthalpy, "T" for temperature)
/// * `p` - Pressure in Pa
/// * `s` - Specific entropy in J/(kg·K)
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn props_si_ps(property: &str, p: f64, s: f64, fluid: &str) -> f64 {
    let prop_c = std::ffi::CString::new(property).unwrap();
    let fluid_c = CString::new(fluid).unwrap();

    PropsSI(
        prop_c.as_ptr(),
        c"P".as_ptr(),
        p,
        c"S".as_ptr(),
        s,
        fluid_c.as_ptr(),
    )
}

/// Get critical point temperature for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn critical_temperature(fluid: &str) -> f64 {
    let fluid_c = CString::new(fluid).unwrap();
    Props1SI(fluid_c.as_ptr(), c"Tcrit".as_ptr())
}

/// Get critical point pressure for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn critical_pressure(fluid: &str) -> f64 {
    let fluid_c = CString::new(fluid).unwrap();
    Props1SI(fluid_c.as_ptr(), c"pcrit".as_ptr())
}

/// Get critical point density for a fluid.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn critical_density(fluid: &str) -> f64 {
    let fluid_c = CString::new(fluid).unwrap();
    Props1SI(fluid_c.as_ptr(), c"rhocrit".as_ptr())
}

/// Check if a fluid is available in CoolProp.
///
/// # Arguments
/// * `fluid` - Fluid name
///
/// # Returns
/// # Safety
/// This function calls the CoolProp C++ library and passes a CString pointer.
/// The caller must ensure the fluid string is valid.
pub unsafe fn is_fluid_available(fluid: &str) -> bool {
    let fluid_c = CString::new(fluid).unwrap();
    // CoolProp C API does not expose isfluid, so we try fetching a property
    let res = Props1SI(fluid_c.as_ptr(), c"Tcrit".as_ptr());
    res.is_finite() && res != 0.0
}

/// Get CoolProp version string.
///
/// # Returns
/// Version string (e.g., "6.14.0")
pub fn get_version() -> String {
    unsafe {
        let mut buffer = vec![0u8; 32];
        let result = get_global_param_string(
            c"version".as_ptr(),
            buffer.as_mut_ptr() as *mut c_char,
            buffer.len() as c_int,
        );

        if result == 0 {
            String::from_utf8_lossy(&buffer)
                .trim_end_matches('\0')
                .to_string()
        } else {
            String::from("Unknown")
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_version() {
        let version = get_version();
        assert!(!version.is_empty());
    }

    #[test]
    fn test_fluid_available() {
        // Test some common refrigerants
        unsafe {
            assert!(is_fluid_available("R134a"));
            assert!(is_fluid_available("R410A"));
            assert!(is_fluid_available("CO2"));
        }
    }
}
