use std::convert::TryFrom;
use std::fmt;
use super::{Aliphatic, Aromatic, BracketSymbol, Charge, Configuration, Element, VirtualHydrogen};
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum AtomKind {
Star,
Aliphatic(Aliphatic),
Aromatic(Aromatic),
Bracket {
isotope: Option<u16>,
symbol: BracketSymbol,
configuration: Option<Configuration>,
hcount: Option<VirtualHydrogen>,
charge: Option<Charge>,
map: Option<u16>,
},
}
impl AtomKind {
pub fn debracket(self, bond_order_sum: u8) -> Self {
let (isotope, symbol, configuration, hcount, charge, map) = match &self {
Self::Star | Self::Aliphatic(_) | Self::Aromatic(_) => return self,
Self::Bracket {
isotope,
symbol,
configuration,
hcount,
charge,
map,
} => (isotope, symbol, configuration, hcount, charge, map),
};
if any(*isotope, *configuration, *charge, *map) {
return self;
}
match symbol {
BracketSymbol::Star => {
hcount.as_ref().map_or(
Self::Star,
|hcount| if hcount.is_zero() { Self::Star } else { self },
)
}
BracketSymbol::Aromatic(aromatic) => {
let hcount = hcount.as_ref().map_or(0, |hcount| hcount.into());
let valence = bond_order_sum.checked_add(hcount).expect("valence");
let allowance = u8::from(hcount != 0);
let aromatic = match Aromatic::try_from(aromatic) {
Ok(aromatic) => aromatic,
Err(()) => return self,
};
for target in aromatic.targets() {
if valence == target - allowance {
return Self::Aromatic(aromatic);
}
}
self
}
BracketSymbol::Element(element) => {
let valence = bond_order_sum
.checked_add(hcount.as_ref().map_or(0, std::convert::Into::into))
.expect("valence");
let aliphatic = match Aliphatic::try_from(element) {
Ok(aliphatic) => aliphatic,
Err(()) => return self,
};
for target in aliphatic.targets() {
if target == &valence {
return Self::Aliphatic(aliphatic);
}
}
self
}
}
}
pub const fn is_aromatic(&self) -> bool {
match self {
Self::Aromatic(_) => true,
Self::Aliphatic(_) | Self::Star => false,
Self::Bracket { symbol, .. } => match symbol {
BracketSymbol::Aromatic(_) => true,
BracketSymbol::Element(_) | BracketSymbol::Star => false,
},
}
}
pub fn targets(&self) -> &[u8] {
match self {
Self::Star => &[],
Self::Aliphatic(aliphatic) => aliphatic.targets(),
Self::Aromatic(aromatic) => aromatic.targets(),
Self::Bracket { symbol, charge, .. } => match symbol {
BracketSymbol::Star => &[],
BracketSymbol::Aromatic(aromatic) => elemental_targets(&aromatic.into(), charge),
BracketSymbol::Element(element) => elemental_targets(element, charge),
},
}
}
pub fn invert_configuration(&mut self) {
if let Self::Bracket {
hcount,
configuration,
..
} = self
{
let new_config = match configuration {
Some(config) => match hcount {
Some(hcount) => {
if hcount.is_zero() {
return;
}
match config {
Configuration::TH1 => Configuration::TH2,
Configuration::TH2 => Configuration::TH1,
_ => unimplemented!("TODO: handle inversion for non-TH"),
}
}
None => return,
},
None => return,
};
configuration.replace(new_config);
}
}
}
const fn any(
isotope: Option<u16>,
configuration: Option<Configuration>,
charge: Option<Charge>,
map: Option<u16>,
) -> bool {
isotope.is_some() || configuration.is_some() || charge.is_some() || map.is_some()
}
fn elemental_targets(element: &Element, charge: &Option<Charge>) -> &'static [u8] {
match element {
Element::B => match charge {
Some(Charge::MinusThree) => &OXYGEN_TARGET,
Some(Charge::MinusTwo) => &NITROGEN_TARGET,
Some(Charge::MinusOne) => &CARBON_TARGET,
None => &BORON_TARGET,
_ => &EMPTY_TARGET,
},
Element::C => match charge {
Some(Charge::MinusTwo) => &OXYGEN_TARGET,
Some(Charge::MinusOne) => &NITROGEN_TARGET,
Some(Charge::One) => &BORON_TARGET,
None => &CARBON_TARGET,
_ => &EMPTY_TARGET,
},
Element::N => match charge {
None => &NITROGEN_TARGET,
Some(Charge::One) => &CARBON_TARGET,
_ => &EMPTY_TARGET,
},
Element::O => match charge {
None => &OXYGEN_TARGET,
Some(Charge::One) => &NITROGEN_TARGET,
_ => &EMPTY_TARGET,
},
Element::P | Element::As => match charge {
Some(Charge::MinusOne) => &SULFUR_TARGET,
None => &PHOSPHOROUS_TARGET,
_ => &EMPTY_TARGET,
},
Element::S | Element::Se => match charge {
None => &SULFUR_TARGET,
Some(Charge::One) => &PHOSPHOROUS_TARGET,
_ => &EMPTY_TARGET,
},
_ => &EMPTY_TARGET,
}
}
static BORON_TARGET: [u8; 1] = [3];
static CARBON_TARGET: [u8; 1] = [4];
static NITROGEN_TARGET: [u8; 2] = [3, 5];
static OXYGEN_TARGET: [u8; 1] = [2];
static PHOSPHOROUS_TARGET: [u8; 2] = [3, 5];
static SULFUR_TARGET: [u8; 3] = [2, 4, 6];
static EMPTY_TARGET: [u8; 0] = [];
impl fmt::Display for AtomKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Star => write!(f, "*"),
Self::Aliphatic(aliphatic) => write!(f, "{}", aliphatic),
Self::Aromatic(aromatic) => write!(f, "{}", aromatic),
Self::Bracket {
isotope,
symbol,
hcount,
configuration,
charge,
map,
} => {
write!(f, "[")?;
if let Some(isotope) = isotope {
write!(f, "{isotope}")?
}
write!(f, "{symbol}")?;
if let Some(configuration) = configuration {
write!(f, "{configuration}")?
}
if let Some(hcount) = hcount {
write!(f, "{hcount}")?
}
if let Some(charge) = charge {
write!(f, "{charge}")?
}
if let Some(map) = map {
write!(f, ":{map}")?
}
write!(f, "]")
}
}
}
}
#[cfg(test)]
mod invert {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn aliphatic_organic() {
let mut kind = AtomKind::Aliphatic(Aliphatic::C);
kind.invert_configuration();
match kind {
AtomKind::Aliphatic(_) => (),
_ => panic!("expected aliphatic"),
}
}
#[test]
fn th1_h_none() {
let mut kind = AtomKind::Bracket {
isotope: None,
symbol: BracketSymbol::Star,
configuration: Some(Configuration::TH1),
hcount: None,
charge: None,
map: None,
};
kind.invert_configuration();
match kind {
AtomKind::Bracket { configuration, .. } => {
assert_eq!(configuration, Some(Configuration::TH1))
}
_ => panic!("expected bracket"),
}
}
#[test]
fn th1_h1() {
let mut kind = AtomKind::Bracket {
isotope: None,
symbol: BracketSymbol::Star,
configuration: Some(Configuration::TH1),
hcount: Some(VirtualHydrogen::H1),
charge: None,
map: None,
};
kind.invert_configuration();
match kind {
AtomKind::Bracket { configuration, .. } => {
assert_eq!(configuration, Some(Configuration::TH2))
}
_ => panic!("expected bracket"),
}
}
#[test]
fn th2_h1() {
let mut kind = AtomKind::Bracket {
isotope: None,
symbol: BracketSymbol::Star,
configuration: Some(Configuration::TH2),
hcount: Some(VirtualHydrogen::H1),
charge: None,
map: None,
};
kind.invert_configuration();
match kind {
AtomKind::Bracket { configuration, .. } => {
assert_eq!(configuration, Some(Configuration::TH1))
}
_ => panic!("expected bracket"),
}
}
}