//! Crate configuration.
//!
//! This module provides global configuration management for `CoolProp`.
//!
//! [`Config`] represents the configuration state that mirrors `CoolProp` configuration options.
//! Create new instances with [`Config::builder`], read the current configuration with [`read`],
//! apply changes with [`update`], and restore defaults with [`reset`].
//!
//! `CoolProp` configuration is global. For predictable behavior, choose the desired
//! configuration during application startup, before running calculations. In typical
//! applications this means applying [`update`] once, or leaving the default configuration as is.
//!
//! # [`serde`](https://crates.io/crates/serde) Support
//!
//! Enable the `serde` feature to add serialization and deserialization support for [`Config`].
//!
//! Add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! rfluids = { version = "0.4", features = ["serde"] }
//! ```
//!
//! Or via command line:
//!
//! ```shell
//! cargo add rfluids --features serde
//! ```
//!
//! This allows direct serialization to TOML, YAML, JSON and other formats, as well as integration
//! with configuration management crates (e.g., [`config`](https://crates.io/crates/config),
//! [`figment`](https://crates.io/crates/figment), [`confy`](https://crates.io/crates/confy), etc.).
//!
//! ## Example
//!
//! Loading configuration with the [`config`](https://crates.io/crates/config) crate
//! (requires `serde` feature).
//!
//! For example, given the following file structure:
//!
//! ```text
//! my-app/
//! ├── config/
//! │   └── coolprop.toml
//! ├── src/
//! │   └── main.rs
//! ├── Cargo.lock
//! └── Cargo.toml
//! ```
//!
//! Configuration file `config/coolprop.toml`:
//!
//! ```toml
//! enable_superancillaries = false
//! alt_tables_path = "path/to/tables"
//! max_table_dir_size_in_gb = 42.0
//! save_raw_tables = true
//! list_punctuation = ';'
//! ```
//!
//! Application code in `src/main.rs`:
//!
//! ```ignore
//! use config::{Config, Environment, File};
//! use rfluids::config::Config as CoolPropConfig;
//!
//! fn main() {
//!     // Load from TOML file and environment variables
//!     let cfg_src = Config::builder()
//!         .add_source(File::with_name("config/coolprop"))
//!         .add_source(Environment::with_prefix("COOLPROP"))
//!         .build()
//!         .unwrap();
//!
//!     // Deserialize into configuration
//!     let cfg: CoolPropConfig = cfg_src.try_deserialize().unwrap();
//!     println!("Loaded configuration: {cfg:#?}");
//!
//!     // Update crate configuration
//!     rfluids::config::update(cfg);
//! }
//! ```

#![allow(clippy::struct_excessive_bools)]

mod declare;

use std::{
    path::PathBuf,
    sync::{LazyLock, RwLock},
};

use declare::declare_config;

use crate::native::CoolProp;

static CONFIG: LazyLock<RwLock<Config>> = LazyLock::new(|| RwLock::new(Config::default()));

declare_config! {
    @custom {
        /// `CoolProp` debug level.
        ///
        /// The debug level controls the verbosity of debugging output from `CoolProp`.
        /// Valid range is `0-10`:
        ///
        /// - `0` -- debugging output is disabled
        /// - `> 0` -- debugging output is enabled, with larger values producing more verbose output
        ///
        /// **Default:** `0`
        pub debug_level: u8 = 0 => (get: CoolProp::get_debug_level, set: CoolProp::set_debug_level),
    }

    /// If `true`, evaluation of the stability of critical point will be skipped and point will be
    /// assumed to be stable.
    ///
    /// **Default:** `false`
    pub assume_critical_point_is_stable: bool = false => AssumeCriticalPointIsStable,

    /// if `true`, any temperature within `1 uK` of the critical temperature will be considered to
    /// be **AT** the critical point.
    ///
    /// **Default:** `true`
    pub critical_within_1uk: bool = true => CriticalWithin1Uk,

    /// If `true`, when possible, `CoolProp` will skip checking whether values are inside the
    /// property limits.
    ///
    /// **Default:** `false`
    pub dont_check_prop_limits: bool = false => DontCheckPropLimits,

    /// If `true`, the critical splines will be used in the near-vicinity of the critical point.
    ///
    /// **Default:** `true`
    pub enable_critical_splines: bool = true => EnableCriticalSplines,

    /// if `true`, the superancillary functions will be used for VLE of pure fluids.
    ///
    /// **Default:** `true`
    pub enable_superancillaries: bool = true => EnableSuperancillaries,

    /// If `true`, when doing water-based mixture dewpoint calculations, use Henry’s Law to
    /// generate guesses for liquid-phase composition.
    ///
    /// **Default:** `false`
    pub henrys_law_to_generate_vle_guesses: bool = false => HenrysLawToGenerateVleGuesses,

    /// If `true`, for mixtures, the molar gas constant _(R)_ will be set to the CODATA value.
    ///
    /// **Default:** `true`
    pub normalize_gas_constants: bool = true => NormalizeGasConstants,

    /// If `true`, and a pair of binary interaction pairs to be added is already there, rather than
    /// not adding the binary interaction pair _(and probably throwing an exception)_, overwrite
    /// it.
    ///
    /// **Default:** `false`
    pub overwrite_binary_interaction: bool = false => OverwriteBinaryInteraction,

    /// if `true`, and a departure function to be added is already there, rather than not adding
    /// the departure function _(and probably throwing an exception)_, overwrite it.
    ///
    /// **Default:** `false`
    pub overwrite_departure_fn: bool = false => OverwriteDepartureFn,

    /// If `true`, and a substance is added to the substances library that is already there, rather
    /// than not adding the substance _(and probably throwing an exception)_, overwrite it.
    ///
    /// **Default:** `false`
    pub overwrite_substances: bool = false => OverwriteSubstances,

    /// Starting pressure in `Pa` for phase envelope construction.
    ///
    /// **Default:** `100.0`
    pub phase_envelope_start_pressure_pa: f64 = 100.0 => PhaseEnvelopeStartPressurePa,

    /// The value for the ideal gas constant in `J/mol/K` according to CODATA 2022. This value is
    /// used to harmonize all the ideal gas constants. This is especially important in the critical
    /// region.
    ///
    /// **Default:** `8.314_462_618_153_24`
    pub ru_codata: f64 = 8.314_462_618_153_24 => RUCodata,

    /// The minimal delta to be used in tracing out the spinodal; make sure that the EOS has a
    /// spinodal at this value of `delta=rho/rho_r`.
    ///
    /// **Default:** `0.5`
    pub spinodal_min_delta: f64 = 0.5 => SpinodalMinDelta,

    /// If `true`, calls to the vectorized versions of `PropsSI` use the previous state as guess
    /// value while looping over the input vectors, only makes sense when working with a single
    /// fluid and with points that are not too far from each other.
    ///
    /// **Default:** `false`
    pub use_guesses_in_props_si: bool = false => UseGuessesInPropsSi,

    /// An alternative path to be provided to the directory that contains `REFPROP`’s fluids and
    /// mixtures directories. If provided, the `SETPATH` function will be called with this
    /// directory prior to calling any `REFPROP` functions.
    ///
    /// **Default:** `None`
    pub alt_refprop_path: Option<PathBuf> => AltRefpropPath,

    /// An alternative path to the shared library file. If provided, it will be used to load
    /// `REFPROP`.
    ///
    /// **Default:** `None`
    pub alt_refprop_lib_path: Option<PathBuf> => AltRefpropLibPath,

    /// An alternative path to the `HMX.BNC` file. If provided, it will be passed into `REFPROP`’s
    /// `SETUP` or `SETMIX` routines.
    ///
    /// **Default:** `None`
    pub alt_refprop_hmx_bnc_path: Option<PathBuf> => AltRefpropHmxBncPath,

    /// If `true`, if the binary interaction parameters in `REFPROP` are estimated, throw an error
    /// rather than silently continuing.
    ///
    /// **Default:** `false`
    pub refprop_dont_estimate_interaction_params: bool = false =>
        RefpropDontEstimateInteractionParams,

    /// If `true`, if the binary interaction parameters in `REFPROP` are unable to be estimated,
    /// silently continue rather than failing.
    ///
    /// **Default:** `false`
    pub refprop_ignore_error_estimated_interaction_params: bool = false =>
        RefpropIgnoreErrorEstimatedInteractionParams,

    /// If `true`, rather than using the highly-accurate pure fluid equations of state, use the
    /// pure-fluid EOS from `GERG-2008`.
    ///
    /// **Default:** `false`
    pub refprop_use_gerg: bool = false => RefpropUseGerg,

    /// If `true`, rather than using the highly-accurate pure fluid equations of state, use the
    /// Peng-Robinson EOS.
    ///
    /// **Default:** `false`
    pub refprop_use_peng_robinson: bool = false => RefpropUsePengRobinson,

    /// If provided, this path will be the root directory for the tabular data. Otherwise,
    /// `${HOME}/.CoolProp/Tables` is used.
    ///
    /// **Default:** `None`
    pub alt_tables_path: Option<PathBuf> => AltTablesPath,

    /// The first character of this string will be used as the separator between the number
    /// fraction.
    ///
    /// **Default:** `'.'`
    pub float_punctuation: char = '.' => FloatPunctuation,

    /// The delimiter to be used when converting a list of strings to a string.
    ///
    /// **Default:** `','`
    pub list_punctuation: char = ',' => ListPunctuation,

    /// The maximum allowed size of the directory that is used to store tabular data.
    ///
    /// **Default:** `1.0`
    pub max_table_dir_size_in_gb: f64 = 1.0 => MaxTableDirSizeInGb,

    /// If `true`, the raw, uncompressed tables will also be written to file.
    ///
    /// **Default:** `false`
    pub save_raw_tables: bool = false => SaveRawTables,

    /// If `true`, the library will always be reloaded, no matter what is currently loaded.
    ///
    /// **Default:** `false`
    pub vtpr_always_reload_lib: bool = false => VtPrAlwaysReloadLib,

    /// The path to the directory containing the UNIFAC JSON files.
    ///
    /// **Default:** `None`
    pub vtpr_unifac_path: Option<PathBuf> => VtPrUnifacPath,
}

/// Returns a clone of the current crate configuration.
///
/// This function acquires a read lock on the global configuration state and returns
/// a clone of the current [`Config`].
///
/// # Panics
///
/// Panics if the internal read lock is poisoned. This should only occur if another thread
/// panicked while holding the write lock.
///
/// # Examples
///
/// ```no_run
/// use rfluids::config;
///
/// let cfg = config::read();
/// println!("Current configuration: {cfg:#?}");
/// ```
pub fn read() -> Config {
    CONFIG.read().unwrap().clone()
}

/// Updates the crate configuration with new values.
///
/// To create a new configuration, use [`Config::builder`] to build a configuration
/// with only the fields you want to change.
///
/// This function compares each field of the new configuration with the current one.
/// For any field that has changed, it synchronizes the value with the underlying `CoolProp`
/// library before updating the global state.
///
/// `CoolProp` configuration is global. For predictable behavior, call this function during
/// application startup, before running calculations, and avoid changing configuration while
/// calculations are already in progress.
///
/// # Panics
///
/// Panics if `CoolProp` rejects a configuration value. This should never happen in practice,
/// as the implementation uses only valid configuration keys with correctly typed values.
/// `CoolProp` validates only the type correctness for each existing configuration key.
///
/// Also panics if the internal write lock is poisoned. This should only occur if another thread
/// panicked while holding the write lock.
///
/// # Examples
///
/// ```no_run
/// use rfluids::config::{self, Config};
///
/// // Update specific configuration options
/// let new_cfg = Config::builder()
///     .enable_critical_splines(false)
///     .enable_superancillaries(false)
///     .dont_check_prop_limits(true)
///     .build();
///
/// config::update(new_cfg);
/// ```
pub fn update(new: Config) {
    let mut cfg = CONFIG.write().unwrap();
    cfg.update(new);
}

/// Resets the crate configuration to its default values.
///
/// `CoolProp` configuration is global. For predictable behavior, reset configuration only before
/// running calculations, or as part of test setup/teardown when no calculations are running.
///
/// # Panics
///
/// Panics if the internal write lock is poisoned. This should only occur if another thread
/// panicked while holding the write lock.
pub fn reset() {
    update(Config::default());
}
