use serde::{Deserialize, Serialize};

use crate::ecs::camera::{Camera, OrthographicCamera, PerspectiveCamera, Projection};
use crate::ecs::light::{AreaLightShape, Light, LightType};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneLight {
    Directional {
        color: [f32; 3],
        intensity: f32,
        cast_shadows: bool,
        shadow_bias: f32,
        #[serde(default)]
        shadow_resolution: u32,
        #[serde(default)]
        shadow_distance: f32,
        #[serde(default)]
        cookie_texture: Option<String>,
    },
    Point {
        color: [f32; 3],
        intensity: f32,
        range: f32,
        cast_shadows: bool,
        shadow_bias: f32,
        #[serde(default)]
        shadow_resolution: u32,
        #[serde(default)]
        shadow_distance: f32,
        #[serde(default)]
        cookie_texture: Option<String>,
    },
    Spot {
        color: [f32; 3],
        intensity: f32,
        range: f32,
        inner_cone_angle: f32,
        outer_cone_angle: f32,
        cast_shadows: bool,
        shadow_bias: f32,
        #[serde(default)]
        shadow_resolution: u32,
        #[serde(default)]
        shadow_distance: f32,
        #[serde(default)]
        cookie_texture: Option<String>,
    },
    Area {
        color: [f32; 3],
        intensity: f32,
        range: f32,
        #[serde(default)]
        shape: AreaLightShape,
        #[serde(default)]
        width: f32,
        #[serde(default)]
        height: f32,
        #[serde(default)]
        radius: f32,
        #[serde(default)]
        two_sided: bool,
        cast_shadows: bool,
        shadow_bias: f32,
        #[serde(default)]
        shadow_resolution: u32,
        #[serde(default)]
        shadow_distance: f32,
        #[serde(default)]
        emissive_texture: Option<String>,
    },
}

impl SceneLight {
    pub fn directional(color: [f32; 3], intensity: f32) -> Self {
        Self::Directional {
            color,
            intensity,
            cast_shadows: true,
            shadow_bias: 0.005,
            shadow_resolution: 0,
            shadow_distance: 0.0,
            cookie_texture: None,
        }
    }

    pub fn point(color: [f32; 3], intensity: f32, range: f32) -> Self {
        Self::Point {
            color,
            intensity,
            range,
            cast_shadows: false,
            shadow_bias: 0.0,
            shadow_resolution: 0,
            shadow_distance: 0.0,
            cookie_texture: None,
        }
    }

    pub fn spot(
        color: [f32; 3],
        intensity: f32,
        range: f32,
        inner_cone_angle: f32,
        outer_cone_angle: f32,
    ) -> Self {
        Self::Spot {
            color,
            intensity,
            range,
            inner_cone_angle,
            outer_cone_angle,
            cast_shadows: false,
            shadow_bias: 0.0,
            shadow_resolution: 0,
            shadow_distance: 0.0,
            cookie_texture: None,
        }
    }

    pub fn to_light(&self) -> Light {
        match self {
            SceneLight::Directional {
                color,
                intensity,
                cast_shadows,
                shadow_bias,
                shadow_resolution,
                shadow_distance,
                cookie_texture,
            } => Light {
                light_type: LightType::Directional,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: 0.0,
                inner_cone_angle: 0.0,
                outer_cone_angle: 0.0,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
                shadow_resolution: *shadow_resolution,
                shadow_distance: *shadow_distance,
                cookie_texture: cookie_texture.clone(),
                ..Default::default()
            },
            SceneLight::Point {
                color,
                intensity,
                range,
                cast_shadows,
                shadow_bias,
                shadow_resolution,
                shadow_distance,
                cookie_texture,
            } => Light {
                light_type: LightType::Point,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: *range,
                inner_cone_angle: 0.0,
                outer_cone_angle: 0.0,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
                shadow_resolution: *shadow_resolution,
                shadow_distance: *shadow_distance,
                cookie_texture: cookie_texture.clone(),
                ..Default::default()
            },
            SceneLight::Spot {
                color,
                intensity,
                range,
                inner_cone_angle,
                outer_cone_angle,
                cast_shadows,
                shadow_bias,
                shadow_resolution,
                shadow_distance,
                cookie_texture,
            } => Light {
                light_type: LightType::Spot,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: *range,
                inner_cone_angle: *inner_cone_angle,
                outer_cone_angle: *outer_cone_angle,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
                shadow_resolution: *shadow_resolution,
                shadow_distance: *shadow_distance,
                cookie_texture: cookie_texture.clone(),
                ..Default::default()
            },
            SceneLight::Area {
                color,
                intensity,
                range,
                shape,
                width,
                height,
                radius,
                two_sided,
                cast_shadows,
                shadow_bias,
                shadow_resolution,
                shadow_distance,
                emissive_texture,
            } => Light {
                light_type: LightType::Area,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: *range,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
                shadow_resolution: *shadow_resolution,
                shadow_distance: *shadow_distance,
                area_shape: *shape,
                area_width: *width,
                area_height: *height,
                area_radius: *radius,
                area_two_sided: *two_sided,
                area_emissive_texture: emissive_texture.clone(),
                ..Default::default()
            },
        }
    }
}

impl From<&Light> for SceneLight {
    fn from(light: &Light) -> Self {
        let color = [light.color.x, light.color.y, light.color.z];
        match light.light_type {
            LightType::Directional => SceneLight::Directional {
                color,
                intensity: light.intensity,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
                shadow_resolution: light.shadow_resolution,
                shadow_distance: light.shadow_distance,
                cookie_texture: light.cookie_texture.clone(),
            },
            LightType::Point => SceneLight::Point {
                color,
                intensity: light.intensity,
                range: light.range,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
                shadow_resolution: light.shadow_resolution,
                shadow_distance: light.shadow_distance,
                cookie_texture: light.cookie_texture.clone(),
            },
            LightType::Spot => SceneLight::Spot {
                color,
                intensity: light.intensity,
                range: light.range,
                inner_cone_angle: light.inner_cone_angle,
                outer_cone_angle: light.outer_cone_angle,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
                shadow_resolution: light.shadow_resolution,
                shadow_distance: light.shadow_distance,
                cookie_texture: light.cookie_texture.clone(),
            },
            LightType::Area => SceneLight::Area {
                color,
                intensity: light.intensity,
                range: light.range,
                shape: light.area_shape,
                width: light.area_width,
                height: light.area_height,
                radius: light.area_radius,
                two_sided: light.area_two_sided,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
                shadow_resolution: light.shadow_resolution,
                shadow_distance: light.shadow_distance,
                emissive_texture: light.area_emissive_texture.clone(),
            },
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneCamera {
    Perspective {
        aspect_ratio: Option<f32>,
        y_fov_rad: f32,
        z_far: Option<f32>,
        z_near: f32,
    },
    Orthographic {
        x_mag: f32,
        y_mag: f32,
        z_far: f32,
        z_near: f32,
    },
}

impl SceneCamera {
    pub fn perspective(y_fov_rad: f32, z_near: f32) -> Self {
        Self::Perspective {
            aspect_ratio: None,
            y_fov_rad,
            z_far: None,
            z_near,
        }
    }

    pub fn orthographic(x_mag: f32, y_mag: f32, z_near: f32, z_far: f32) -> Self {
        Self::Orthographic {
            x_mag,
            y_mag,
            z_far,
            z_near,
        }
    }

    pub fn to_camera(&self) -> Camera {
        match self {
            SceneCamera::Perspective {
                aspect_ratio,
                y_fov_rad,
                z_far,
                z_near,
            } => Camera {
                projection: Projection::Perspective(PerspectiveCamera {
                    aspect_ratio: *aspect_ratio,
                    y_fov_rad: *y_fov_rad,
                    z_far: *z_far,
                    z_near: *z_near,
                }),
                smoothing: None,
            },
            SceneCamera::Orthographic {
                x_mag,
                y_mag,
                z_far,
                z_near,
            } => Camera {
                projection: Projection::Orthographic(OrthographicCamera {
                    x_mag: *x_mag,
                    y_mag: *y_mag,
                    z_far: *z_far,
                    z_near: *z_near,
                }),
                smoothing: None,
            },
        }
    }
}

impl From<&Camera> for SceneCamera {
    fn from(camera: &Camera) -> Self {
        match &camera.projection {
            Projection::Perspective(perspective) => SceneCamera::Perspective {
                aspect_ratio: perspective.aspect_ratio,
                y_fov_rad: perspective.y_fov_rad,
                z_far: perspective.z_far,
                z_near: perspective.z_near,
            },
            Projection::Orthographic(orthographic) => SceneCamera::Orthographic {
                x_mag: orthographic.x_mag,
                y_mag: orthographic.y_mag,
                z_far: orthographic.z_far,
                z_near: orthographic.z_near,
            },
        }
    }
}
