use core::fmt;
use std::any::{type_name, TypeId};
use std::collections::BTreeMap;
use std::hash::Hash;
use std::marker::PhantomData;
use getset::{CopyGetters, Getters, MutGetters};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{AbstractError, Context, NoArgs, Schema, Xylem};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Id<S, X> {
index: u32, _ph: PhantomData<fn() -> (S, X)>,
}
impl<S, X> Id<S, X> {
pub fn new(index: usize) -> Self {
Self { index: index.try_into().expect("Too many identifiers"), _ph: PhantomData }
}
pub fn index(&self) -> usize { self.index.try_into().expect("Too many identifiers") }
}
impl<S, X> fmt::Debug for Id<S, X> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Id({})", self.index) }
}
impl<S, X> Clone for Id<S, X> {
fn clone(&self) -> Self { Self { index: self.index, _ph: PhantomData } }
}
impl<S, X> Copy for Id<S, X> {}
impl<S, X> Default for Id<S, X> {
fn default() -> Self { Self { index: 0, _ph: PhantomData } }
}
impl<S, X> PartialEq for Id<S, X> {
fn eq(&self, other: &Self) -> bool { self.index == other.index }
}
impl<S, X> Eq for Id<S, X> {}
impl<S, X> PartialOrd for Id<S, X> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
}
impl<S, X> Ord for Id<S, X> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.index.cmp(&other.index) }
}
impl<S, X> Hash for Id<S, X> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.index.hash(state); }
}
impl<S: Schema, X: Identifiable<S>> Xylem<S> for Id<S, X> {
type From = String;
type Args = IdArgs;
#[inline]
fn convert_impl(
from: Self::From,
context: &mut <S as Schema>::Context,
args: &Self::Args,
) -> Result<Self, <S as Schema>::Error> {
let index = {
if args.new {
let counter =
context.get_mut::<IdCounter<X>, _>(TypeId::of::<X::Scope>(), Default::default);
if counter.names.iter().any(|other| other == &from) {
return Err(S::Error::new(format_args!("Duplicate ID {}", &from)));
}
let index = counter
.names
.len()
.try_into()
.expect("More than u32::MAX_VALUE IDs registered");
counter.names.push(from.clone());
index
} else {
let index = match context.get::<IdCounter<X>>(TypeId::of::<X::Scope>()) {
Some(counter) => {
let index = counter.names.iter().position(|other| other == &from);
match index {
Some(index) => index,
None => {
return Err(S::Error::new(format_args!("Unknown ID {}", &from)))
}
}
}
None => {
let mut index = None;
for import in context.get_each::<ImportScope>() {
if let Some(id) = import.map.get(&TypeId::of::<X>()) {
let store =
match context.get::<GlobalIdStore<S, X>>(TypeId::of::<()>()) {
Some(store) => store,
None => {
return Err(S::Error::new(format_args!(
"Attempted to import scope for {}, but it was not \
tracked before. Did you forget to \
#[xylem(args(targs = true, track = true))]?",
type_name::<X>()
)));
}
};
let ids = match store.ids.get(id) {
Some(ids) => ids,
None => {
return Err(S::Error::new(
"Scope was successfully imported but the ID is not \
tracked",
));
}
};
index = match ids.iter().position(|id| id == &from) {
Some(index) => Some(index),
None => {
return Err(S::Error::new(format_args!(
"Unknown ID {}",
&from
)))
}
};
break;
}
}
match index {
Some(index) => index,
None => {
return Err(S::Error::new(
"Use of ID before registering the first one. Did you forget \
to #[xylem(args(new = true))] and put it as the first field?",
))
}
}
}
};
let import = context.get_mut::<ImportScope, _>(
context.nth_last_scope(1).expect("Stack too shallow"),
Default::default,
);
for &imported in &args.import {
import.map.insert(imported, vec![index]); }
index.try_into().expect("More than u32::MAX_VALUE IDs registered")
}
};
let id = Id { index, _ph: PhantomData };
if args.new {
let mut new = false;
let current_id = context.get_mut::<CurrentId, _>(TypeId::of::<X>(), || {
new = true;
CurrentId {
id: id.index(),
parent: TypeId::of::<X::Scope>(),
string: from.clone(),
}
});
if !new {
return Err(S::Error::new(format_args!(
"Multiple new IDs defined for {} ({}, {})",
type_name::<X>(),
id.index(),
current_id.id,
)));
}
if args.track {
let mut parent_ids = Vec::new();
let mut next_parent = TypeId::of::<X::Scope>();
while let Some(parent_id) = context.get::<CurrentId>(next_parent) {
parent_ids.push(parent_id.id);
next_parent = parent_id.parent;
}
parent_ids.reverse();
let store =
context.get_mut::<GlobalIdStore<S, X>, _>(TypeId::of::<()>(), Default::default);
store.ids.entry(parent_ids).or_default().push(from);
}
}
Ok(id)
}
}
#[derive(Default)]
pub struct IdArgs {
pub new: bool,
pub track: bool,
pub import: Vec<TypeId>,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IdString<S, X> {
value: String,
_ph: PhantomData<fn() -> (S, X)>,
}
impl<S, X> IdString<S, X> {
pub fn value(&self) -> &str { &self.value }
}
impl<S, X> fmt::Debug for IdString<S, X> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IdString").field("value", &self.value).finish()
}
}
impl<S, X> Clone for IdString<S, X> {
fn clone(&self) -> Self { Self { value: self.value.clone(), _ph: PhantomData } }
}
impl<S: Schema, X: Identifiable<S>> Xylem<S> for IdString<S, X> {
type From = ();
type Args = NoArgs;
#[inline]
fn convert_impl(
(): Self::From,
context: &mut <S as Schema>::Context,
_args: &Self::Args,
) -> Result<Self, <S as Schema>::Error> {
let id = match context.get::<CurrentId>(TypeId::of::<X>()) {
Some(id) => id,
None => {
return Err(S::Error::new(format_args!("No current ID for {}", type_name::<X>())))
}
};
Ok(Self { value: id.string.clone(), _ph: PhantomData })
}
}
struct IdCounter<X: 'static> {
names: Vec<String>,
_ph: PhantomData<&'static X>,
}
impl<X: 'static> IdCounter<X> {}
impl<X: 'static> Default for IdCounter<X> {
fn default() -> Self { Self { names: Vec::new(), _ph: PhantomData } }
}
#[derive(Getters, CopyGetters)]
pub struct CurrentId {
#[getset(get_copy = "pub")]
id: usize,
#[getset(get_copy = "pub")]
parent: TypeId,
#[getset(get = "pub")]
string: String,
}
#[derive(Getters, MutGetters)]
pub struct GlobalIdStore<S: Schema, X: Identifiable<S>> {
#[getset(get = "pub", get_mut = "pub")]
ids: BTreeMap<Vec<usize>, Vec<String>>,
_ph: PhantomData<&'static (S, X)>,
}
impl<S: Schema, X: Identifiable<S>> Default for GlobalIdStore<S, X> {
fn default() -> Self { Self { ids: BTreeMap::new(), _ph: PhantomData } }
}
#[derive(Default)]
struct ImportScope {
map: BTreeMap<TypeId, Vec<usize>>,
}
pub trait Identifiable<S: Schema>: Xylem<S> {
type Scope: Xylem<S>;
fn id(&self) -> Id<S, Self>;
}