ledcat 0.2.0

Control lots of LED's over lots of protocols
use regex::Regex;
use std::str;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Dimensions {
    One(usize),
    Two(usize, usize),
}

impl Dimensions {
    pub fn size(&self) -> usize {
        match *self {
            Dimensions::One(size) => size,
            Dimensions::Two(w, h) => w * h,
        }
    }
}

impl str::FromStr for Dimensions {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let d1 = Regex::new(r"^[1-9]\d*$").unwrap();
        let d2 = Regex::new(r"^([1-9]\d*)x([1-9]\d*)$").unwrap();
        if let Some(cap) = d1.captures(s) {
            Ok(Dimensions::One(cap[0].parse().unwrap()))
        } else if let Some(cap) = d2.captures(s) {
            Ok(Dimensions::Two(
                cap[1].parse().unwrap(),
                cap[2].parse().unwrap(),
            ))
        } else {
            Err(format!("can not parse \"{}\" into Dimensions", s))
        }
    }
}

pub enum Axis {
    X,
    Y,
}

pub trait Transposition {
    fn transpose(&self, index: usize) -> usize;
}

impl<T: Transposition> Transposition for &T {
    fn transpose(&self, index: usize) -> usize {
        (*self).transpose(index)
    }
}

impl Transposition for Box<dyn Transposition> {
    fn transpose(&self, index: usize) -> usize {
        self.as_ref().transpose(index)
    }
}

impl<T: Transposition> Transposition for Vec<T> {
    fn transpose(&self, index: usize) -> usize {
        self.iter().fold(index, |index, tr| tr.transpose(index))
    }
}

pub struct Reverse {
    pub length: usize,
}

impl Transposition for Reverse {
    fn transpose(&self, index: usize) -> usize {
        assert!(index < self.length);
        self.length - index - 1
    }
}

pub struct Mirror {
    pub width: usize,
    pub height: usize,
    pub axis: Axis,
}

impl Transposition for Mirror {
    fn transpose(&self, index: usize) -> usize {
        assert!(index < self.width * self.height);
        let x = index % self.width;
        let y = index / self.width;
        match self.axis {
            Axis::X => self.width * y + (self.width - x - 1),
            Axis::Y => self.width * (self.height - y - 1) + x,
        }
    }
}

pub struct Zigzag {
    pub width: usize,
    pub height: usize,
    pub major_axis: Axis,
}

impl Transposition for Zigzag {
    fn transpose(&self, index: usize) -> usize {
        assert!(index <= self.width * self.height);
        match self.major_axis {
            Axis::X => {
                let x = index % self.width;
                let y = index / self.width;
                if x % 2 == 0 {
                    x * self.height + y
                } else {
                    x * self.height + self.height - y - 1
                }
            }
            Axis::Y => {
                let y = index / self.width;
                let x = if y % 2 == 0 {
                    index % self.width
                } else {
                    self.width - index % self.width - 1
                };
                y * self.width + x
            }
        }
    }
}

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

    fn transpose_all(trans: impl Transposition, input: impl Iterator<Item = usize>) -> Vec<usize> {
        input.map(|index| trans.transpose(index)).collect()
    }

    #[test]
    fn dimensions_1d_parse() {
        assert!("".parse::<Dimensions>().is_err());
        assert!("0".parse::<Dimensions>().is_err());
        assert!("-42".parse::<Dimensions>().is_err());
        assert!("asdf".parse::<Dimensions>().is_err());
        assert_eq!(Dimensions::One(42), "42".parse::<Dimensions>().unwrap());
    }

    #[test]
    fn dimensions_2d_parse() {
        assert!("0x0".parse::<Dimensions>().is_err());
        assert!("1x-1".parse::<Dimensions>().is_err());
        assert!("-1x0".parse::<Dimensions>().is_err());
        assert!("-1x-1".parse::<Dimensions>().is_err());
        assert_eq!(
            Dimensions::Two(4, 20),
            "4x20".parse::<Dimensions>().unwrap()
        );
    }

    #[test]
    fn dimensions_size() {
        assert_eq!(42, Dimensions::One(42).size());
        assert_eq!(80, Dimensions::Two(4, 20).size());
    }

    #[test]
    fn transposition_list() {
        let tr: Vec<Box<dyn Transposition>> = vec![
            Box::from(Zigzag {
                width: 4,
                height: 3,
                major_axis: Axis::Y,
            }),
            Box::from(Reverse { length: 4 * 3 }),
        ];
        assert_eq!(
            vec![11, 10, 9, 8, 4, 5, 6, 7, 3, 2, 1, 0],
            transpose_all(&tr, 0..12)
        );
    }

    #[test]
    fn reverse() {
        assert_eq!(
            vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
            transpose_all(&Reverse { length: 10 }, 0..10)
        );
    }

    #[test]
    fn mirror_x() {
        let m = Mirror {
            width: 3,
            height: 3,
            axis: Axis::X,
        };
        assert_eq!(vec![2, 1, 0, 5, 4, 3, 8, 7, 6], transpose_all(&m, 0..9));
    }

    #[test]
    fn mirror_y() {
        let m = Mirror {
            width: 3,
            height: 3,
            axis: Axis::Y,
        };
        assert_eq!(vec![6, 7, 8, 3, 4, 5, 0, 1, 2], transpose_all(&m, 0..9));
    }

    #[test]
    fn zigzag_x() {
        let zz = Zigzag {
            width: 3,
            height: 3,
            major_axis: Axis::X,
        };
        assert_eq!(vec![0, 5, 6, 1, 4, 7, 2, 3, 8], transpose_all(&zz, 0..9));
        let zz = Zigzag {
            width: 4,
            height: 3,
            major_axis: Axis::X,
        };
        assert_eq!(
            vec![0, 5, 6, 11, 1, 4, 7, 10, 2, 3, 8, 9],
            transpose_all(&zz, 0..12)
        );
    }

    #[test]
    fn zigzag_y() {
        let zz = Zigzag {
            width: 3,
            height: 3,
            major_axis: Axis::Y,
        };
        assert_eq!(vec![0, 1, 2, 5, 4, 3, 6, 7, 8], transpose_all(&zz, 0..9));
        let zz = Zigzag {
            width: 4,
            height: 3,
            major_axis: Axis::Y,
        };
        assert_eq!(
            vec![0, 1, 2, 3, 7, 6, 5, 4, 8, 9, 10, 11],
            transpose_all(&zz, 0..12)
        );
    }
}