use energyml_core::identity;
use energyml_core::value::{FieldValue, Reflect};
use energyml_domain::{Crs, crs, units};
use energyml_storage::resource::ObjectSummary;
use crate::error::{Error, Result};
use crate::scene::Placement;
use crate::source::ValueSource;
use crate::well;
const FRAME: &str = "WellboreFrameRepresentation";
const TRAJECTORY: &str = "WellboreTrajectoryRepresentation";
#[derive(Clone, Debug, PartialEq)]
pub struct WellHead {
pub id: String,
pub title: String,
pub xy: [f64; 2],
}
#[derive(Clone, Debug, PartialEq)]
pub struct HeadRefused {
pub id: String,
pub why: String,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Heads {
pub heads: Vec<WellHead>,
pub refused: Vec<HeadRefused>,
pub system: Option<u64>,
}
#[derive(Clone, Debug, PartialEq)]
struct System {
epsg: Option<u64>,
uom: Option<String>,
}
impl System {
fn describe(&self) -> String {
let epsg = self
.epsg
.map(|code| format!("EPSG:{code}"))
.unwrap_or_else(|| "no EPSG code".to_string());
match &self.uom {
Some(uom) => format!("{epsg}, in {uom}"),
None => epsg,
}
}
}
pub fn trajectories_of(summaries: &[ObjectSummary]) -> Vec<String> {
summaries
.iter()
.filter(|one| one.qualified_type.ends_with(TRAJECTORY))
.map(|one| one.uri.clone())
.collect()
}
pub fn of_objects(source: &dyn ValueSource, asked: &[String], placement: Placement) -> Result<Heads> {
let mut out = Heads::default();
let mut seen: Option<(System, String)> = None;
for id in asked {
match head_of_one(source, id, placement) {
Ok((head, system)) => {
match &seen {
None => seen = Some((system, id.clone())),
Some((first, first_id)) if *first != system => {
return Err(Error::malformed(
id,
format!(
"this well's head is in {} but `{first_id}`'s is in {}: a profile measures in one \
plane, and two systems would be measured against each other as if they were one",
system.describe(),
first.describe()
),
));
}
Some(_) => {}
}
out.heads.push(head);
}
Err(why) => out.refused.push(HeadRefused { id: id.clone(), why }),
}
}
out.system = seen.and_then(|(system, _)| system.epsg);
Ok(out)
}
fn head_of_one(
source: &dyn ValueSource,
id: &str,
placement: Placement,
) -> std::result::Result<(WellHead, System), String> {
let object = source
.object_at(id)
.ok_or_else(|| format!("`{id}` is not an object of this package"))?;
let root = object.as_reflect();
let key = root.type_info().type_key;
let title = identity::title_of_reflect(root).unwrap_or_default().to_string();
let followed;
let trajectory: &dyn Reflect = match key {
TRAJECTORY => root,
FRAME => {
let Some(FieldValue::Object(reference)) = root.field("Trajectory") else {
return Err(format!("`{title}` is a wellbore frame that names no trajectory"));
};
let uri = crate::follow::address_of(reference).ok_or_else(|| {
format!("`{title}` is a wellbore frame whose trajectory reference carries no uuid")
})?;
followed = source.object_at(&uri).ok_or_else(|| {
format!("`{title}` is a wellbore frame whose trajectory {uri} is not in this package")
})?;
followed.as_reflect()
}
other => {
return Err(format!(
"`{id}` is a `{other}`: only a wellbore trajectory or a wellbore frame has a head to project"
));
}
};
let [x, y, _] = well::head_of(trajectory, source).map_err(|why| format!("{why}"))?;
let crs = crs::of(trajectory, source).unwrap_or(None);
let has_geometry = crate::geometry::field_object(trajectory, "Geometry").is_some();
let (xy, system) = place(x, y, crs.as_ref(), has_geometry, placement)?;
Ok((
WellHead {
id: id.to_string(),
title,
xy,
},
system,
))
}
fn place(
x: f64,
y: f64,
crs: Option<&Crs>,
has_geometry: bool,
placement: Placement,
) -> std::result::Result<([f64; 2], System), String> {
let epsg = crs.and_then(|crs| crs.projected.epsg());
let uom = crs.and_then(|crs| crs.horizontal_uom.clone());
match placement {
Placement::Projected => Ok(([x, y], System { epsg, uom })),
Placement::Metres => {
let convert = |value: f64| match &uom {
Some(unit) => {
units::metres(value, unit).map_err(|why| format!("the horizontal unit `{unit}`: {why}"))
}
None => Ok(value),
};
Ok(([convert(x)?, convert(y)?], System { epsg, uom: None }))
}
Placement::Local => match crs {
Some(crs) if has_geometry => {
let [lx, ly, _] = crs.from_projected([x, y, 0.0]);
Ok(([lx, ly], System { epsg, uom }))
}
_ => Ok(([x, y], System { epsg, uom })),
},
}
}