/// Iterator over the downward-closed multi-index set
/// {(i_1, ..., i_d) | i_k >= 0, i_1 + ... + i_d <= bound}.
///
/// Does not iterate over the last dimension explicitly; instead
/// reports how many values the last dimension can take at each
/// position. This is the key to the unidirectional principle:
/// multiply along one dimension at a time, cycling indices.
#[derive(Debug, Clone)]
pub struct BoundedSumIter {
    d: usize,
    bound: usize,
    head: Vec<usize>,
    head_sum: usize,
    valid: bool,
}

impl BoundedSumIter {
    pub fn new(d: usize, bound: usize) -> Self {
        assert!(d >= 1);
        Self {
            d,
            bound,
            head: vec![0; d.saturating_sub(1)],
            head_sum: 0,
            valid: true,
        }
    }

    pub fn last_dim_count(&self) -> usize {
        self.bound - self.head_sum + 1
    }

    pub fn next(&mut self) {
        if self.d == 1 {
            self.valid = false;
            return;
        }

        let tail = self.d - 2;
        if self.bound > self.head_sum {
            self.head_sum += 1;
            self.head[tail] += 1;
        } else {
            let mut dim = tail as isize;
            while dim >= 0 && self.head[dim as usize] == 0 {
                dim -= 1;
            }

            if dim > 0 {
                let d = dim as usize;
                self.head_sum -= self.head[d] - 1;
                self.head[d] = 0;
                self.head[d - 1] += 1;
            } else if dim == 0 {
                self.head[0] = 0;
                self.head_sum = 0;
                self.valid = false;
            } else {
                self.valid = false;
            }
        }
    }

    pub fn valid(&self) -> bool {
        self.valid
    }

    pub fn reset(&mut self) {
        self.head.fill(0);
        self.head_sum = 0;
        self.valid = true;
    }

    pub fn first_index(&self) -> usize {
        if self.head.is_empty() { 0 } else { self.head[0] }
    }

    pub fn index_at(&self, dim: usize) -> usize {
        self.head[dim]
    }

    pub fn dim(&self) -> usize {
        self.d
    }

    pub fn first_index_bound(&self) -> usize {
        self.bound + 1
    }

    pub fn index_bounds(&self) -> Vec<usize> {
        vec![self.bound + 1; self.d]
    }

    pub fn num_values(&self) -> usize {
        binom(self.bound + self.d, self.d)
    }

    pub fn num_values_per_first_index(&self) -> Vec<usize> {
        (0..=self.bound)
            .map(|i| binom((self.bound - i) + (self.d - 1), self.d - 1))
            .collect()
    }

    pub fn go_to_end(&mut self) {
        self.head.fill(0);
        self.head_sum = 0;
        self.valid = false;
    }

    /// Cycle: last dimension moves to front. For a bounded-sum set
    /// the constraint is symmetric, so the iterator is identical.
    pub fn cycle(&self) -> Self {
        let mut c = self.clone();
        c.reset();
        c
    }
}

pub fn binom(n: usize, k: usize) -> usize {
    let k = k.min(n.saturating_sub(k));
    let mut prod: usize = 1;
    for i in 0..k {
        prod = prod * (n - i) / (i + 1);
    }
    prod
}

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

    #[test]
    fn binom_basic() {
        assert_eq!(binom(5, 2), 10);
        assert_eq!(binom(10, 3), 120);
        assert_eq!(binom(0, 0), 1);
    }

    #[test]
    fn iter_count_2d() {
        let it = BoundedSumIter::new(2, 3);
        assert_eq!(it.num_values(), binom(5, 2)); // C(5,2)=10
    }

    #[test]
    fn iter_traversal_2d() {
        let mut it = BoundedSumIter::new(2, 3);
        let mut total = 0;
        while it.valid() {
            total += it.last_dim_count();
            it.next();
        }
        assert_eq!(total, 10);
    }

    #[test]
    fn iter_traversal_3d() {
        let mut it = BoundedSumIter::new(3, 2);
        // {(i,j,k) | i+j+k <= 2} has C(5,3)=10 elements
        let mut total = 0;
        while it.valid() {
            total += it.last_dim_count();
            it.next();
        }
        assert_eq!(total, 10);
    }

    #[test]
    fn num_values_per_first() {
        let it = BoundedSumIter::new(3, 2);
        let counts = it.num_values_per_first_index();
        // first_index=0: C(4,2)=6, first_index=1: C(3,2)=3, first_index=2: C(2,2)=1
        assert_eq!(counts, vec![6, 3, 1]);
    }
}
