use rayon::prelude::*;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use super::types::{VTexPhase, VTexProgress};
use super::utils::find_gts_path;
use super::{GtsFile, VirtualTextureExtractor};
use crate::error::Error;
#[derive(Debug, Clone)]
pub struct GtsExtractResult {
pub texture_count: usize,
pub gtp_count: usize,
}
#[derive(Debug, Clone)]
pub struct BatchExtractResult {
pub success_count: usize,
pub error_count: usize,
pub texture_count: usize,
pub results: Vec<String>,
}
pub fn extract_gts_file<P, F>(
input_path: P,
output_dir: Option<&Path>,
progress: F,
) -> Result<GtsExtractResult, Error>
where
P: AsRef<Path>,
F: Fn(&VTexProgress),
{
let input_path = input_path.as_ref();
let input_path_str = input_path.to_string_lossy();
let input_ext = input_path
.extension()
.map(|e| e.to_string_lossy().to_lowercase())
.unwrap_or_default();
let is_single_gtp = input_ext == "gtp";
progress(&VTexProgress::new(VTexPhase::ReadingMetadata, 0, 1));
let gts_path_str = find_gts_path(&input_path_str)?;
let gts_path = PathBuf::from(>s_path_str);
let output_path = match output_dir {
Some(dir) => dir.to_path_buf(),
None => input_path
.parent()
.map_or_else(|| PathBuf::from("."), std::path::Path::to_path_buf),
};
let input_stem = input_path.file_stem().map_or_else(
|| "textures".to_string(),
|n| n.to_string_lossy().to_string(),
);
let texture_output_dir = output_path.join(&input_stem);
std::fs::create_dir_all(&texture_output_dir).map_err(|e| {
Error::Io(std::io::Error::other(format!(
"Failed to create output directory: {e}"
)))
})?;
if is_single_gtp {
progress(&VTexProgress::with_file(
VTexPhase::ExtractingTiles,
1,
1,
"Extracting GTP",
));
VirtualTextureExtractor::extract_with_gts(input_path, >s_path, &texture_output_dir)?;
progress(&VTexProgress::new(VTexPhase::Complete, 1, 1));
let count = std::fs::read_dir(&texture_output_dir)
.map(|entries| entries.filter_map(std::result::Result::ok).count())
.unwrap_or(0);
Ok(GtsExtractResult {
texture_count: count,
gtp_count: 1,
})
} else {
let gts = GtsFile::open(>s_path)?;
let gts_dir = gts_path.parent().unwrap_or(Path::new("."));
let total_page_files = gts.page_files.len();
if total_page_files == 0 {
return Err(Error::InvalidFormat(
"No page files found in GTS".to_string(),
));
}
let mut extracted_count = 0;
let mut failed_count = 0;
for (i, page_file) in gts.page_files.iter().enumerate() {
let gtp_path = gts_dir.join(&page_file.filename);
progress(&VTexProgress::with_file(
VTexPhase::ExtractingTiles,
i + 1,
total_page_files,
&page_file.filename,
));
if gtp_path.exists() {
let gtp_stem = Path::new(&page_file.filename)
.file_stem()
.map_or_else(|| format!("gtp_{i}"), |n| n.to_string_lossy().to_string());
let gtp_output_dir = texture_output_dir.join(>p_stem);
match VirtualTextureExtractor::extract_with_gts(
>p_path,
>s_path,
>p_output_dir,
) {
Ok(()) => extracted_count += 1,
Err(e) => {
let filename = &page_file.filename;
tracing::warn!("Failed to extract {filename}: {e}");
failed_count += 1;
}
}
} else {
let path_display = gtp_path.display();
tracing::warn!("GTP file not found: {path_display}");
failed_count += 1;
}
}
progress(&VTexProgress::new(
VTexPhase::Complete,
total_page_files,
total_page_files,
));
if extracted_count == 0 && total_page_files > 0 {
return Err(Error::InvalidFormat(format!(
"No GTP files could be extracted (0/{total_page_files} succeeded, {failed_count} failed)"
)));
}
Ok(GtsExtractResult {
texture_count: extracted_count,
gtp_count: total_page_files,
})
}
}
pub fn extract_batch<F>(
gts_files: &[PathBuf],
output_dir: Option<&Path>,
progress: F,
) -> BatchExtractResult
where
F: Fn(&VTexProgress) + Send + Sync,
{
let total = gts_files.len();
let success_counter = AtomicUsize::new(0);
let error_counter = AtomicUsize::new(0);
let texture_counter = AtomicUsize::new(0);
let processed = AtomicUsize::new(0);
let results: Vec<String> = gts_files
.par_iter()
.map(|gts_path| {
let gts_name = gts_path.file_name().map_or_else(
|| "unknown".to_string(),
|n| n.to_string_lossy().to_string(),
);
let current = processed.fetch_add(1, Ordering::SeqCst) + 1;
progress(&VTexProgress::with_file(
VTexPhase::ExtractingTiles,
current,
total,
>s_name,
));
let noop_progress = |_: &VTexProgress| {};
match extract_gts_file(gts_path, output_dir, noop_progress) {
Ok(result) => {
success_counter.fetch_add(1, Ordering::SeqCst);
texture_counter.fetch_add(result.texture_count, Ordering::SeqCst);
format!(
"Extracted {} textures from {}",
result.texture_count, gts_name
)
}
Err(e) => {
error_counter.fetch_add(1, Ordering::SeqCst);
format!("Failed {gts_name}: {e}")
}
}
})
.collect();
BatchExtractResult {
success_count: success_counter.load(Ordering::SeqCst),
error_count: error_counter.load(Ordering::SeqCst),
texture_count: texture_counter.load(Ordering::SeqCst),
results,
}
}