use super::Decoded;
use super::bonds::GlobalBond;
use super::validation::{schema_error, usize_of};
use crate::mmtf::schema::Group;
use molframe_core::diagnostic::Diagnostic;
use molframe_core::index::AtomIndex;
use molframe_core::optional::OptionalSymbol;
use molframe_core::{BondOrder, BondProvenance, BondRecord, BondTableBuilder, Element};
pub(super) fn add_group_bonds(
group: &Group,
atom_offset: usize,
keep_map: &[Option<AtomIndex>],
bonds: &mut BondTableBuilder,
) -> Result<(), Diagnostic> {
if !group.bond_atom_list.len().is_multiple_of(2) {
return Err(schema_error("group bond endpoint list has odd length"));
}
for (bond_index, pair) in group.bond_atom_list.as_chunks::<2>().0.iter().enumerate() {
let first = checked_atom_offset(atom_offset, pair[0])?;
let second = checked_atom_offset(atom_offset, pair[1])?;
let Some((atom_a, atom_b)) = retained_atoms(keep_map, first, second) else {
continue;
};
bonds.push(BondRecord {
atom_a,
atom_b,
order: decoded_order(
&group.bond_order_list,
&group.bond_resonance_list,
bond_index,
),
provenance: BondProvenance::File,
kind: OptionalSymbol::NONE,
});
}
Ok(())
}
pub(super) fn add_global_bonds(
global_bonds: &[GlobalBond],
keep_map: &[Option<AtomIndex>],
bonds: &mut BondTableBuilder,
) -> Result<(), Diagnostic> {
for bond in global_bonds {
let first = usize::try_from(bond.atom_a)
.map_err(|_| schema_error("MMTF local bond endpoint exceeds usize"))?;
let second = usize::try_from(bond.atom_b)
.map_err(|_| schema_error("MMTF local bond endpoint exceeds usize"))?;
let Some((atom_a, atom_b)) = retained_atoms(keep_map, first, second) else {
continue;
};
bonds.push(BondRecord {
atom_a,
atom_b,
order: bond.order,
provenance: BondProvenance::File,
kind: OptionalSymbol::NONE,
});
}
Ok(())
}
pub(super) fn retained_atoms(
keep_map: &[Option<AtomIndex>],
first: usize,
second: usize,
) -> Option<(AtomIndex, AtomIndex)> {
match (keep_map.get(first), keep_map.get(second)) {
(Some(Some(atom_a)), Some(Some(atom_b))) => Some((*atom_a, *atom_b)),
_ => None,
}
}
pub(super) fn checked_atom_offset(atom_offset: usize, local: i32) -> Result<usize, Diagnostic> {
atom_offset
.checked_add(usize_of(local)?)
.ok_or_else(|| schema_error("MMTF atom index overflows"))
}
pub(super) fn decoded_order(orders: &[i32], resonance: &[i32], index: usize) -> BondOrder {
if resonance.get(index) == Some(&1) {
return BondOrder::Aromatic;
}
match orders.get(index).copied() {
Some(1) => BondOrder::Single,
Some(2) => BondOrder::Double,
Some(3) => BondOrder::Triple,
Some(4) => BondOrder::Quadruple,
_ => BondOrder::Unknown,
}
}
pub(super) fn group_at(decoded: &Decoded, group_index: usize) -> Result<&Group, Diagnostic> {
let type_index = decoded
.group_type
.get(group_index)
.copied()
.ok_or_else(|| schema_error("group type missing"))
.and_then(usize_of)?;
decoded
.file
.group_list
.get(type_index)
.ok_or_else(|| schema_error("group type index is out of bounds"))
}
pub(super) fn group_element(group: &Group, local: usize) -> Element {
match group
.element_list
.as_ref()
.and_then(|elements| elements.get(local))
.and_then(|symbol| Element::from_symbol(symbol))
{
Some(element) => element,
None => Element::UNKNOWN,
}
}
pub(super) fn atoms_in_groups(
decoded: &Decoded,
start: usize,
count: usize,
) -> Result<usize, Diagnostic> {
let end = start
.checked_add(count)
.ok_or_else(|| schema_error("MMTF group range overflows"))?;
let mut atoms = 0usize;
for group_index in start..end {
let group_atoms = group_at(decoded, group_index)?.atom_name_list.len();
atoms = atoms
.checked_add(group_atoms)
.ok_or_else(|| schema_error("MMTF atom count overflows"))?;
}
Ok(atoms)
}