use crate::table::Table;
use std::ops::Range;

#[derive(Debug, Clone)]
pub struct FeatureSpace(Vec<Range<f64>>);

impl FeatureSpace {
    pub fn from_table(table: &Table) -> Self {
        let ranges = (0..table.features_len())
            .map(|i| {
                let start = table
                    .column(i)
                    .min_by(|a, b| a.total_cmp(b))
                    .expect("never fails");
                let end = table
                    .column(i)
                    .max_by(|a, b| a.total_cmp(b))
                    .expect("never fails");
                Range { start, end }
            })
            .collect();
        Self(ranges)
    }

    pub fn ranges(&self) -> &[Range<f64>] {
        &self.0
    }

    pub fn split(&self, feature_index: usize, split_point: f64) -> (Self, Self) {
        debug_assert!(feature_index < self.0.len());
        debug_assert!(self.0[feature_index].start <= split_point);
        debug_assert!(split_point <= self.0[feature_index].end);

        let mut lower = self.clone();
        let mut upper = self.clone();
        lower.0[feature_index].end = split_point;
        upper.0[feature_index].start = split_point;
        (lower, upper)
    }

    pub fn marginal_size(&self, fixed: &[usize]) -> f64 {
        self.0
            .iter()
            .enumerate()
            .filter(|(i, _)| !fixed.contains(i))
            .map(|(_, r)| r.end - r.start)
            .product()
    }

    pub fn partial_size(&self, features: &[usize]) -> f64 {
        features
            .iter()
            .map(|&i| self.0[i].end - self.0[i].start)
            .product()
    }

    pub fn size(&self) -> f64 {
        self.0.iter().map(|r| r.end - r.start).product()
    }
}
