use std::{
borrow::Cow,
fmt::{Display, Formatter, Write},
sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};
use once_cell::sync::Lazy;
use crate::{
checker::{Checker, Compare},
expr::{Operator, PostProcessor},
roll::{DiceRoll, GurgleRoll, ItemRoll, RollTree, RollTreeNode},
};
static WANTED_LANG: AtomicUsize = AtomicUsize::new(Language::EN.value());
static CUSTOM_LANG_PTR: AtomicPtr<OutputSpans> =
AtomicPtr::new(std::ptr::null::<OutputSpans>() as *mut _);
static LANG: Lazy<Cow<'static, OutputSpans>> =
Lazy::new(
|| match Language::from_value(WANTED_LANG.load(Ordering::SeqCst)) {
Language::EN => Cow::Owned(OutputSpans::new_en()),
Language::ZhCN => Cow::Owned(OutputSpans::new_zh_cn()),
Language::Custom => Cow::Borrowed(Language::get_global_custom().unwrap()),
},
);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Language {
EN,
ZhCN,
Custom,
}
impl Language {
const fn value(&self) -> usize {
match *self {
Self::EN => 0,
Self::ZhCN => 1,
Self::Custom => 999,
}
}
fn from_value(value: usize) -> Self {
match value {
0 => Self::EN,
1 => Self::ZhCN,
999 => Self::Custom,
_ => panic!("Can't convert {} into Language", value),
}
}
#[allow(clippy::needless_pass_by_value)] pub fn set_global(lang: Self) {
if lang == Self::Custom {
panic!("Call set global with custom is invalid, you should use `set_global_custom` instead");
}
WANTED_LANG.store(lang.value(), Ordering::SeqCst);
}
pub fn set_global_custom(s: OutputSpans) {
WANTED_LANG.store(Self::Custom.value(), Ordering::SeqCst);
let p = Box::into_raw(Box::new(s));
let last = CUSTOM_LANG_PTR.swap(p, Ordering::SeqCst);
if !last.is_null() {
panic!("`set_global_custom` can only be called once");
}
}
fn get_global_custom() -> Option<&'static OutputSpans> {
let p = CUSTOM_LANG_PTR.load(Ordering::SeqCst);
if p.is_null() {
None
} else {
Some(unsafe { &*p })
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OutputSpans {
pub comma: Cow<'static, str>,
pub target_is: Cow<'static, str>,
pub success: Cow<'static, str>,
pub failed: Cow<'static, str>,
}
impl OutputSpans {
#[must_use]
pub fn new_en() -> Self {
Self {
comma: ", ".into(),
target_is: "target is".into(),
success: "success".into(),
failed: "failed".into(),
}
}
#[must_use]
pub fn new_zh_cn() -> Self {
Self {
comma: ",".into(),
target_is: "ç›®æ ‡ä¸º".into(),
success: "通过".into(),
failed: "失败".into(),
}
}
}
impl Display for Checker {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self.compare {
Compare::Gte => ">=",
Compare::Gt => ">",
Compare::Lte => "<=",
Compare::Lt => "<",
Compare::Eq => "=",
})?;
f.write_fmt(format_args!("{}", self.target))
}
}
impl Display for DiceRoll {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let (prefix, mid, postfix) = match self.post_processor() {
PostProcessor::Sum => ("", "+", ""),
PostProcessor::Avg => ("Avg[", ",", "]"),
PostProcessor::Max => ("Max[", ",", "]"),
PostProcessor::Min => ("Min[", ",", "]"),
};
f.write_char('(')?;
f.write_str(prefix)?;
let last = self.len() - 1;
for (i, value) in self.points().iter().enumerate() {
f.write_fmt(format_args!("{}", value))?;
if i != last {
f.write_str(mid)?;
}
}
f.write_str(postfix)?;
if self.post_processor() != PostProcessor::Sum {
f.write_fmt(format_args!("={}", self.value()))?
}
f.write_char(')')
}
}
impl Display for ItemRoll {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Number(x) => f.write_fmt(format_args!("{}", x)),
Self::Dice(dice) => f.write_fmt(format_args!("{}", dice)),
Self::Parentheses(e) => f.write_fmt(format_args!("({})", e.as_ref())),
}
}
}
impl Display for RollTree {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let op = match self.mid {
Operator::Add => "+",
Operator::Minus => "-",
Operator::Multiply => "*",
};
f.write_fmt(format_args!("{} {} {}", self.left, op, self.right))
}
}
impl Display for RollTreeNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Leaf(leaf) => f.write_fmt(format_args!("{}", leaf)),
Self::Tree(tree) => f.write_fmt(format_args!("{}", tree)),
}
}
}
impl Display for GurgleRoll<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.expr()))?;
if !std::matches!(self.expr(), RollTreeNode::Leaf(ItemRoll::Number(_))) {
f.write_fmt(format_args!(" = {}", self.value()))?;
}
if let Some(c) = self.checker() {
f.write_str(&LANG.comma)?;
f.write_str(&LANG.target_is)?;
f.write_fmt(format_args!("{}", c))?;
f.write_str(&LANG.comma)?;
if self.success().unwrap() {
f.write_str(&LANG.success)?;
} else {
f.write_str(&LANG.failed)?;
}
}
Ok(())
}
}