use once_cell::sync::Lazy;
use std::sync::{RwLock, Mutex};
use std::collections::HashMap;
use super::prelude::*;
use regex::Regex;
use crate::tags;
pub static KONFIG_DATA: Lazy<RwLock<Konfig>> = Lazy::new(|| {
RwLock::new(Konfig::new_internal())
});
pub static KONFIG_WRITE_LOCK: Lazy<Mutex<()>> = Lazy::new(|| {
Mutex::new(())
});
fn get_base_stat_name_from_path(full_path: &str) -> &str {
let path_without_target = full_path.split('@').next().unwrap_or(full_path);
path_without_target.split('.').next().unwrap_or(path_without_target)
}
pub struct Konfig {
stat_types_map: HashMap<String, String>,
stat_types_regex_rules: Vec<(Regex, String)>,
stat_types_default: String,
relationship_types_map: HashMap<String, ModType>,
relationship_types_regex_rules: Vec<(Regex, ModType)>,
relationship_types_default: ModType,
total_expressions_map: HashMap<String, String>,
total_expressions_regex_rules: Vec<(Regex, String)>,
total_expressions_default: String,
tag_set_resolvers: HashMap<String, Box<dyn TagSet + Send + Sync>>,
default_tag_set_resolver: Option<Box<dyn TagSet + Send + Sync>>,
}
impl Konfig {
fn new_internal() -> Self {
Self {
stat_types_map: HashMap::new(),
stat_types_regex_rules: Vec::new(),
stat_types_default: "Modifiable".to_string(),
relationship_types_map: HashMap::new(),
relationship_types_regex_rules: Vec::new(),
relationship_types_default: ModType::Add,
total_expressions_map: HashMap::new(),
total_expressions_regex_rules: Vec::new(),
total_expressions_default: "0".to_string(),
tag_set_resolvers: HashMap::new(),
default_tag_set_resolver: None,
}
}
pub fn get_stat_type(path_key: &str) -> String {
let reader = KONFIG_DATA.read().unwrap();
if let Some(value) = reader.stat_types_map.get(path_key) {
return value.clone();
}
for (regex, value) in &reader.stat_types_regex_rules {
if regex.is_match(path_key) {
return value.clone();
}
}
reader.stat_types_default.clone()
}
pub fn register_stat_type(stat: &str, stat_type: &str) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.stat_types_map.insert(stat.to_string(), stat_type.to_string());
}
pub fn register_stat_type_regex(pattern: &str, value: &str) -> Result<(), regex::Error> {
let regex = Regex::new(pattern)?;
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.stat_types_regex_rules.push((regex, value.to_string()));
Ok(())
}
pub fn set_stat_type_default(default_value: &str) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.stat_types_default = default_value.to_string();
}
pub fn get_relationship_type(path_key: &str) -> ModType {
let reader = KONFIG_DATA.read().unwrap();
if let Some(value) = reader.relationship_types_map.get(path_key) {
return value.clone();
}
for (regex, value) in &reader.relationship_types_regex_rules {
if regex.is_match(path_key) {
return value.clone();
}
}
reader.relationship_types_default.clone()
}
pub fn register_relationship_type(stat_path_part: &str, relationship: ModType) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.relationship_types_map.insert(stat_path_part.to_string(), relationship);
}
pub fn register_relationship_type_regex(pattern: &str, value: ModType) -> Result<(), regex::Error> {
let regex = Regex::new(pattern)?;
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.relationship_types_regex_rules.push((regex, value));
Ok(())
}
pub fn set_relationship_type_default(default_value: ModType) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.relationship_types_default = default_value;
}
pub fn get_total_expression(path_key: &str) -> String {
let reader = KONFIG_DATA.read().unwrap();
if let Some(value) = reader.total_expressions_map.get(path_key) {
return value.clone();
}
for (regex, value) in &reader.total_expressions_regex_rules {
if regex.is_match(path_key) {
return value.clone();
}
}
reader.total_expressions_default.clone()
}
pub fn register_total_expression(stat: &str, expression: &str) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.total_expressions_map.insert(stat.to_string(), expression.to_string());
}
pub fn register_total_expression_regex(pattern: &str, value: &str) -> Result<(), regex::Error> {
let regex = Regex::new(pattern)?;
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.total_expressions_regex_rules.push((regex, value.to_string()));
Ok(())
}
pub fn set_total_expression_default(default_value: &str) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.total_expressions_default = default_value.to_string();
}
pub fn register_tag_set(stat_base_name: &str, resolver: Box<dyn TagSet + Send + Sync>) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.tag_set_resolvers.insert(stat_base_name.to_string(), resolver);
}
pub fn set_default_tag_set(resolver: Box<dyn TagSet + Send + Sync>) {
let _guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut writer = KONFIG_DATA.write().unwrap();
writer.default_tag_set_resolver = Some(resolver);
}
pub(crate) fn internal_get_tag_resolver_for_stat_name(&self, stat_name: &str) -> Option<&(dyn TagSet + Send + Sync)> {
self.tag_set_resolvers.get(stat_name)
.map(|boxed_resolver| boxed_resolver.as_ref()) .or_else(|| self.default_tag_set_resolver.as_deref())
}
pub fn process_path(path_str: &str) -> String {
let has_potential_string_tag = path_str.contains(".{") ||
(path_str.starts_with('{') && path_str.ends_with('}'));
if !has_potential_string_tag {
return path_str.to_string();
}
let base_name = get_base_stat_name_from_path(path_str);
let konfig_instance_guard = KONFIG_DATA.read().unwrap();
if let Some(resolver) = konfig_instance_guard.internal_get_tag_resolver_for_stat_name(base_name) {
tags::process_tag(path_str, resolver)
} else {
path_str.to_string()
}
}
pub fn reset_for_test() {
let _write_serialization_guard = KONFIG_WRITE_LOCK.lock().unwrap();
let mut konfig_writer = KONFIG_DATA.write().unwrap();
*konfig_writer = Konfig::new_internal();
}
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use super::*;
struct MockTagSet;
impl TagSet for MockTagSet {
fn match_tag(&self, _tag_str: &str) -> u32 { 0 }
fn tag_category_for_bit(&self, _tag_bit: u32) -> u32 { 0 }
fn tag_category_for_group(&self, _group_tag: u32) -> u32 { 0 }
fn all_defined_groups(&self) -> &'static [u32] { &[] }
}
#[test]
#[serial]
fn test_konfig_access_and_registration() {
Konfig::reset_for_test();
Konfig::register_stat_type("Life", "Complex");
Konfig::register_relationship_type("Strength", ModType::Mul);
Konfig::register_total_expression("Mana", "base * (1 + increased)");
assert_eq!(Konfig::get_stat_type("Life"), "Complex");
assert_eq!(Konfig::get_relationship_type("Strength"), ModType::Mul);
assert_eq!(Konfig::get_total_expression("Mana"), "base * (1 + increased)");
assert_eq!(Konfig::get_stat_type("Dexterity"), "Modifiable"); }
#[test]
#[serial]
fn test_stat_type_regex_and_default() {
Konfig::reset_for_test();
Konfig::set_stat_type_default("CustomDefault");
Konfig::register_stat_type_regex(r"\.\d+$", "Complex").unwrap();
Konfig::register_stat_type_regex(r"(?i)(current)", "Flat").unwrap();
Konfig::register_stat_type_regex(r"(?i)(base|added|increased)", "Complex").unwrap();
Konfig::register_stat_type("CurrentBananas", "Complex");
assert_eq!(Konfig::get_stat_type("$[Life.current]"), "Flat"); assert_eq!(Konfig::get_stat_type("CurrentBananas"), "Complex"); assert_eq!(Konfig::get_stat_type("SomeOtherStat"), "CustomDefault"); }
#[test]
#[serial]
fn test_tag_set_registration_and_retrieval() {
Konfig::reset_for_test();
let specific_resolver = Box::new(MockTagSet);
Konfig::register_tag_set("Damage", specific_resolver);
let default_resolver = Box::new(MockTagSet);
Konfig::set_default_tag_set(default_resolver);
{
let reader = KONFIG_DATA.read().unwrap(); assert!(reader.internal_get_tag_resolver_for_stat_name("Damage").is_some(), "Damage specific resolver should exist");
assert!(reader.internal_get_tag_resolver_for_stat_name("UnknownStat").is_some(), "Default resolver should be used for UnknownStat");
}
struct PathProcessingTagSet;
impl TagSet for PathProcessingTagSet {
fn match_tag(&self, tag_str: &str) -> u32 {
match tag_str {
"FIRE" => 1,
"COLD" => 2,
_ => 0, }
}
fn build_permissive_mask(&self, tag_expression_str: &str) -> u32 {
if tag_expression_str == "FIRE" { 1 }
else if tag_expression_str == "COLD" { 2 }
else if tag_expression_str == "FIRE|COLD" { 3 } else { tag_expression_str.parse().unwrap_or(u32::MAX) } }
fn tag_category_for_bit(&self, _tag_bit: u32) -> u32 { 0 }
fn tag_category_for_group(&self, _group_tag: u32) -> u32 { 0 }
fn all_defined_groups(&self) -> &'static [u32] { &[] }
}
Konfig::register_tag_set("TestStat", Box::new(PathProcessingTagSet));
assert_eq!(Konfig::process_path("TestStat.part.{FIRE}"), "TestStat.part.1");
assert_eq!(Konfig::process_path("TestStat.part.{COLD}@Player"), "TestStat.part.2@Player");
assert_eq!(Konfig::process_path("TestStat.part.{FIRE|COLD}"), "TestStat.part.3");
assert_eq!(Konfig::process_path("TestStat.part.{99}"), "TestStat.part.{99}"); assert_eq!(Konfig::process_path("TestStat.part.ActualNumericalTag"), "TestStat.part.ActualNumericalTag");
Konfig::set_default_tag_set(Box::new(PathProcessingTagSet));
assert_eq!(Konfig::process_path("AnyStat.value.{FIRE}"), "AnyStat.value.1"); assert_eq!(Konfig::process_path("{FIRE}"), "1"); }
}