use chematic_core::{Atom, AtomIdx, Element, Molecule, MoleculeBuilder};
use crate::coords::{Coords3D, Point3};
#[derive(Debug, Clone, PartialEq)]
pub enum XyzError {
InvalidAtomCount,
InvalidLine(usize),
UnknownElement(String),
}
impl core::fmt::Display for XyzError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidAtomCount => write!(f, "invalid atom count in XYZ header"),
Self::InvalidLine(n) => write!(f, "invalid XYZ coordinate line {n}"),
Self::UnknownElement(s) => write!(f, "unknown element symbol '{s}' in XYZ file"),
}
}
}
pub fn parse_xyz(input: &str) -> Result<(Molecule, Coords3D), XyzError> {
let mut lines = input.lines();
let count_line = lines.next().unwrap_or("").trim();
let n: usize = count_line
.parse()
.map_err(|_| XyzError::InvalidAtomCount)?;
lines.next();
let mut builder = MoleculeBuilder::new();
let mut points: Vec<Point3> = Vec::with_capacity(n);
for i in 0..n {
let line = lines
.next()
.ok_or(XyzError::InvalidLine(i + 3))?
.trim();
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 4 {
return Err(XyzError::InvalidLine(i + 3));
}
let symbol = parts[0];
let x: f64 = parts[1].parse().map_err(|_| XyzError::InvalidLine(i + 3))?;
let y: f64 = parts[2].parse().map_err(|_| XyzError::InvalidLine(i + 3))?;
let z: f64 = parts[3].parse().map_err(|_| XyzError::InvalidLine(i + 3))?;
let element = Element::from_symbol(symbol)
.ok_or_else(|| XyzError::UnknownElement(symbol.to_string()))?;
builder.add_atom(Atom::new(element));
points.push(Point3::new(x, y, z));
}
let mol = builder.build();
let coords = Coords3D { points };
Ok((mol, coords))
}
pub fn write_xyz(mol: &Molecule, coords: &Coords3D, comment: &str) -> String {
let n = mol.atom_count();
let mut out = String::new();
out.push_str(&n.to_string());
out.push('\n');
out.push_str(comment);
out.push('\n');
for i in 0..n {
let idx = AtomIdx(i as u32);
let atom = mol.atom(idx);
let p = coords.get(idx);
out.push_str(&format!(
"{:<3} {:12.6} {:12.6} {:12.6}\n",
atom.element.symbol(),
p.x,
p.y,
p.z
));
}
out
}