use crate::blockpedia::{errors::*, BlockFacts, Result, BLOCKS};
use std::collections::HashMap;
pub fn find_blocks_by_property(
property: &str,
value: &str,
) -> impl Iterator<Item = &'static BlockFacts> {
let property = property.to_string();
let value = value.to_string();
BLOCKS
.values()
.filter(move |block| {
block.get_property(&property) == Some(value.as_str())
|| block
.get_property_values(&property)
.map(|values| values.contains(&value))
.unwrap_or(false)
})
.copied()
}
pub fn find_blocks_matching<F>(predicate: F) -> impl Iterator<Item = &'static BlockFacts>
where
F: Fn(&BlockFacts) -> bool,
{
BLOCKS
.values()
.filter(move |block| predicate(block))
.copied()
}
pub fn search_blocks(pattern: &str) -> impl Iterator<Item = &'static BlockFacts> {
let pattern = pattern.to_lowercase();
BLOCKS
.values()
.filter(move |block| {
let block_id = block.id().to_lowercase();
if pattern.contains('*') {
let parts: Vec<&str> = pattern.split('*').collect();
if parts.is_empty() {
return true;
}
let mut search_pos = 0;
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
if i == 0 {
if !block_id.starts_with(part) {
return false;
}
search_pos = part.len();
} else if i == parts.len() - 1 {
if !block_id.ends_with(part) {
return false;
}
} else {
if let Some(pos) = block_id[search_pos..].find(part) {
search_pos += pos + part.len();
} else {
return false;
}
}
}
true
} else {
block_id.contains(&pattern)
}
})
.copied()
}
pub fn get_property_values(property: &str) -> Option<Vec<String>> {
let mut all_values = std::collections::HashSet::new();
let mut found_property = false;
for block in BLOCKS.values() {
if let Some(values) = block.get_property_values(property) {
found_property = true;
for value in values {
all_values.insert(value);
}
}
}
if found_property {
let mut sorted_values: Vec<String> = all_values.into_iter().collect();
sorted_values.sort();
Some(sorted_values)
} else {
None
}
}
pub fn count_blocks_where<F>(predicate: F) -> usize
where
F: Fn(&BlockFacts) -> bool,
{
BLOCKS
.values()
.filter(move |block| predicate(block))
.count()
}
pub fn get_block_families() -> HashMap<String, Vec<String>> {
let mut families = HashMap::new();
for block in BLOCKS.values() {
let id = block.id();
if let Some(colon_pos) = id.find(':') {
let name_part = &id[colon_pos + 1..];
let family_name = if name_part.ends_with("_stairs") {
"stairs"
} else if name_part.ends_with("_slab") {
"slab"
} else if name_part.ends_with("_wool") {
"wool"
} else if name_part.ends_with("_log") {
"log"
} else if name_part.ends_with("_planks") {
"planks"
} else if name_part.ends_with("_leaves") {
"leaves"
} else if name_part.ends_with("_door") {
"door"
} else if name_part.ends_with("_fence") {
"fence"
} else if name_part.ends_with("_wall") {
"wall"
} else if name_part.contains("_wood") {
"wood"
} else if name_part.contains("stone") && !name_part.contains("redstone") {
"stone"
} else {
name_part
};
families
.entry(family_name.to_string())
.or_insert_with(Vec::new)
.push(id.to_string());
}
}
for blocks in families.values_mut() {
blocks.sort();
}
families
}
pub fn blocks_with_properties(
properties: &[(&str, &str)],
) -> impl Iterator<Item = &'static BlockFacts> {
let properties: Vec<(String, String)> = properties
.iter()
.map(|(prop, value)| (prop.to_string(), value.to_string()))
.collect();
BLOCKS
.values()
.filter(move |block| {
properties.iter().all(|(prop, value)| {
if value == "*" {
block.has_property(prop)
} else {
block.get_property(prop) == Some(value.as_str())
|| block
.get_property_values(prop)
.map(|values| values.contains(value))
.unwrap_or(false)
}
})
})
.copied()
}
pub fn find_rare_properties(max_frequency: f64) -> HashMap<String, usize> {
let total_blocks = BLOCKS.len();
let mut property_counts = HashMap::new();
for block in BLOCKS.values() {
for (property, _) in block.properties {
*property_counts.entry(property.to_string()).or_insert(0) += 1;
}
}
property_counts
.into_iter()
.filter(|(_, count)| (*count as f64 / total_blocks as f64) < max_frequency)
.collect()
}
#[derive(Debug)]
pub struct PropertyStats {
pub total_unique_properties: usize,
pub most_common_property: (String, usize),
pub blocks_with_no_properties: usize,
pub average_properties_per_block: f64,
}
pub fn get_property_stats() -> PropertyStats {
let mut property_counts = HashMap::new();
let mut blocks_with_no_properties = 0;
let mut total_property_instances = 0;
for block in BLOCKS.values() {
if block.properties.is_empty() {
blocks_with_no_properties += 1;
} else {
total_property_instances += block.properties.len();
for (property, _) in block.properties {
*property_counts.entry(property.to_string()).or_insert(0) += 1;
}
}
}
let most_common_property = property_counts
.iter()
.max_by_key(|(_, count)| *count)
.map(|(prop, count)| (prop.clone(), *count))
.unwrap_or(("none".to_string(), 0));
PropertyStats {
total_unique_properties: property_counts.len(),
most_common_property,
blocks_with_no_properties,
average_properties_per_block: total_property_instances as f64 / BLOCKS.len() as f64,
}
}
pub fn get_enhanced_block_families() -> HashMap<String, Vec<String>> {
let mut families = HashMap::new();
for block in BLOCKS.values() {
let id = block.id();
if let Some(colon_pos) = id.find(':') {
let name_part = &id[colon_pos + 1..];
let family_name = detect_block_family(name_part);
families
.entry(family_name.to_string())
.or_insert_with(Vec::new)
.push(id.to_string());
}
}
for blocks in families.values_mut() {
blocks.sort();
}
families
}
fn detect_block_family(name_part: &str) -> &str {
if name_part.ends_with("_stairs") {
return "stairs";
}
if name_part.ends_with("_slab") {
return "slab";
}
if name_part.ends_with("_wall") {
return "wall";
}
if name_part.ends_with("_fence") {
return "fence";
}
if name_part.ends_with("_fence_gate") {
return "fence_gate";
}
if name_part.ends_with("_door") {
return "door";
}
if name_part.ends_with("_trapdoor") {
return "trapdoor";
}
if name_part.ends_with("_button") {
return "button";
}
if name_part.ends_with("_pressure_plate") {
return "pressure_plate";
}
if name_part.ends_with("_wood") || name_part.ends_with("_log") {
return "wood";
}
if name_part.ends_with("_planks") {
return "planks";
}
if name_part.ends_with("_leaves") {
return "leaves";
}
if name_part.ends_with("_sapling") {
return "sapling";
}
if name_part.ends_with("_wool") {
return "wool";
}
if name_part.ends_with("_carpet") {
return "carpet";
}
if name_part.ends_with("_concrete") {
return "concrete";
}
if name_part.ends_with("_concrete_powder") {
return "concrete_powder";
}
if name_part.ends_with("_terracotta") {
return "terracotta";
}
if name_part.ends_with("_glazed_terracotta") {
return "glazed_terracotta";
}
if name_part.ends_with("_glass") {
return "glass";
}
if name_part.ends_with("_glass_pane") {
return "glass_pane";
}
if name_part.ends_with("_stained_glass") {
return "stained_glass";
}
if name_part.ends_with("_stained_glass_pane") {
return "stained_glass_pane";
}
if name_part.contains("stone")
&& !name_part.contains("redstone")
&& !name_part.contains("sandstone")
{
return "stone";
}
if name_part.contains("sandstone") {
return "sandstone";
}
if name_part.contains("granite") {
return "granite";
}
if name_part.contains("diorite") {
return "diorite";
}
if name_part.contains("andesite") {
return "andesite";
}
if name_part.contains("redstone") {
return "redstone";
}
if name_part.ends_with("_ore") {
return "ore";
}
if name_part.starts_with("raw_") {
return "raw_materials";
}
if name_part.contains("_ingot") || name_part.contains("_nugget") {
return "metals";
}
if name_part.ends_with("_sword") {
return "sword";
}
if name_part.ends_with("_pickaxe") {
return "pickaxe";
}
if name_part.ends_with("_axe") && !name_part.ends_with("_pickaxe") {
return "axe";
}
if name_part.ends_with("_shovel") {
return "shovel";
}
if name_part.ends_with("_hoe") {
return "hoe";
}
if name_part.ends_with("_helmet") {
return "helmet";
}
if name_part.ends_with("_chestplate") {
return "chestplate";
}
if name_part.ends_with("_leggings") {
return "leggings";
}
if name_part.ends_with("_boots") {
return "boots";
}
if name_part.contains("bread") || name_part.contains("cake") || name_part.contains("cookie") {
return "food";
}
name_part
}
pub fn blocks_with_complex_properties(
requirements: &[(String, Vec<String>)],
) -> impl Iterator<Item = &'static BlockFacts> {
let requirements: Vec<(String, Vec<String>)> = requirements.to_vec();
BLOCKS
.values()
.filter(move |block| {
requirements.iter().all(|(prop, values)| {
if let Some(block_values) = block.get_property_values(prop) {
values
.iter()
.any(|required_val| block_values.contains(required_val))
} else {
false
}
})
})
.copied()
}
pub fn analyze_property_correlation() -> HashMap<String, Vec<(String, f64)>> {
let mut correlations = HashMap::new();
let mut property_pairs = HashMap::new();
let mut individual_properties = HashMap::new();
for block in BLOCKS.values() {
let block_properties: Vec<String> = block
.properties
.iter()
.map(|(p, _)| p.to_string())
.collect();
for prop in &block_properties {
*individual_properties.entry(prop.clone()).or_insert(0) += 1;
}
for i in 0..block_properties.len() {
for j in (i + 1)..block_properties.len() {
let pair = if block_properties[i] < block_properties[j] {
(block_properties[i].clone(), block_properties[j].clone())
} else {
(block_properties[j].clone(), block_properties[i].clone())
};
*property_pairs.entry(pair).or_insert(0) += 1;
}
}
}
for ((prop1, prop2), pair_count) in property_pairs {
let prop1_count = individual_properties.get(&prop1).unwrap_or(&0);
let prop2_count = individual_properties.get(&prop2).unwrap_or(&0);
if *prop1_count > 0 && *prop2_count > 0 {
let correlation = pair_count as f64 / (*prop1_count as f64).min(*prop2_count as f64);
correlations
.entry(prop1.clone())
.or_insert_with(Vec::new)
.push((prop2.clone(), correlation));
correlations
.entry(prop2)
.or_insert_with(Vec::new)
.push((prop1, correlation));
}
}
for correlations_list in correlations.values_mut() {
correlations_list
.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
}
correlations
}
pub fn find_similar_blocks(
target_block_id: &str,
min_shared_properties: usize,
) -> Vec<(&'static BlockFacts, usize)> {
let target_block = match BLOCKS.get(target_block_id) {
Some(block) => block,
None => return Vec::new(),
};
let target_properties: std::collections::HashSet<&str> =
target_block.properties.iter().map(|(p, _)| *p).collect();
let mut similar_blocks = Vec::new();
for block in BLOCKS.values() {
if block.id() == target_block_id {
continue; }
let block_properties: std::collections::HashSet<&str> =
block.properties.iter().map(|(p, _)| *p).collect();
let shared_count = target_properties.intersection(&block_properties).count();
if shared_count >= min_shared_properties {
similar_blocks.push((*block, shared_count));
}
}
similar_blocks.sort_by(|a, b| b.1.cmp(&a.1));
similar_blocks
}
#[derive(Debug)]
pub struct AdvancedPropertyStats {
pub basic_stats: PropertyStats,
pub property_distribution: HashMap<String, HashMap<String, usize>>, pub most_diverse_property: (String, usize), pub most_correlated_properties: Vec<(String, String, f64)>, }
pub fn get_advanced_property_stats() -> AdvancedPropertyStats {
let basic_stats = get_property_stats();
let mut property_distribution = HashMap::new();
for block in BLOCKS.values() {
for (property, _) in block.properties {
if let Some(values) = block.get_property_values(property) {
let prop_dist = property_distribution
.entry(property.to_string())
.or_insert_with(HashMap::new);
for value in values {
*prop_dist.entry(value).or_insert(0) += 1;
}
}
}
}
let most_diverse_property = property_distribution
.iter()
.max_by_key(|(_, values)| values.len())
.map(|(prop, values)| (prop.clone(), values.len()))
.unwrap_or(("none".to_string(), 0));
let correlations = analyze_property_correlation();
let mut all_correlations = Vec::new();
for (prop1, correlations_list) in correlations {
for (prop2, correlation) in correlations_list {
if prop1 < prop2 {
all_correlations.push((prop1.clone(), prop2, correlation));
}
}
}
all_correlations.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
let most_correlated_properties = all_correlations.into_iter().take(5).collect();
AdvancedPropertyStats {
basic_stats,
property_distribution,
most_diverse_property,
most_correlated_properties,
}
}
pub mod validated {
use super::*;
pub fn find_blocks_by_property_safe(
property: &str,
value: &str,
) -> Result<Vec<&'static BlockFacts>> {
validation::validate_property_name(property)?;
validation::validate_property_value(value)?;
let results: Vec<_> = find_blocks_by_property(property, value).collect();
if results.is_empty() {
return Err(BlockpediaError::Query(QueryError::NoResults(format!(
"No blocks found with property '{}' = '{}'",
property, value
))));
}
Ok(results)
}
pub fn search_blocks_safe(pattern: &str) -> Result<Vec<&'static BlockFacts>> {
if pattern.is_empty() {
return Err(BlockpediaError::invalid_format(
pattern,
"non-empty search pattern",
));
}
if pattern.len() > 128 {
return Err(BlockpediaError::Validation(
ValidationError::InvalidLength {
input: pattern.to_string(),
min_length: 1,
max_length: 128,
},
));
}
let invalid_chars: Vec<char> = pattern
.chars()
.filter(|c| {
!c.is_ascii_alphanumeric() && *c != '_' && *c != '-' && *c != ':' && *c != '*'
})
.collect();
if !invalid_chars.is_empty() {
return Err(BlockpediaError::Validation(
ValidationError::InvalidCharacters {
input: pattern.to_string(),
invalid_chars,
},
));
}
let results: Vec<_> = search_blocks(pattern).collect();
if results.is_empty() {
let suggestions = recovery::suggest_similar_blocks(pattern);
let suggestion_text = if suggestions.is_empty() {
"No suggestions available".to_string()
} else {
format!("Suggestions: {}", suggestions.join(", "))
};
return Err(BlockpediaError::Query(QueryError::NoResults(format!(
"No blocks match pattern '{}'. {}",
pattern, suggestion_text
))));
}
Ok(results)
}
pub fn get_property_values_safe(property: &str) -> Result<Vec<String>> {
validation::validate_property_name(property)?;
get_property_values(property).ok_or_else(|| {
BlockpediaError::Property(PropertyError::NotFound {
block_id: "any".to_string(),
property: property.to_string(),
})
})
}
pub fn validate_block_properties_safe(
block_id: &str,
properties: &[(String, String)],
) -> Result<()> {
validation::validate_block_id(block_id)?;
let block_facts = BLOCKS
.get(block_id)
.ok_or_else(|| BlockpediaError::block_not_found(block_id))?;
let mut errors = Vec::new();
for (property, value) in properties {
if let Err(e) = validation::validate_property_name(property) {
errors.push(format!("Property '{}': {}", property, e));
continue;
}
if let Err(e) = validation::validate_property_value(value) {
errors.push(format!("Value '{}': {}", value, e));
continue;
}
if !block_facts.has_property(property) {
errors.push(format!(
"Property '{}' does not exist on block '{}'",
property, block_id
));
continue;
}
if let Some(valid_values) = block_facts.get_property_values(property) {
if !valid_values.contains(value) {
errors.push(format!(
"Invalid value '{}' for property '{}'. Valid values: {:?}",
value, property, valid_values
));
}
}
}
if !errors.is_empty() {
return Err(BlockpediaError::State(StateError::ValidationFailed {
state: format!("{}[properties]", block_id),
errors,
}));
}
Ok(())
}
pub fn create_block_state_safe(
block_id: &str,
properties: &[(String, String)],
) -> Result<crate::blockpedia::BlockState> {
validate_block_properties_safe(block_id, properties)?;
let mut state = crate::blockpedia::BlockState::new(block_id)?;
for (property, value) in properties {
state = state.with(property, value)?;
}
Ok(state)
}
pub fn query_with_timeout<F, R>(query_name: &str, query_fn: F) -> Result<R>
where
F: FnOnce() -> R,
{
if query_name.len() > 64 {
return Err(BlockpediaError::Query(QueryError::InvalidSyntax(
"Query name too long".to_string(),
)));
}
Ok(query_fn())
}
}