use crate::block::BestFitKind::{DoubledFit, ExactFit, GreaterThanFit};
use crate::error::{ Result};
use std::cmp::Ordering::Equal;
pub type Dimension = f64;
type Volume = f64;
enum BestFitKind {
DoubledFit(usize),
ExactFit(usize),
GreaterThanFit(usize),
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Block {
pub dims: [Dimension; 3],
}
impl Block {
pub fn new<F: Into<Dimension>>(d1: F, d2: F, d3: F) -> Self {
let mut dims = [d1.into(), d2.into(), d3.into()];
dims.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Equal));
Self { dims }
}
pub fn volume(&self) -> Volume {
self.dims.iter().map(|&dim| Volume::from(dim)).product()
}
pub fn does_it_fit(&self, other: &Block) -> bool {
self.dims
.iter()
.zip(other.dims.iter())
.all(|(d, other_d)| d >= other_d)
}
pub fn best_fit(mut self, item: &Block) -> Option<Vec<Block>> {
if !self.does_it_fit(&item) {
return None;
}
let mut blocks = vec![];
let side_1 = match self._get_best_fit(item) {
DoubledFit(i) => {
let block_1 = Block::new(
self.dims[i] - item.dims[2],
self.dims[(i + 2) % 3],
self.dims[(i + 1) % 3],
);
self.dims[i] = item.dims[2];
blocks.push(block_1);
i
}
ExactFit(i) => {
i
}
GreaterThanFit(i) => {
blocks.push(Block::new(
self.dims[i] - item.dims[2],
item.dims[0],
item.dims[1],
));
i
}
};
let (side_2, side_3) = self._get_side_2_side_3(item, side_1);
let block_2a = Block::new(
self.dims[side_1],
self.dims[side_2],
self.dims[side_3] - item.dims[0],
);
let block_3a = Block::new(
self.dims[side_1],
self.dims[side_2] - item.dims[1],
item.dims[0],
);
let block_2b = Block::new(
self.dims[side_1],
self.dims[side_2] - item.dims[1],
self.dims[side_3],
);
let block_3b = Block::new(
self.dims[side_1],
self.dims[side_3] - item.dims[0],
item.dims[1],
);
if block_2a.volume() < block_2b.volume() {
blocks.push(block_2a);
blocks.push(block_3a);
} else {
blocks.push(block_2b);
blocks.push(block_3b);
}
let mut res = blocks
.into_iter()
.filter(|block| block.dims[0] > 0 as Dimension)
.collect::<Vec<Block>>();
res.sort_by(|block_a, block_b| {
block_a
.volume()
.partial_cmp(&block_b.volume())
.unwrap_or(Equal)
});
Some(res)
}
fn _get_side_2_side_3(&self, item: &Block, side_1: usize) -> (usize, usize) {
if item.dims[1] > self.dims[(side_1 + 2) % 3] {
((side_1 + 1) % 3, (side_1 + 2) % 3)
} else if item.dims[1] > self.dims[(side_1 + 1) % 3] {
((side_1 + 2) % 3, (side_1 + 1) % 3)
} else {
((side_1 + 1) % 3, (side_1 + 2) % 3)
}
}
fn _get_best_fit(&self, item: &Block) -> BestFitKind {
let doubled_fit_side = self.dims.iter().enumerate().find_map(|(i, side)| {
if side >= &(item.dims[2] * 2 as Dimension) {
Some(i)
} else {
None
}
});
let exact_fit_side = self.dims.iter().enumerate().find_map(|(i, dim)| {
if dim == &item.dims[2] {
Some(i)
} else {
None
}
});
match (doubled_fit_side, exact_fit_side) {
(Some(i), None) => DoubledFit(i),
(None, Some(i)) => ExactFit(i),
(Some(doubled_i), Some(exact_i)) => {
if doubled_i <= exact_i {
DoubledFit(doubled_i)
} else {
ExactFit(exact_i)
}
}
(None, None) => {
let i = self
.dims
.iter()
.enumerate()
.find_map(|(i, dim)| if dim >= &item.dims[2] { Some(i) } else { None })
.expect("Invariant violated: item must fit within the container!");
GreaterThanFit(i)
}
}
}
}