use crate::config::SymmetryType;

pub fn mirror_coords(
    row: usize,
    col: usize,
    width: usize,
    height: usize,
    symmetry: SymmetryType,
) -> Vec<(usize, usize)> {
    let mut coords = vec![(row, col)];

    let mirror_row = |i: usize| height.checked_sub(1).and_then(|h| h.checked_sub(i));
    let mirror_col = |i: usize| width.checked_sub(1).and_then(|w| w.checked_sub(i));

    match symmetry {
        SymmetryType::Rotational180 => {
            if let (Some(mr), Some(mc)) = (mirror_row(row), mirror_col(col)) {
                coords.push((mr, mc));
            }
        }
        SymmetryType::Rotational90 => {
            // each 90-degree image is kept only when it lands inside the grid,
            // so non-square grids no longer underflow or go out of bounds
            if let Some(mc) = mirror_col(row) {
                if col < height {
                    coords.push((col, mc));
                }
            }
            if let (Some(mr), Some(mc)) = (mirror_row(row), mirror_col(col)) {
                coords.push((mr, mc));
            }
            if let Some(mr) = mirror_row(col) {
                if row < width {
                    coords.push((mr, row));
                }
            }
        }
        SymmetryType::Diagonal => {
            if width == height {
                coords.push((col, row));
            }
        }
        SymmetryType::Mirror => {
            if let Some(mc) = mirror_col(col) {
                coords.push((row, mc));
            }
        }
        SymmetryType::None => {}
    }
    coords.sort();
    coords.dedup();
    coords
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rotational_180() {
        let coords = mirror_coords(0, 0, 5, 5, SymmetryType::Rotational180);
        assert_eq!(coords, vec![(0, 0), (4, 4)]);
    }

    #[test]
    fn rotational_180_center() {
        let coords = mirror_coords(2, 2, 5, 5, SymmetryType::Rotational180);
        assert_eq!(coords, vec![(2, 2)]);
    }

    #[test]
    fn rotational_90() {
        let coords = mirror_coords(0, 1, 5, 5, SymmetryType::Rotational90);
        assert!(coords.contains(&(0, 1)));
        assert!(coords.contains(&(1, 4)));
        assert!(coords.contains(&(4, 3)));
        assert!(coords.contains(&(3, 0)));
    }

    #[test]
    fn mirror_lr() {
        let coords = mirror_coords(1, 0, 5, 5, SymmetryType::Mirror);
        assert_eq!(coords, vec![(1, 0), (1, 4)]);
    }

    #[test]
    fn diagonal() {
        let coords = mirror_coords(1, 3, 5, 5, SymmetryType::Diagonal);
        assert_eq!(coords, vec![(1, 3), (3, 1)]);
    }

    #[test]
    fn rotational_90_non_square_stays_in_bounds() {
        let coords = mirror_coords(0, 3, 5, 7, SymmetryType::Rotational90);
        for (r, c) in &coords {
            assert!(*r < 7 && *c < 5, "point ({}, {}) out of bounds", r, c);
        }
        assert!(coords.contains(&(0, 3)));
        let coords2 = mirror_coords(6, 4, 5, 7, SymmetryType::Rotational90);
        for (r, c) in &coords2 {
            assert!(*r < 7 && *c < 5, "point ({}, {}) out of bounds", r, c);
        }
    }

    #[test]
    fn no_symmetry() {
        let coords = mirror_coords(1, 2, 5, 5, SymmetryType::None);
        assert_eq!(coords, vec![(1, 2)]);
    }
}
