use crate::error::BytaryResult;
use crate::format::Format;
use pathfinding::prelude::dijkstra;
use std::collections::HashMap;
use std::io;
use std::io::{Read, Write};
use std::rc::Rc;
type ConvertFn = dyn Fn(&mut dyn Read, &mut dyn Write) -> BytaryResult<()>;
pub struct ConversionGraph {
graph: HashMap<Format, HashMap<Format, (Rc<ConvertFn>, u32)>>,
}
impl ConversionGraph {
pub fn compose(converters: Vec<Rc<ConvertFn>>) -> Rc<ConvertFn> {
if converters.len() == 1 {
return converters[0].clone();
}
Rc::new(move |input: &mut dyn Read, output: &mut dyn Write| {
let mut prev_output: Box<dyn Read> = Box::new(input);
for converter in converters.iter().take(converters.len().saturating_sub(1)) {
let mut buffer = Vec::new();
converter(&mut prev_output, &mut buffer)?;
prev_output = Box::new(io::Cursor::new(buffer));
}
if let Some(last_processor) = converters.last() {
last_processor(&mut prev_output, output)?;
}
Ok(())
})
}
pub fn get_copy_converter() -> Rc<ConvertFn> {
Rc::new(|r, w| {
io::copy(r, w)?;
Ok(())
})
}
pub fn new() -> Self {
Self {
graph: HashMap::new(),
}
}
pub fn size(&self) -> usize {
self.graph.iter().map(|(_, h)| h.len()).sum()
}
pub fn add_direct<T: Fn(&mut dyn Read, &mut dyn Write) -> BytaryResult<()> + 'static>(
&mut self,
from: Format,
to: Format,
converter: T,
cost: u32,
) {
self.graph
.entry(from)
.or_default()
.insert(to, (Rc::new(converter), cost));
}
pub fn get_converter(&self, from: &Format, to: &Format) -> Option<Rc<ConvertFn>> {
if to == from {
return Some(Self::get_copy_converter());
}
let path = self.find_shortest_path(from, to)?;
if path.len() <= 1 {
return None;
}
let converters = self.path_to_converters(&path).unwrap();
Some(Self::compose(converters))
}
pub fn can_convert(&self, from: &Format, to: &Format) -> bool {
if from == to {
return true;
}
self.find_shortest_path(from, to).is_some()
}
pub fn can_convert_between(&self, format1: &Format, format2: &Format) -> bool {
if format1 == format2 {
return true;
}
self.find_shortest_path(format1, format2)
.and(self.find_shortest_path(format2, format1))
.is_some()
}
pub fn find_shortest_path(&self, from: &Format, to: &Format) -> Option<Vec<Format>> {
Some(dijkstra(from, |n| self.successors(n), |f| f == to)?.0)
}
pub fn path_to_converters(&self, path: &Vec<Format>) -> Option<Vec<Rc<ConvertFn>>> {
let converters = path
.windows(2)
.map_while(|w| Some(self.get_direct_converter(&w[0], &w[1])?))
.collect();
Some(converters)
}
fn get_direct_converter(&self, from: &Format, to: &Format) -> Option<Rc<ConvertFn>> {
self.graph
.get(from)
.and_then(|map| map.get(to))
.map_or(None, |(f, _)| Some(f.clone()))
}
fn successors(&self, n: &Format) -> Vec<(Format, u32)> {
self.graph
.get(&n)
.unwrap_or(&HashMap::new())
.iter()
.map(|(format, (_, cost))| (format.clone(), *cost))
.collect::<Vec<(Format, u32)>>()
}
}