use std::collections::HashMap;
use crate::error::{M2Error, Result};
use crate::model::M2Model;
use crate::version::M2Version;
pub struct M2Converter {
conversion_paths: HashMap<(M2Version, M2Version), Vec<M2Version>>,
}
impl M2Converter {
pub fn new() -> Self {
let mut converter = Self {
conversion_paths: HashMap::new(),
};
converter.build_conversion_paths();
converter
}
pub fn convert(&self, model: &M2Model, target_version: M2Version) -> Result<M2Model> {
let source_version = model.header.version().ok_or(M2Error::ConversionError {
from: model.header.version,
to: target_version.to_header_version(),
reason: "Unknown source version".to_string(),
})?;
if source_version == target_version {
return Ok(model.clone());
}
if source_version.has_direct_conversion_path(&target_version) {
return model.convert(target_version);
}
if let Some(path) = self.get_conversion_path(source_version, target_version) {
let mut current_model = model.clone();
for step_version in path {
current_model = current_model.convert(*step_version)?;
}
Ok(current_model)
} else {
Err(M2Error::ConversionError {
from: source_version.to_header_version(),
to: target_version.to_header_version(),
reason: "No conversion path found".to_string(),
})
}
}
fn build_conversion_paths(&mut self) {
let versions = [
M2Version::Vanilla,
M2Version::TBC,
M2Version::WotLK,
M2Version::Cataclysm,
M2Version::MoP,
M2Version::WoD,
M2Version::Legion,
M2Version::BfA,
M2Version::Shadowlands,
M2Version::Dragonflight,
M2Version::TheWarWithin,
];
for &from_version in &versions {
for &to_version in &versions {
if from_version == to_version {
continue;
}
if from_version.has_direct_conversion_path(&to_version) {
self.conversion_paths
.insert((from_version, to_version), vec![to_version]);
continue;
}
let mut path = Vec::new();
let direction = if to_version > from_version { 1 } else { -1 };
let start_idx = versions.iter().position(|&v| v == from_version).unwrap();
let end_idx = versions.iter().position(|&v| v == to_version).unwrap();
if direction > 0 {
for &version in &versions[(start_idx + 1)..=end_idx] {
path.push(version);
}
} else {
for &version in versions[end_idx..start_idx].iter().rev() {
path.push(version);
}
}
self.conversion_paths
.insert((from_version, to_version), path);
}
}
}
fn get_conversion_path(&self, from: M2Version, to: M2Version) -> Option<&Vec<M2Version>> {
self.conversion_paths.get(&(from, to))
}
}
impl Default for M2Converter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conversion_paths() {
let converter = M2Converter::new();
let path = converter
.get_conversion_path(M2Version::Vanilla, M2Version::Cataclysm)
.unwrap();
assert_eq!(
path,
&vec![M2Version::TBC, M2Version::WotLK, M2Version::Cataclysm]
);
let path = converter
.get_conversion_path(M2Version::Vanilla, M2Version::MoP)
.unwrap();
assert_eq!(
path,
&vec![
M2Version::TBC,
M2Version::WotLK,
M2Version::Cataclysm,
M2Version::MoP
]
);
let path = converter
.get_conversion_path(M2Version::TheWarWithin, M2Version::Vanilla)
.unwrap();
assert!(path.len() > 1);
let versions = [
M2Version::Vanilla,
M2Version::TBC,
M2Version::WotLK,
M2Version::Cataclysm,
M2Version::MoP,
M2Version::WoD,
M2Version::Legion,
M2Version::BfA,
M2Version::Shadowlands,
M2Version::Dragonflight,
M2Version::TheWarWithin,
];
for &from_version in &versions {
for &to_version in &versions {
if from_version == to_version {
continue;
}
assert!(
converter
.get_conversion_path(from_version, to_version)
.is_some()
);
}
}
}
}