use super::TrackCounts;
use crate::compute::grid::OriginZeroLine;
use crate::geometry::AbsoluteAxis;
use crate::geometry::Line;
use crate::util::sys::Vec;
use core::cmp::{max, min};
use core::fmt::Debug;
use core::ops::Range;
use grid::Grid;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub(crate) enum CellOccupancyState {
#[default]
Unoccupied,
DefinitelyPlaced,
AutoPlaced,
}
pub(crate) struct CellOccupancyMatrix {
inner: Grid<CellOccupancyState>,
columns: TrackCounts,
rows: TrackCounts,
}
impl Debug for CellOccupancyMatrix {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(
f,
"Rows: neg_implicit={} explicit={} pos_implicit={}",
self.rows.negative_implicit, self.rows.explicit, self.rows.positive_implicit
)?;
writeln!(
f,
"Cols: neg_implicit={} explicit={} pos_implicit={}",
self.columns.negative_implicit, self.columns.explicit, self.columns.positive_implicit
)?;
writeln!(f, "State:")?;
for row_idx in 0..self.inner.rows() {
for cell in self.inner.iter_row(row_idx) {
let letter = match *cell {
CellOccupancyState::Unoccupied => '_',
CellOccupancyState::DefinitelyPlaced => 'D',
CellOccupancyState::AutoPlaced => 'A',
};
write!(f, "{letter}")?;
}
writeln!(f)?;
}
Ok(())
}
}
impl CellOccupancyMatrix {
pub fn with_track_counts(columns: TrackCounts, rows: TrackCounts) -> Self {
Self { inner: Grid::new(rows.len(), columns.len()), rows, columns }
}
pub fn is_area_in_range(
&self,
primary_axis: AbsoluteAxis,
primary_range: Range<i16>,
secondary_range: Range<i16>,
) -> bool {
if primary_range.start < 0 || primary_range.end > self.track_counts(primary_axis).len() as i16 {
return false;
}
if secondary_range.start < 0 || secondary_range.end > self.track_counts(primary_axis.other_axis()).len() as i16
{
return false;
}
true
}
fn expand_to_fit_range(&mut self, row_range: Range<i16>, col_range: Range<i16>) {
let req_negative_rows = min(row_range.start, 0);
let req_positive_rows = max(row_range.end - self.rows.len() as i16, 0);
let req_negative_cols = min(col_range.start, 0);
let req_positive_cols = max(col_range.end - self.columns.len() as i16, 0);
let old_row_count = self.rows.len();
let old_col_count = self.columns.len();
let new_row_count = old_row_count + (req_negative_rows + req_positive_rows) as usize;
let new_col_count = old_col_count + (req_negative_cols + req_positive_cols) as usize;
let mut data = Vec::with_capacity(new_row_count * new_col_count);
for _ in 0..(req_negative_rows as usize * new_col_count) {
data.push(CellOccupancyState::Unoccupied);
}
for row in 0..old_row_count {
for _ in 0..req_negative_cols {
data.push(CellOccupancyState::Unoccupied);
}
for col in 0..old_col_count {
data.push(*self.inner.get(row, col).unwrap());
}
for _ in 0..req_positive_cols {
data.push(CellOccupancyState::Unoccupied);
}
}
for _ in 0..(req_positive_rows as usize * new_col_count) {
data.push(CellOccupancyState::Unoccupied);
}
self.inner = Grid::from_vec(data, new_col_count);
self.rows.negative_implicit += req_negative_rows as u16;
self.rows.positive_implicit += req_positive_rows as u16;
self.columns.negative_implicit += req_negative_cols as u16;
self.columns.positive_implicit += req_positive_cols as u16;
}
pub fn mark_area_as(
&mut self,
primary_axis: AbsoluteAxis,
primary_span: Line<OriginZeroLine>,
secondary_span: Line<OriginZeroLine>,
value: CellOccupancyState,
) {
let (row_span, column_span) = match primary_axis {
AbsoluteAxis::Horizontal => (secondary_span, primary_span),
AbsoluteAxis::Vertical => (primary_span, secondary_span),
};
let mut col_range = self.columns.oz_line_range_to_track_range(column_span);
let mut row_range = self.rows.oz_line_range_to_track_range(row_span);
let is_in_range = self.is_area_in_range(AbsoluteAxis::Horizontal, col_range.clone(), row_range.clone());
if !is_in_range {
self.expand_to_fit_range(row_range.clone(), col_range.clone());
col_range = self.columns.oz_line_range_to_track_range(column_span);
row_range = self.rows.oz_line_range_to_track_range(row_span);
}
for x in row_range {
for y in col_range.clone() {
*self.inner.get_mut(x as usize, y as usize).unwrap() = value;
}
}
}
pub fn line_area_is_unoccupied(
&self,
primary_axis: AbsoluteAxis,
primary_span: Line<OriginZeroLine>,
secondary_span: Line<OriginZeroLine>,
) -> bool {
let primary_range = self.track_counts(primary_axis).oz_line_range_to_track_range(primary_span);
let secondary_range = self.track_counts(primary_axis.other_axis()).oz_line_range_to_track_range(secondary_span);
self.track_area_is_unoccupied(primary_axis, primary_range, secondary_range)
}
pub fn track_area_is_unoccupied(
&self,
primary_axis: AbsoluteAxis,
primary_range: Range<i16>,
secondary_range: Range<i16>,
) -> bool {
let (row_range, col_range) = match primary_axis {
AbsoluteAxis::Horizontal => (secondary_range, primary_range),
AbsoluteAxis::Vertical => (primary_range, secondary_range),
};
for x in row_range {
for y in col_range.clone() {
match self.inner.get(x as usize, y as usize) {
None | Some(CellOccupancyState::Unoccupied) => continue,
_ => return false,
}
}
}
true
}
pub fn row_is_occupied(&self, row_index: usize) -> bool {
self.inner.iter_row(row_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
}
pub fn column_is_occupied(&self, column_index: usize) -> bool {
self.inner.iter_col(column_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
}
pub fn track_counts(&self, track_type: AbsoluteAxis) -> &TrackCounts {
match track_type {
AbsoluteAxis::Horizontal => &self.columns,
AbsoluteAxis::Vertical => &self.rows,
}
}
pub fn last_of_type(
&self,
track_type: AbsoluteAxis,
start_at: OriginZeroLine,
kind: CellOccupancyState,
) -> Option<OriginZeroLine> {
let track_counts = self.track_counts(track_type.other_axis());
let track_computed_index = track_counts.oz_line_to_next_track(start_at);
let maybe_index = match track_type {
AbsoluteAxis::Horizontal => {
self.inner.iter_row(track_computed_index as usize).rposition(|item| *item == kind)
}
AbsoluteAxis::Vertical => {
self.inner.iter_col(track_computed_index as usize).rposition(|item| *item == kind)
}
};
maybe_index.map(|idx| track_counts.track_to_prev_oz_line(idx as u16))
}
}