#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Face {
    PosX,
    NegX,
    PosY,
    NegY,
    PosZ,
    NegZ,
}

impl Face {
    pub fn all() -> [Face; 6] {
        [
            Face::PosX,
            Face::NegX,
            Face::PosY,
            Face::NegY,
            Face::PosZ,
            Face::NegZ,
        ]
    }
}

pub struct LodConfig {
    pub max_level: u32,
    pub split_distance_factor: f64,
    pub base_size_m: f64,
}

impl LodConfig {
    pub fn default_mercury() -> Self {
        Self {
            max_level: 14,
            split_distance_factor: 2.0,
            base_size_m: crate::physics::orbit::MERCURY_RADIUS * 2.0,
        }
    }

    pub fn node_size(&self, level: u32) -> f64 {
        self.base_size_m / (1u64 << level) as f64
    }

    pub fn should_split(&self, level: u32, distance_m: f64) -> bool {
        if level >= self.max_level {
            return false;
        }
        distance_m < self.node_size(level) * self.split_distance_factor
    }
}

pub struct QuadTreeNode {
    pub face: Face,
    pub level: u32,
    pub x: u32,
    pub y: u32,
    pub children: Option<Box<[QuadTreeNode; 4]>>,
}

impl QuadTreeNode {
    pub fn new(face: Face, level: u32, x: u32, y: u32) -> Self {
        Self {
            face,
            level,
            x,
            y,
            children: None,
        }
    }

    pub fn is_leaf(&self) -> bool {
        self.children.is_none()
    }

    pub fn split(&mut self) {
        let l = self.level + 1;
        let bx = self.x * 2;
        let by = self.y * 2;
        self.children = Some(Box::new([
            QuadTreeNode::new(self.face, l, bx, by),
            QuadTreeNode::new(self.face, l, bx + 1, by),
            QuadTreeNode::new(self.face, l, bx, by + 1),
            QuadTreeNode::new(self.face, l, bx + 1, by + 1),
        ]));
    }

    pub fn leaf_count(&self) -> usize {
        match &self.children {
            None => 1,
            Some(c) => c.iter().map(|n| n.leaf_count()).sum(),
        }
    }
}

pub struct LodTerrain {
    pub roots: Vec<QuadTreeNode>,
    pub config: LodConfig,
}

impl LodTerrain {
    pub fn new(config: LodConfig) -> Self {
        let roots = Face::all()
            .iter()
            .map(|&f| QuadTreeNode::new(f, 0, 0, 0))
            .collect();
        Self { roots, config }
    }

    pub fn total_leaves(&self) -> usize {
        self.roots.iter().map(|r| r.leaf_count()).sum()
    }
}

pub fn lod_level(distance: f64) -> u8 {
    if distance < 1_000.0 {
        5
    } else if distance < 10_000.0 {
        4
    } else if distance < 100_000.0 {
        3
    } else if distance < 1_000_000.0 {
        2
    } else {
        1
    }
}
