//! # Thermodynamic Calculation API Module
//!
//! ## Aim
//! This module provides a unified trait-based API for thermodynamic property calculations
//! across different data sources (NASA, NIST, etc.). It implements the strategy pattern
//! using enum_dispatch for zero-cost abstractions and provides factory methods for
//! creating appropriate calculators based on database type.
//!
//! ## Main Data Structures and Logic
//! - `ThermoCalculator` trait: Unified interface for all thermodynamic calculation implementations
//! - `ThermoEnum`: Enum dispatch wrapper providing zero-cost polymorphism
//! - `ThermoError`: Comprehensive error handling with automatic conversion from specific errors
//! - `EnergyUnit` enum: Standardized energy unit handling (Joules vs Calories)
//! - Factory pattern implementation for creating calculators by type or name
//!
//! ## Key Methods
//! - `extract_model_coefficients()`: Extract appropriate coefficients for given temperature
//! - `calculate_Cp_dH_dS()`: Compute thermodynamic properties at specified conditions
//! - `create_closures_Cp_dH_dS()`: Generate efficient closure functions for repeated calculations
//! - `create_sym_Cp_dH_dS()`: Create symbolic expressions for analytical work
//! - `Taylor_series_cp_dh_ds()`: Generate Taylor series expansions around operating points
//! - `renew_base()`: Update calculator with new substance data (for NIST web scraping)
//!
//! ## Usage
//! ```rust, ignore
//! let mut calc = create_thermal_by_name("NASA_gas");
//! calc.from_serde(database_entry)?;
//! calc.extract_model_coefficients(400.0)?;
//! calc.calculate_Cp_dH_dS(400.0)?;
//! let cp = calc.get_Cp()?;
//! let symbolic_cp = calc.get_Cp_sym()?;
//! ```
//!
//! ## Interesting Features
//! - Zero-cost abstractions using enum_dispatch for runtime polymorphism without vtables
//! - Automatic error conversion between different calculator error types
//! - Factory methods supporting both enum-based and string-based calculator creation
//! - Unified interface hiding implementation details of different thermodynamic databases
//! - Support for both numerical and symbolic computation modes
//! - Comprehensive getter methods for accessing calculated properties and functions
//! - Integration with web scraping for real-time NIST data access

use crate::Thermodynamics::DBhandlers::NIST_parser::{Phase, SearchType};
use RustedSciThe::symbolic::symbolic_engine::Expr;
use enum_dispatch::enum_dispatch;
use serde_json::Value;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum ThermoError {
    NoCoefficientsFound { temperature: f64, range: String },
    InvalidTemperatureRange,
    UnsupportedUnit(String),
    SerdeError(serde_json::Error),
    SymbolicError(String),
    CalculationError(String),
    FittingError(String),
}

impl fmt::Display for ThermoError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ThermoError::NoCoefficientsFound { temperature, range } => {
                write!(
                    f,
                    "No coefficients found for temperature {} K. Valid range: {}",
                    temperature, range
                )
            }
            ThermoError::InvalidTemperatureRange => {
                write!(f, "Invalid temperature range in coefficient data")
            }
            ThermoError::SerdeError(msg) => {
                write!(f, "Failed to deserialize NASA data: {}", msg)
            }
            ThermoError::UnsupportedUnit(unit) => {
                write!(
                    f,
                    "Unsupported unit: {}. Only 'J' and 'cal' are supported",
                    unit
                )
            }
            ThermoError::SymbolicError(msg) => {
                write!(f, "Symbolic error: {}", msg)
            }
            ThermoError::CalculationError(msg) => {
                write!(f, "Calculation error: {}", msg)
            }
            ThermoError::FittingError(msg) => {
                write!(f, "Fitting error: {}", msg)
            }
        }
    }
}

impl Error for ThermoError {}

impl From<super::NISTdata::NISTError> for ThermoError {
    fn from(err: super::NISTdata::NISTError) -> Self {
        match err {
            super::NISTdata::NISTError::NoCoefficientsFound { temperature, range } => {
                ThermoError::NoCoefficientsFound { temperature, range }
            }
            super::NISTdata::NISTError::InvalidTemperatureRange => {
                ThermoError::InvalidTemperatureRange
            }
            super::NISTdata::NISTError::SerdeError(msg) => ThermoError::SerdeError(msg),
            super::NISTdata::NISTError::UnsupportedUnit(unit) => ThermoError::UnsupportedUnit(unit),
            super::NISTdata::NISTError::SymbolicError(msg) => ThermoError::SymbolicError(msg),
            super::NISTdata::NISTError::FittingError(msg) => ThermoError::FittingError(msg),
            //    super::NISTdata::NISTError::SerdeError(err) => ThermoError::SerdeError(err),
        }
    }
}

impl From<super::NASAdata::NASAError> for ThermoError {
    fn from(err: super::NASAdata::NASAError) -> Self {
        match err {
            super::NASAdata::NASAError::NoCoefficientsFound { temperature, range } => {
                ThermoError::NoCoefficientsFound { temperature, range }
            }
            super::NASAdata::NASAError::InvalidTemperatureRange => {
                ThermoError::InvalidTemperatureRange
            }
            super::NASAdata::NASAError::SerdeError(msg) => ThermoError::SerdeError(msg),
            super::NASAdata::NASAError::UnsupportedUnit(unit) => ThermoError::UnsupportedUnit(unit),
            super::NASAdata::NASAError::SymbolicError(msg) => ThermoError::SymbolicError(msg),
            super::NASAdata::NASAError::FittingError(msg) => ThermoError::FittingError(msg),
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum EnergyUnit {
    J,
    Cal,
}
#[enum_dispatch]
pub trait ThermoCalculator {
    fn newinstance(&mut self) -> Result<(), ThermoError>;
    fn set_unit(&mut self, unit: Option<EnergyUnit>) -> Result<(), ThermoError>;
    fn from_serde(&mut self, serde: Value) -> Result<(), ThermoError>;
    fn extract_model_coefficients(&mut self, t: f64) -> Result<(), ThermoError>;
    fn parse_coefficients(&mut self) -> Result<(), ThermoError>;
    fn calculate_Cp_dH_dS(&mut self, t: f64) -> Result<(), ThermoError>;
    fn create_closures_Cp_dH_dS(&mut self) -> Result<(), ThermoError>;
    fn create_sym_Cp_dH_dS(&mut self) -> Result<(), ThermoError>;
    fn Taylor_series_cp_dh_ds(
        &mut self,
        temperature: f64,
        order: usize,
    ) -> Result<(Expr, Expr, Expr), ThermoError>;
    fn pretty_print_data(&self) -> Result<(), ThermoError>;
    fn renew_base(
        &mut self,
        sub_name: String,
        search_type: SearchType,
        phase: Phase,
    ) -> Result<(), ThermoError>;
    fn get_coefficients(&self) -> Result<Vec<f64>, ThermoError>;

    // Add getter methods for thermodynamic properties
    fn print_instance(&self) -> Result<(), ThermoError>;
    fn get_Cp(&self) -> Result<f64, ThermoError>;
    fn get_dh(&self) -> Result<f64, ThermoError>;
    fn get_ds(&self) -> Result<f64, ThermoError>;
    fn get_C_fun(&self) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, ThermoError>;
    fn get_dh_fun(&self) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, ThermoError>;
    fn get_ds_fun(&self) -> Result<Box<dyn Fn(f64) -> f64 + Send + Sync>, ThermoError>;
    fn get_Cp_sym(&self) -> Result<Expr, ThermoError>;
    fn get_dh_sym(&self) -> Result<Expr, ThermoError>;
    fn get_ds_sym(&self) -> Result<Expr, ThermoError>;
    fn get_composition(&self) -> Result<Option<HashMap<String, f64>>, ThermoError>;
    fn fitting_coeffs_for_T_interval(&mut self) -> Result<(), ThermoError>;
    fn integr_mean(&mut self) -> Result<(), ThermoError>;
    fn set_T_interval(&mut self, T_min: f64, T_max: f64) -> Result<(), ThermoError>;
    fn calculate_Cp_dH_dS_with_T_range(&mut self, T: f64) -> Result<(), ThermoError>;
    fn create_closures_Cp_dH_dS_with_T_range(&mut self, T: f64) -> Result<(), ThermoError>;
    fn create_sym_Cp_dH_dS_with_T_range(&mut self, T: f64) -> Result<(), ThermoError>;
    fn is_coeffs_valid_for_T(&self, T: f64) -> Result<bool, ThermoError>;
}
#[derive(Clone, Debug)]
#[enum_dispatch(ThermoCalculator)]
pub enum ThermoEnum {
    NIST(super::NISTdata::NISTdata),
    NASA(super::NASAdata::NASAdata),
}

pub enum ThermoType {
    NIST,
    NASA,
}

pub fn create_thermal(calc_type: ThermoType) -> ThermoEnum {
    match calc_type {
        ThermoType::NIST => ThermoEnum::NIST(super::NISTdata::NISTdata::new()),
        ThermoType::NASA => ThermoEnum::NASA(super::NASAdata::NASAdata::new()),
    }
}

pub fn create_thermal_by_name(calc_name: &str) -> ThermoEnum {
    match calc_name {
        "Cantera_nasa_base_gas"
        | "NASA_gas"
        | "NASA_cond"
        | "Cantera_nasa_base_cond"
        | "nuig_thermo"
        | "NASA"
        | "NASA7" => ThermoEnum::NASA(super::NASAdata::NASAdata::new()),
        "NIST" | "NIST9" => ThermoEnum::NIST(super::NISTdata::NISTdata::new()),
        _ => panic!("no such library!"),
    }
}
pub fn energy_dimension(unit: EnergyUnit) -> String {
    match unit {
        EnergyUnit::J => "J".to_owned(),
        EnergyUnit::Cal => "cal".to_owned(),
    }
}
