use bytemuck::{Pod, Zeroable};
use glam::Mat4;

use crate::entity::EntityHandle;

pub const MAX_SPOTLIGHTS: usize = 4;

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable, Default)]
pub struct SpotlightRaw
{
    pub position: [f32; 3],
    pub inner_angle: f32,
    pub direction: [f32; 3],
    pub outer_angle: f32,
    pub range: f32,
    pub _padding: [f32; 7],
}

pub struct Spotlight
{
    pub position: glam::Vec3,
    pub direction: glam::Vec3,
    pub inner_angle: f32,
    pub outer_angle: f32,
    pub range: f32,
}

impl Spotlight
{
    pub fn new(
        position: glam::Vec3,
        direction: glam::Vec3,
        inner_angle: f32,
        outer_angle: f32,
        range: f32,
    ) -> Self
    {
        Self {
            position,
            direction: direction.normalize(),
            inner_angle,
            outer_angle,
            range,
        }
    }

    pub fn to_raw(&self) -> SpotlightRaw
    {
        SpotlightRaw {
            position: self.position.to_array(),
            inner_angle: self.inner_angle,
            direction: self.direction.to_array(),
            outer_angle: self.outer_angle,
            range: self.range,
            _padding: [0.0; 7],
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Uniforms
{
    pub model: [[f32; 4]; 4],
    pub view: [[f32; 4]; 4],
    pub projection: [[f32; 4]; 4],
    pub light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS],
    pub camera_position: [f32; 3],
    pub height_scale: f32,
    pub player_position: [f32; 3],
    pub time: f32,
    pub shadow_bias: f32,
    pub tile_scale: f32,
    pub enable_dissolve: u32,
    pub enable_snow_light: u32,
    pub spotlight_count: u32,
    pub debug_mode: u32,
    pub _padding2: u32,
    pub _padding3: u32,
    pub spotlights: [SpotlightRaw; MAX_SPOTLIGHTS],
}

impl Uniforms
{
    pub fn new(
        model: Mat4,
        view: Mat4,
        projection: Mat4,
        light_view_projections: &[Mat4],
        camera_position: glam::Vec3,
        player_position: glam::Vec3,
        height_scale: f32,
        time: f32,
        shadow_bias: f32,
        tile_scale: f32,
        enable_dissolve: bool,
        enable_snow_light: bool,
        spotlights: &[Spotlight],
        debug_mode: u32,
    ) -> Self
    {
        let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS];
        for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate()
        {
            spotlight_array[i] = spotlight.to_raw();
        }

        let mut lvp_array = [[[0.0f32; 4]; 4]; MAX_SPOTLIGHTS];
        for (i, mat) in light_view_projections
            .iter()
            .take(MAX_SPOTLIGHTS)
            .enumerate()
        {
            lvp_array[i] = mat.to_cols_array_2d();
        }

        Self {
            model: model.to_cols_array_2d(),
            view: view.to_cols_array_2d(),
            projection: projection.to_cols_array_2d(),
            light_view_projections: lvp_array,
            camera_position: camera_position.to_array(),
            height_scale,
            player_position: player_position.to_array(),
            time,
            shadow_bias,
            tile_scale,
            enable_dissolve: if enable_dissolve { 1 } else { 0 },
            enable_snow_light: if enable_snow_light { 1 } else { 0 },
            spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32,
            debug_mode,
            _padding2: 0,
            _padding3: 0,
            spotlights: spotlight_array,
        }
    }
}

#[derive(Clone, Copy)]
pub enum Pipeline
{
    Standard,
    SnowClipmap,
    DebugLines,
}

pub struct DrawCall
{
    pub vertex_buffer: wgpu::Buffer,
    pub index_buffer: wgpu::Buffer,
    pub num_indices: u32,
    pub model: Mat4,
    pub pipeline: Pipeline,
    pub instance_buffer: Option<wgpu::Buffer>,
    pub num_instances: u32,
    pub tile_scale: f32,
    pub enable_dissolve: bool,
    pub enable_snow_light: bool,
    pub displacement_bind_group: Option<wgpu::BindGroup>,
    pub entity: Option<EntityHandle>,
}
