use sciforge::hub::domain::meteorology::radiation::{solar_constant, stefan_boltzmann_flux};
use sciforge::hub::prelude::constants::R_GAS;

pub const ONEBARPRESSURE: f64 = crate::ONEBARPRESSURE;
pub const ONEBARTEMPK: f64 = crate::ONEBARTEMPK;

pub fn atmospheremolarmass() -> f64 {
    crate::matmosphere()
}

pub const LAPSERATETROPOSPHERE: f64 = 0.001;

pub struct AtmosphericLayer {
    pub name: &'static str,
    pub basealtitudem: f64,
    pub topaltitudem: f64,
    pub basetemperaturek: f64,
    pub lapseratekperm: f64,
}

impl AtmosphericLayer {
    pub fn temperatureat(&self, altitudem: f64) -> f64 {
        let h = (altitudem - self.basealtitudem)
            .max(0.0)
            .min(self.topaltitudem - self.basealtitudem);
        self.basetemperaturek - self.lapseratekperm * h
    }

    pub fn pressureat(&self, altitudem: f64) -> f64 {
        let t = self.temperatureat(altitudem);
        let h = altitudem - self.basealtitudem;
        let g0 = *crate::SURFACEGRAVITY;
        if self.lapseratekperm.abs() < 1e-10 {
            let pbase = barometricpressure(self.basealtitudem);
            pbase * (-g0 * atmospheremolarmass() * h / (R_GAS * self.basetemperaturek)).exp()
        } else {
            let pbase = barometricpressure(self.basealtitudem);
            pbase
                * (t / self.basetemperaturek)
                    .powf(g0 * atmospheremolarmass() / (R_GAS * self.lapseratekperm))
        }
    }

    pub fn densityat(&self, altitudem: f64) -> f64 {
        let p = self.pressureat(altitudem);
        let t = self.temperatureat(altitudem);
        p * atmospheremolarmass() / (R_GAS * t)
    }
}

pub fn troposphere() -> AtmosphericLayer {
    AtmosphericLayer {
        name: "Troposphere",
        basealtitudem: 0.0,
        topaltitudem: 50000.0,
        basetemperaturek: 72.0,
        lapseratekperm: 0.001,
    }
}

pub fn stratosphere() -> AtmosphericLayer {
    AtmosphericLayer {
        name: "Stratosphere",
        basealtitudem: 50000.0,
        topaltitudem: 250000.0,
        basetemperaturek: 52.0,
        lapseratekperm: -0.0005,
    }
}

pub fn thermosphere() -> AtmosphericLayer {
    AtmosphericLayer {
        name: "Thermosphere",
        basealtitudem: 250000.0,
        topaltitudem: 2000000.0,
        basetemperaturek: 152.0,
        lapseratekperm: -0.0001,
    }
}

pub fn barometricpressure(altitudem: f64) -> f64 {
    let g0 = *crate::SURFACEGRAVITY;
    if altitudem <= 50000.0 {
        let t = ONEBARTEMPK - LAPSERATETROPOSPHERE * altitudem;
        ONEBARPRESSURE
            * (t / ONEBARTEMPK).powf(g0 * atmospheremolarmass() / (R_GAS * LAPSERATETROPOSPHERE))
    } else {
        let ptropo = barometricpressure(50000.0);
        let ttropo = 52.0;
        ptropo * (-g0 * atmospheremolarmass() * (altitudem - 50000.0) / (R_GAS * ttropo)).exp()
    }
}

pub fn scaleheight() -> f64 {
    *crate::SCALEHEIGHT
}

pub fn meansolarirradiance() -> f64 {
    solar_constant() / (30.07 * 30.07)
}

pub fn effectivetemperaturek() -> f64 {
    let s = meansolarirradiance();
    let internal = crate::INTERNALHEATINGWM2;
    let totalflux = s * (1.0 - crate::BONDALBEDO) / 4.0 + internal;
    stefan_boltzmann_flux(totalflux.powf(0.25))
}
