use super::block_string::{parse_custom_name, parse_items_array};
use super::container_spec::get_container_spec;
use crate::nbt::{NbtMap, NbtValue};
use std::collections::HashMap;
pub fn parse_generic_nbt(nbt_str: &str) -> Result<HashMap<String, NbtValue>, String> {
let mut nbt_map = HashMap::new();
let fields = split_nbt_fields(nbt_str);
for field in fields {
let field = field.trim();
if field.is_empty() {
continue;
}
if field.starts_with("Items:[") || field.starts_with("CustomName:") {
continue;
}
if let Some((key, value_str)) = field.split_once(':') {
let key = key.trim();
let value_str = value_str.trim();
if key == "signal" || key == "item" {
continue;
}
let value = parse_nbt_value(value_str)?;
nbt_map.insert(key.to_string(), value);
}
}
Ok(nbt_map)
}
fn parse_nbt_value(value_str: &str) -> Result<NbtValue, String> {
let value_str = value_str.trim();
if value_str.starts_with('"') && value_str.ends_with('"') {
let s = value_str[1..value_str.len() - 1].to_string();
return Ok(NbtValue::String(s));
}
if value_str.ends_with('b') || value_str.ends_with('B') {
let num_str = &value_str[..value_str.len() - 1];
if let Ok(byte_val) = num_str.parse::<i8>() {
return Ok(NbtValue::Byte(byte_val));
}
}
if value_str.ends_with('s') || value_str.ends_with('S') {
let num_str = &value_str[..value_str.len() - 1];
if let Ok(short_val) = num_str.parse::<i16>() {
return Ok(NbtValue::Short(short_val));
}
}
if let Some(num_str) = value_str.strip_suffix('L') {
if let Ok(long_val) = num_str.parse::<i64>() {
return Ok(NbtValue::Long(long_val));
}
}
if value_str.ends_with('f') || value_str.ends_with('F') {
let num_str = &value_str[..value_str.len() - 1];
if let Ok(float_val) = num_str.parse::<f32>() {
return Ok(NbtValue::Float(float_val));
}
}
if value_str.ends_with('d') || value_str.ends_with('D') {
let num_str = &value_str[..value_str.len() - 1];
if let Ok(double_val) = num_str.parse::<f64>() {
return Ok(NbtValue::Double(double_val));
}
}
if value_str.contains('.') {
if let Ok(double_val) = value_str.parse::<f64>() {
return Ok(NbtValue::Double(double_val));
}
}
if let Ok(int_val) = value_str.parse::<i32>() {
return Ok(NbtValue::Int(int_val));
}
Ok(NbtValue::String(value_str.to_string()))
}
fn split_nbt_fields(s: &str) -> Vec<String> {
let mut fields = Vec::new();
let mut current = String::new();
let mut in_quotes = false;
let mut bracket_depth = 0;
let mut brace_depth = 0;
for c in s.chars() {
match c {
'"' => {
in_quotes = !in_quotes;
current.push(c);
}
'[' if !in_quotes => {
bracket_depth += 1;
current.push(c);
}
']' if !in_quotes => {
bracket_depth -= 1;
current.push(c);
}
'{' if !in_quotes => {
brace_depth += 1;
current.push(c);
}
'}' if !in_quotes => {
brace_depth -= 1;
current.push(c);
}
',' if !in_quotes && bracket_depth == 0 && brace_depth == 0 => {
if !current.trim().is_empty() {
fields.push(current.clone());
}
current.clear();
}
_ => {
current.push(c);
}
}
}
if !current.trim().is_empty() {
fields.push(current);
}
fields
}
pub fn parse_signal_params(nbt_str: &str) -> Option<(u8, Option<String>)> {
let fields = split_nbt_fields(nbt_str);
let mut signal = None;
let mut item = None;
for field in fields {
let field = field.trim();
if let Some((key, value)) = field.split_once('=') {
let key = key.trim();
let value = value.trim();
match key {
"signal" => {
signal = value.parse::<u8>().ok();
}
"item" => {
item = Some(value.to_string());
}
_ => {}
}
}
}
signal.map(|s| (s, item))
}
pub fn create_container_items_nbt(
container_slots: u32,
signal_strength: u8,
item_id: Option<&str>,
) -> Vec<NbtValue> {
if signal_strength == 0 {
return Vec::new();
}
let item_id = if let Some(id) = item_id {
if id.starts_with("minecraft:") {
id.to_string()
} else {
format!("minecraft:{}", id)
}
} else {
"minecraft:redstone_block".to_string()
};
const MAX_STACK: u32 = 64;
const MAX_SIGNAL: u32 = 14;
let total_capacity = container_slots * MAX_STACK;
let calculated = (total_capacity as f64 / MAX_SIGNAL as f64) * (signal_strength as f64 - 1.0);
let items_needed = calculated.ceil() as u32;
let total_items = std::cmp::max(signal_strength as u32, items_needed);
let total_items = std::cmp::min(total_items, total_capacity);
let mut items = Vec::new();
let mut remaining_items = total_items;
let mut slot: u8 = 0;
while remaining_items > 0 && (slot as u32) < container_slots {
let stack_size = std::cmp::min(remaining_items, MAX_STACK);
let mut item_nbt = NbtMap::new();
item_nbt.insert("count".to_string(), NbtValue::Int(stack_size as i32));
item_nbt.insert("Slot".to_string(), NbtValue::Byte(slot as i8));
item_nbt.insert("id".to_string(), NbtValue::String(item_id.clone()));
items.push(NbtValue::Compound(item_nbt));
remaining_items -= stack_size;
slot += 1;
}
items
}
fn get_jukebox_disc(signal: u8) -> Option<&'static str> {
match signal {
0 => None,
1 => Some("minecraft:music_disc_13"),
2 => Some("minecraft:music_disc_cat"),
3 => Some("minecraft:music_disc_blocks"),
4 => Some("minecraft:music_disc_chirp"),
5 => Some("minecraft:music_disc_far"),
6 => Some("minecraft:music_disc_mall"),
7 => Some("minecraft:music_disc_mellohi"),
8 => Some("minecraft:music_disc_stal"),
9 => Some("minecraft:music_disc_strad"),
10 => Some("minecraft:music_disc_ward"),
11 => Some("minecraft:music_disc_11"),
12 => Some("minecraft:music_disc_wait"),
13 => Some("minecraft:music_disc_pigstep"),
14 => Some("minecraft:music_disc_otherside"),
15 => Some("minecraft:music_disc_5"),
_ => None,
}
}
pub fn parse_enhanced_nbt(
block_name: &str,
nbt_str: &str,
) -> Result<HashMap<String, NbtValue>, String> {
let mut nbt_map = HashMap::new();
let block_name_stripped = block_name.strip_prefix("minecraft:").unwrap_or(block_name);
if block_name_stripped == "jukebox" {
if let Some((signal, _)) = parse_signal_params(nbt_str) {
if signal > 15 {
return Err("Signal strength must be between 0 and 15".to_string());
}
if let Some(disc) = get_jukebox_disc(signal) {
let mut record_item = NbtMap::new();
record_item.insert("count".to_string(), NbtValue::Int(1));
record_item.insert("id".to_string(), NbtValue::String(disc.to_string()));
nbt_map.insert("RecordItem".to_string(), NbtValue::Compound(record_item));
}
}
}
else if let Some(spec) = get_container_spec(block_name) {
if let Some((signal, custom_item)) = parse_signal_params(nbt_str) {
if signal > 15 {
return Err("Signal strength must be between 0 and 15".to_string());
}
if !nbt_str.contains("Items:[") {
let items = create_container_items_nbt(spec.slots, signal, custom_item.as_deref());
if !items.is_empty() {
nbt_map.insert("Items".to_string(), NbtValue::List(items));
}
}
}
}
if nbt_str.contains("Items:[") {
let items = parse_items_array(nbt_str)?;
nbt_map.insert("Items".to_string(), NbtValue::List(items));
}
if nbt_str.contains("CustomName:") {
let name = parse_custom_name(nbt_str)?;
nbt_map.insert("CustomName".to_string(), NbtValue::String(name));
}
let generic_nbt = parse_generic_nbt(nbt_str)?;
for (key, value) in generic_nbt {
if key != "Items" || !nbt_map.contains_key("Items") {
nbt_map.insert(key, value);
}
}
Ok(nbt_map)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_nbt_value_types() {
assert!(matches!(
parse_nbt_value("\"hello\""),
Ok(NbtValue::String(_))
));
assert!(matches!(parse_nbt_value("123"), Ok(NbtValue::Int(123))));
assert!(matches!(parse_nbt_value("1b"), Ok(NbtValue::Byte(1))));
assert!(matches!(parse_nbt_value("3.14"), Ok(NbtValue::Double(_))));
}
#[test]
fn test_parse_signal_params() {
let (signal, item) = parse_signal_params("signal=14").unwrap();
assert_eq!(signal, 14);
assert_eq!(item, None);
let (signal, item) = parse_signal_params("signal=10,item=diamond").unwrap();
assert_eq!(signal, 10);
assert_eq!(item, Some("diamond".to_string()));
}
#[test]
fn test_create_container_items_hopper() {
let items = create_container_items_nbt(5, 10, None);
assert!(items.len() <= 5, "Hopper should not exceed 5 slots");
assert!(!items.is_empty(), "Should generate items for signal=10");
}
}