use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BoundingBox {
    pub min: (i32, i32, i32),
    pub max: (i32, i32, i32),
}

impl Default for BoundingBox {
    fn default() -> Self {
        // An “empty” box; will be overwritten immediately by rebuild_bbox()
        BoundingBox {
            min: (0, 0, 0),
            max: (0, 0, 0),
        }
    }
}

impl BoundingBox {
    pub fn new(min: (i32, i32, i32), max: (i32, i32, i32)) -> Self {
        BoundingBox { min, max }
    }

    pub fn contains(&self, point: (i32, i32, i32)) -> bool {
        point.0 >= self.min.0
            && point.0 <= self.max.0
            && point.1 >= self.min.1
            && point.1 <= self.max.1
            && point.2 >= self.min.2
            && point.2 <= self.max.2
    }

    pub fn intersects(&self, other: &BoundingBox) -> bool {
        self.min.0 <= other.max.0
            && self.max.0 >= other.min.0
            && self.min.1 <= other.max.1
            && self.max.1 >= other.min.1
            && self.min.2 <= other.max.2
            && self.max.2 >= other.min.2
    }

    pub fn intersects_range(
        &self,
        min_x: i32,
        min_y: i32,
        min_z: i32,
        max_x: i32,
        max_y: i32,
        max_z: i32,
    ) -> bool {
        !(self.max.0 < min_x
            || self.min.0 >= max_x
            || self.max.1 < min_y
            || self.min.1 >= max_y
            || self.max.2 < min_z
            || self.min.2 >= max_z)
    }

    pub fn union(&self, other: &BoundingBox) -> BoundingBox {
        BoundingBox {
            min: (
                self.min.0.min(other.min.0),
                self.min.1.min(other.min.1),
                self.min.2.min(other.min.2),
            ),
            max: (
                self.max.0.max(other.max.0),
                self.max.1.max(other.max.1),
                self.max.2.max(other.max.2),
            ),
        }
    }

    pub fn coords_to_index(&self, x: i32, y: i32, z: i32) -> usize {
        let (width, _, length) = self.get_dimensions();
        let dx = x - self.min.0;
        let dy = y - self.min.1;
        let dz = z - self.min.2;
        (dx + dz * width + dy * width * length) as usize
    }

    pub fn index_to_coords(&self, index: usize) -> (i32, i32, i32) {
        let (width, _, length) = self.get_dimensions();
        let dx = (index % width as usize) as i32;
        let dy = (index / (width * length) as usize) as i32;
        let dz = ((index / width as usize) % length as usize) as i32;
        (dx + self.min.0, dy + self.min.1, dz + self.min.2)
    }

    pub fn get_dimensions(&self) -> (i32, i32, i32) {
        (
            (self.max.0 - self.min.0 + 1),
            (self.max.1 - self.min.1 + 1),
            (self.max.2 - self.min.2 + 1),
        )
    }

    pub fn to_position_and_size(&self) -> ((i32, i32, i32), (i32, i32, i32)) {
        (self.min, self.get_dimensions())
    }

    pub fn from_position_and_size(position: (i32, i32, i32), size: (i32, i32, i32)) -> Self {
        let position2 = (
            position.0 + size.0,
            position.1 + size.1,
            position.2 + size.2,
        );

        let offset_min = (
            -size.0.signum().min(0),
            -size.1.signum().min(0),
            -size.2.signum().min(0),
        );
        let offset_max = (
            -size.0.signum().max(0),
            -size.1.signum().max(0),
            -size.2.signum().max(0),
        );

        BoundingBox::new(
            (
                position.0.min(position2.0) + offset_min.0,
                position.1.min(position2.1) + offset_min.1,
                position.2.min(position2.2) + offset_min.2,
            ),
            (
                position.0.max(position2.0) + offset_max.0,
                position.1.max(position2.1) + offset_max.1,
                position.2.max(position2.2) + offset_max.2,
            ),
        )
    }
    pub fn volume(&self) -> u64 {
        let (width, height, length) = self.get_dimensions();
        width as u64 * height as u64 * length as u64
    }
}
