use std::mem::MaybeUninit;

use crate::{vec3, vector::Vector3};

pub struct Chunk {
    blocks: [[[Block; 16]; 16]; 16],
    pub chunk_coord: Vector3<i32>,
}

impl Chunk {
    pub fn new(chunk_coord: Vector3<i32>) -> Self {
        let mut blocks: [[[MaybeUninit<Block>; 16]; 16]; 16] =
            unsafe { MaybeUninit::uninit().assume_init() };

        blocks.iter_mut().for_each(|bs| {
            bs.iter_mut().for_each(|bs| {
                bs.iter_mut().for_each(|u| {
                    u.write(Block {
                        typ: BlockType::Air,
                    });
                })
            })
        });

        let blocks = unsafe {
            std::mem::transmute::<[[[MaybeUninit<Block>; 16]; 16]; 16], [[[Block; 16]; 16]; 16]>(
                blocks,
            )
        };

        Chunk {
            blocks,
            chunk_coord,
        }
    }

    pub fn set_block(&mut self, block: Block, local_coord: Vector3<i32>) {
        match local_coord {
            Vector3 {
                x: x @ 0..16,
                y: y @ 0..16,
                z: z @ 0..16,
            } => self.blocks[x as usize][y as usize][z as usize] = block,
            _ => todo!(),
        }
    }

    pub fn new_init(blocks: [[[Block; 16]; 16]; 16], chunk_coord: Vector3<i32>) -> Self {
        Self {
            blocks,
            chunk_coord,
        }
    }

    pub fn get_block(&self, local_coord: Vector3<i32>) -> Option<&Block> {
        match local_coord {
            Vector3 {
                x: x @ 0..16,
                y: y @ 0..16,
                z: z @ 0..16,
            } => Some(&self.blocks[x as usize][y as usize][z as usize]),
            _ => None,
        }
    }

    pub fn blocks(&self) -> impl Iterator<Item = (&Block, Vector3<i32>)> {
        let i = self
            .blocks
            .iter()
            .enumerate()
            .map(|(x, bs)| {
                bs.iter()
                    .enumerate()
                    .map(move |(y, bs)| {
                        bs.iter()
                            .enumerate()
                            .map(move |(z, b)| (b, vec3!(x as i32, y as i32, z as i32)))
                    })
                    .flatten()
            })
            .flatten();
        i
    }
}

#[derive(Clone, Copy)]
pub struct Block {
    pub typ: BlockType,
}

impl Block {
    pub fn is_transparent(&self) -> bool {
        match self.typ {
            BlockType::Air => true,
            BlockType::Solid => false,
        }
    }
}

#[derive(Clone, Copy)]
pub enum BlockType {
    Air,
    Solid,
}
