use encoding_rs_io::DecodeReaderBytesBuilder;
use std::{
io::{BufRead, BufReader},
path::Path,
};
use tracing::{debug, error, trace, warn};
use crate::{
creature_variation::CreatureVariation,
options::ParserOptions,
parser::{
entity::Entity,
graphics::{Graphic, GraphicTypeToken, TilePage, GRAPHIC_TYPE_TOKEN_MAP},
inorganic::Inorganic,
material_template::MaterialTemplate,
module_info_file::ModuleInfoFile,
plant::Plant,
DF_ENCODING, PARSABLE_OBJECT_TYPES, RAW_TOKEN_RE, {ObjectType, OBJECT_TOKEN_MAP},
{RawMetadata, RawObject},
},
unprocessed_raw::{Modification, UnprocessedRaw},
util::try_get_file,
ParserError, RawModuleLocation,
};
use super::header::read_raw_file_type;
pub struct FileParseResults {
pub parsed_raws: Vec<Box<dyn RawObject>>,
pub unprocessed_raws: Vec<UnprocessedRaw>,
}
pub fn parse_raw_file<P: AsRef<Path>>(
raw_file_path: &P,
options: &ParserOptions,
) -> Result<FileParseResults, ParserError> {
let mod_info_file = match ModuleInfoFile::from_raw_file_path(raw_file_path) {
Ok(m) => m,
Err(e) => {
warn!(
"parse_raw_file: Using an empty ModuleInfoFile because of error parsing the file"
);
debug!("{e:?}");
ModuleInfoFile::new(
raw_file_path
.as_ref()
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or(""),
RawModuleLocation::Unknown,
"none",
)
}
};
parse_raw_file_with_info(raw_file_path, &mod_info_file, options)
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn parse_raw_file_with_info<P: AsRef<Path>>(
raw_file_path: &P,
mod_info_file: &ModuleInfoFile,
options: &ParserOptions,
) -> Result<FileParseResults, ParserError> {
let mut created_raws: Vec<Box<dyn RawObject>> = Vec::new();
let mut unprocessed_raws: Vec<UnprocessedRaw> = Vec::new();
let file = try_get_file(raw_file_path)?;
let decoding_reader = DecodeReaderBytesBuilder::new()
.encoding(Some(*DF_ENCODING))
.build(file);
let reader = BufReader::new(decoding_reader);
let mut started = false;
let mut raw_filename = String::new();
let mut temp_plant = Plant::empty();
let mut temp_inorganic = Inorganic::empty();
let mut temp_graphic = Graphic::empty();
let mut temp_material_template = MaterialTemplate::empty();
let mut temp_entity = Entity::empty();
let mut temp_creature_variation = CreatureVariation::empty();
let mut temp_unprocessed_raw = UnprocessedRaw::default();
let mut last_parsed_type = ObjectType::Unknown;
let mut last_graphic_type = GraphicTypeToken::Unknown;
let mut temp_tile_page = TilePage::empty();
let mut current_modification = Modification::MainRawBody { raws: Vec::new() };
let object_type = read_raw_file_type(raw_file_path)?;
let mut raw_metadata = RawMetadata::new(
mod_info_file,
&object_type,
raw_filename.as_str(),
&raw_file_path,
options.attach_metadata_to_raws,
);
if !options.object_types_to_parse.contains(&object_type) {
debug!(
"parse_raw_file_with_info: Quitting early because object type {:?} is not included in options!",
object_type
);
return Ok(FileParseResults {
parsed_raws: Vec::new(),
unprocessed_raws: Vec::new(),
});
}
if !PARSABLE_OBJECT_TYPES.contains(&&object_type) {
debug!(
"parse_raw_file_with_info: Quitting early because object type {:?} is not parsable!",
object_type
);
return Ok(FileParseResults {
parsed_raws: Vec::new(),
unprocessed_raws: Vec::new(),
});
}
for (index, line) in reader.lines().enumerate() {
if line.is_err() {
error!(
"parse_raw_file_with_info: Error processing {}:{}",
raw_file_path.as_ref().display(),
index
);
continue;
}
let line = match line {
Ok(l) => l,
Err(e) => {
error!("parse_raw_file_with_info: Line-reading error\n{:?}", e);
continue;
}
};
if index == 0 {
raw_filename = String::from(&line);
raw_metadata = RawMetadata::new(
mod_info_file,
&object_type,
raw_filename.as_str(),
&raw_file_path,
options.attach_metadata_to_raws,
);
continue;
}
for cap in RAW_TOKEN_RE.captures_iter(&line) {
let captured_key = match cap.get(2) {
Some(v) => v.as_str(),
_ => {
continue;
}
};
let captured_value = match cap.get(3) {
Some(v) => v.as_str(),
_ => {
continue;
}
};
trace!(
"parse_raw_file_with_info: Key: {} Value: {}",
captured_key,
captured_value
);
match captured_key {
"OBJECT" => {
if !OBJECT_TOKEN_MAP.contains_key(captured_value) {
error!(
"parse_raw_file_with_info: Unknown object type: {} Raw: {}",
captured_value.to_uppercase(),
raw_filename
);
return Err(ParserError::InvalidRawFile(format!(
"Unknown object type: {}",
captured_value.to_uppercase()
)));
}
if &object_type
!= OBJECT_TOKEN_MAP
.get(captured_value)
.unwrap_or(&ObjectType::Unknown)
{
error!(
"parse_raw_file_with_info: Object type mismatch: {} != {}",
object_type,
captured_value.to_uppercase()
);
return Err(ParserError::InvalidRawFile(format!(
"Object type mismatch: {} != {}",
object_type,
captured_value.to_uppercase()
)));
}
}
"CREATURE" | "SELECT_CREATURE" => {
if started && last_parsed_type == ObjectType::Entity {
temp_entity.parse_tag(captured_key, captured_value);
continue;
}
if started
&& (last_parsed_type == ObjectType::Creature
|| last_parsed_type == ObjectType::CreatureCaste
|| last_parsed_type == ObjectType::SelectCreature)
{
temp_unprocessed_raw.add_modification(current_modification.clone());
unprocessed_raws.push(temp_unprocessed_raw.clone());
} else {
started = true;
}
temp_unprocessed_raw =
UnprocessedRaw::new(&ObjectType::Creature, &raw_metadata, captured_value);
current_modification = Modification::MainRawBody { raws: Vec::new() };
last_parsed_type = ObjectType::Creature;
}
"CREATURE_VARIATION" => {
if started && last_parsed_type == ObjectType::CreatureVariation {
created_raws.push(Box::new(temp_creature_variation.clone()));
} else {
started = true;
}
temp_creature_variation =
CreatureVariation::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::CreatureVariation;
}
"CASTE" | "SELECT_CASTE" => {
if object_type != ObjectType::Creature
&& object_type != ObjectType::Entity
&& object_type != ObjectType::Graphics
{
continue;
}
if started && object_type == ObjectType::Entity {
temp_entity.parse_tag(captured_key, captured_value);
continue;
}
current_modification.add_raw(format!("{captured_key}:{captured_value}"));
last_parsed_type = ObjectType::CreatureCaste;
}
"PLANT" => {
if started {
created_raws.push(Box::new(temp_plant.clone()));
} else {
started = true;
}
temp_plant = Plant::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::Plant;
}
"INORGANIC" | "SELECT_INORGANIC" => {
if started {
created_raws.push(Box::new(temp_inorganic.clone()));
} else {
started = true;
}
temp_inorganic = Inorganic::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::Inorganic;
}
"MATERIAL_TEMPLATE" => {
if started {
created_raws.push(Box::new(temp_material_template.clone()));
} else {
started = true;
}
temp_material_template =
MaterialTemplate::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::MaterialTemplate;
}
"CREATURE_GRAPHICS"
| "CREATURE_CASTE_GRAPHICS"
| "TILE_GRAPHICS"
| "PLANT_GRAPHICS" => {
if started {
created_raws.push(Box::new(temp_graphic.clone()));
} else {
started = true;
}
last_parsed_type = ObjectType::Graphics;
last_graphic_type = *GRAPHIC_TYPE_TOKEN_MAP
.get(captured_key)
.unwrap_or(&GraphicTypeToken::Unknown);
temp_graphic =
Graphic::new(captured_value, &raw_metadata.clone(), last_graphic_type);
}
"TILE_PAGE" => {
if started {
created_raws.push(Box::new(temp_tile_page.clone()));
} else {
started = true;
}
temp_tile_page = TilePage::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::TilePage;
}
"ENTITY" => {
if started {
created_raws.push(Box::new(temp_entity.clone()));
} else {
started = true;
}
temp_entity = Entity::new(captured_value, &raw_metadata.clone());
last_parsed_type = ObjectType::Entity;
}
"GO_TO_END" => {
debug!("began tracking AddToEnding modification");
temp_unprocessed_raw.add_modification(current_modification.clone());
current_modification = Modification::AddToEnding { raws: Vec::new() };
}
"GO_TO_START" => {
debug!("began tracking AddToBeginning modification");
temp_unprocessed_raw.add_modification(current_modification.clone());
current_modification = Modification::AddToBeginning { raws: Vec::new() };
}
"GO_TO_TAG" => {
debug!("began tracking AddBeforeTag:{captured_value} modification");
temp_unprocessed_raw.add_modification(current_modification.clone());
current_modification = Modification::AddBeforeTag {
tag: captured_value.to_string(),
raws: Vec::new(),
};
}
"COPY_TAGS_FROM" => {
debug!("began tracking CopyTagsFrom:{captured_value} modification");
temp_unprocessed_raw.add_modification(Modification::CopyTagsFrom {
identifier: captured_value.to_string(),
});
}
"APPLY_CREATURE_VARIATION" => {
debug!("began tracking ApplyCreatureVariation:{captured_value} modification");
temp_unprocessed_raw.add_modification(Modification::ApplyCreatureVariation {
identifier: captured_value.to_string(),
});
}
_ => {
if started {
match last_parsed_type {
ObjectType::Creature
| ObjectType::CreatureCaste
| ObjectType::SelectCreature => {
current_modification
.add_raw(format!("{captured_key}:{captured_value}"));
}
ObjectType::CreatureVariation => {
temp_creature_variation.parse_tag(captured_key, captured_value);
}
ObjectType::Plant => {
temp_plant.parse_tag(captured_key, captured_value);
}
ObjectType::Inorganic => {
temp_inorganic.parse_tag(captured_key, captured_value);
}
ObjectType::MaterialTemplate => {
temp_material_template.parse_tag(captured_key, captured_value);
}
ObjectType::Graphics => {
if temp_graphic.get_graphic_type() == GraphicTypeToken::Tile {
last_graphic_type = *GRAPHIC_TYPE_TOKEN_MAP
.get(captured_key)
.unwrap_or(&GraphicTypeToken::Unknown);
}
temp_graphic.parse_sprite_from_tag(
captured_key,
captured_value,
last_graphic_type,
);
}
ObjectType::TilePage => {
temp_tile_page.parse_tag(captured_key, captured_value);
}
ObjectType::Entity => {
temp_entity.parse_tag(captured_key, captured_value);
}
_ => {
}
}
}
}
}
}
}
if started {
if !temp_unprocessed_raw.is_empty() {
temp_unprocessed_raw.add_modification(current_modification.clone());
unprocessed_raws.push(temp_unprocessed_raw.clone());
}
if !temp_plant.is_empty() {
created_raws.push(Box::new(temp_plant.clone()));
}
if !temp_inorganic.is_empty() {
created_raws.push(Box::new(temp_inorganic.clone()));
}
if !temp_material_template.is_empty() {
created_raws.push(Box::new(temp_material_template.clone()));
}
if !temp_graphic.is_empty() {
created_raws.push(Box::new(temp_graphic.clone()));
}
if !temp_tile_page.is_empty() {
created_raws.push(Box::new(temp_tile_page.clone()));
}
if !temp_entity.is_empty() {
created_raws.push(Box::new(temp_entity.clone()));
}
if !temp_creature_variation.is_empty() {
created_raws.push(Box::new(temp_creature_variation.clone()));
}
}
debug!(
"parse_raw_file_with_info: Parsed {} raws from {}",
created_raws.len(),
raw_filename
);
Ok(FileParseResults {
parsed_raws: created_raws,
unprocessed_raws,
})
}