use std::env;
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JupiterRuntimeMode {
Binary,
Simulated,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct JupiterContext {
pub mode: JupiterRuntimeMode,
pub orbital_radius_km: f64,
pub orbital_period_days: f64,
pub orbital_angle_deg: f64,
pub rotation_period_h: f64,
pub rotation_angle_deg: f64,
pub axial_tilt_deg: f64,
pub surface_speed_m_s: f64,
}
static PLANET_BINARY_CACHE: OnceLock<Option<PathBuf>> = OnceLock::new();
pub fn ensure_jupiters_binary_or_simulate() -> JupiterContext {
if find_planet_executable().is_some() {
return reference_jupiter_context(JupiterRuntimeMode::Binary);
}
simulated_jupiter_context()
}
pub fn reference_jupiter_context(mode: JupiterRuntimeMode) -> JupiterContext {
JupiterContext {
mode,
orbital_radius_km: 778_500_000.0,
orbital_period_days: 4_332.59,
orbital_angle_deg: 0.0,
rotation_period_h: 9.925,
rotation_angle_deg: 0.0,
axial_tilt_deg: 3.13,
surface_speed_m_s: 12_600.0,
}
}
pub fn simulated_jupiter_context() -> JupiterContext {
reference_jupiter_context(JupiterRuntimeMode::Simulated)
}
pub fn jupiter_sibling_satellites() -> &'static [&'static str] {
&["callistos", "europas", "ganymedes", "ios"]
}
fn find_planet_executable() -> Option<PathBuf> {
PLANET_BINARY_CACHE
.get_or_init(find_planet_executable_uncached)
.clone()
}
fn find_planet_executable_uncached() -> Option<PathBuf> {
let names = binary_names();
names
.iter()
.find_map(|binary_name| {
find_in_path(binary_name)
.or_else(|| cargo_bin_dir().map(|directory| directory.join(binary_name)))
.filter(|candidate| candidate.is_file())
})
.or_else(|| find_globally(names))
}
fn binary_names() -> &'static [&'static str] {
if cfg!(windows) {
&["jupiters.exe", "jupiter.exe", "jupiters", "jupiter"]
} else {
&["jupiters", "jupiter"]
}
}
fn cargo_bin_dir() -> Option<PathBuf> {
env::var_os("HOME").map(|home_dir| PathBuf::from(home_dir).join(".cargo/bin"))
}
fn find_in_path(binary_name: &str) -> Option<PathBuf> {
let path_var = env::var_os("PATH")?;
env::split_paths(&path_var)
.map(|directory| directory.join(binary_name))
.find(|candidate| candidate.is_file())
}
fn find_globally(binary_names: &[&str]) -> Option<PathBuf> {
let mut stack = global_search_roots();
while let Some(directory) = stack.pop() {
let entries = match fs::read_dir(&directory) {
Ok(entries) => entries,
Err(_) => continue,
};
for entry in entries.flatten() {
let file_type = match entry.file_type() {
Ok(file_type) => file_type,
Err(_) => continue,
};
let path = entry.path();
if file_type.is_file() {
let is_match = path
.file_name()
.and_then(|name| name.to_str())
.map(|name| binary_names.contains(&name))
.unwrap_or(false);
if is_match {
return Some(path);
}
} else if file_type.is_dir() && !file_type.is_symlink() {
stack.push(path);
}
}
}
None
}
fn global_search_roots() -> Vec<PathBuf> {
if cfg!(windows) {
let mut roots = Vec::new();
for drive in b'A'..=b'Z' {
let candidate = PathBuf::from(format!("{}:\\", drive as char));
if candidate.is_dir() {
roots.push(candidate);
}
}
roots
} else {
vec![PathBuf::from("/")]
}
}