use std::fmt::Display;
use ltk_meta::PropertyKind;
use crate::{HashType, RitobinName as _, ScalarType};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ContainerType {
Map(Option<(ScalarType, ScalarType)>),
Container(Option<ScalarType>),
OrderedList(Option<ScalarType>),
Optional(Option<ScalarType>),
}
impl ContainerType {
pub fn with_hash_types(self, hash_types: [HashType; 2]) -> Self {
match self {
ContainerType::Map(types) => Self::Map(types.map(|(key, value)| {
(
key.with_hash_type(hash_types[0]),
value.with_hash_type(hash_types[1]),
)
})),
ContainerType::Container(sub) => {
Self::Container(sub.map(|sub| sub.with_hash_type(hash_types[0])))
}
ContainerType::OrderedList(sub) => {
Self::OrderedList(sub.map(|sub| sub.with_hash_type(hash_types[0])))
}
ContainerType::Optional(sub) => {
Self::Optional(sub.map(|sub| sub.with_hash_type(hash_types[0])))
}
}
}
pub fn with_default_hash_types(self) -> Self {
self.with_hash_types([HashType::DEFAULT, HashType::DEFAULT])
}
pub const fn base_kind(&self) -> PropertyKind {
use PropertyKind as K;
match self {
ContainerType::Map(..) => K::Map,
ContainerType::Container(..) => K::Container,
ContainerType::OrderedList(..) => K::UnorderedContainer,
ContainerType::Optional(..) => K::Optional,
}
}
pub const fn subtype(&self, n: usize) -> Option<ScalarType> {
match self {
Self::Map(Some((key, value))) => match n {
0 => Some(*key),
1 => Some(*value),
_ => None,
},
Self::Container(sub) if n == 0 => *sub,
Self::OrderedList(sub) if n == 0 => *sub,
Self::Optional(sub) if n == 0 => *sub,
_ => None,
}
}
}
impl Display for ContainerType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let base = self.base_kind().to_rito_name();
match self {
Self::Map(Some((key, value))) => write!(f, "{base}[{key},{value}]"),
Self::Map(None) => write!(f, "{base}"),
Self::Container(item) | Self::OrderedList(item) | Self::Optional(item) => match item {
Some(item) => write!(f, "{base}[{item}]"),
None => write!(f, "{base}"),
},
}
}
}
impl ContainerType {
pub fn from_parts(base: PropertyKind, subtypes: [Option<PropertyKind>; 2]) -> Option<Self> {
let [first, second] =
subtypes.map(|sub| sub.and_then(|k| ScalarType::try_from_kind(k).ok()));
Some(match base {
PropertyKind::Container => Self::Container(first),
PropertyKind::UnorderedContainer => Self::OrderedList(first),
PropertyKind::Optional => Self::Optional(first),
PropertyKind::Map => Self::Map(first.zip(second)),
_ => return None,
})
}
pub const fn try_from_parts(
base: PropertyKind,
subtypes: (PropertyKind, Option<PropertyKind>),
) -> Result<Self, BadContainerType> {
use BadContainerType as E;
let item = match base {
PropertyKind::Container | PropertyKind::UnorderedContainer | PropertyKind::Optional => {
if subtypes.1.is_some() {
return Err(E::BadSubtypeCount {
base,
got: 2,
expected: 1,
});
}
match ScalarType::try_from_kind(subtypes.0) {
Ok(item) => Some(item),
Err(e) => return Err(E::NonScalarSubtype { base, got: e.0 }),
}
}
_ => None,
};
match base {
PropertyKind::Container => Ok(Self::Container(item)),
PropertyKind::UnorderedContainer => Ok(Self::OrderedList(item)),
PropertyKind::Optional => Ok(Self::Optional(item)),
PropertyKind::Map => {
if !subtypes.0.is_valid_map_key() {
return Err(E::BadMapKey { got: subtypes.0 });
}
let key = match ScalarType::try_from_kind(subtypes.0) {
Ok(key) => key,
Err(e) => return Err(E::NonScalarSubtype { base, got: e.0 }),
};
let value = match subtypes.1 {
Some(value) => match ScalarType::try_from_kind(value) {
Ok(value) => value,
Err(e) => return Err(E::NonScalarSubtype { base, got: e.0 }),
},
None => {
return Err(E::BadSubtypeCount {
base,
got: 1,
expected: 2,
})
}
};
Ok(Self::Map(Some((key, value))))
}
got => Err(E::NonContainerBase { got }),
}
}
}
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BadContainerType {
#[error("{got:?} is not a container type")]
NonContainerBase {
got: PropertyKind,
},
#[error("{base:?} expects {expected} subtypes, got {got}")]
BadSubtypeCount {
base: PropertyKind,
got: u8,
expected: u8,
},
#[error("cannot hold {got:?} in {base:?} - containers can only hold scalars.")]
NonScalarSubtype {
base: PropertyKind,
got: PropertyKind,
},
#[error("{got:?} is not a valid map key")]
BadMapKey {
got: PropertyKind,
},
}
impl From<ContainerType> for PropertyKind {
fn from(value: ContainerType) -> Self {
value.base_kind()
}
}
impl ContainerType {
pub fn equivalent_to(&self, other: &Self) -> bool {
match (self, other) {
(Self::Map(l0), Self::Map(r0)) => l0.zip(*r0).is_none_or(|(a, b)| a == b),
(Self::Container(l0), Self::Container(r0)) => l0.zip(*r0).is_none_or(|(a, b)| a == b),
(Self::OrderedList(l0), Self::OrderedList(r0)) => {
l0.zip(*r0).is_none_or(|(a, b)| a == b)
}
(Self::Optional(l0), Self::Optional(r0)) => l0.zip(*r0).is_none_or(|(a, b)| a == b),
_ => false,
}
}
}