use crate::assets::{Assets, GeometryHandle, MaterialHandle};
use crate::geometry::{Aabb, GeometryTopology};

use super::{CameraKey, InstanceId, LightKey, NodeKey, NodeKind, Scene, Transform, Vec3};

mod builders;
mod material;
mod texture;
pub use material::SceneMaterialInspection;
pub use texture::SceneTextureInspection;

#[derive(Debug, Clone, PartialEq)]
pub struct SceneInspectionReport {
    nodes: Vec<SceneNodeInspection>,
    draw_list: Vec<SceneDrawInspection>,
    camera_frustums: Vec<SceneCameraFrustumInspection>,
    normal_overlays: Vec<SceneNormalInspection>,
    active_camera: Option<CameraKey>,
    visible_drawable_count: usize,
    camera_count: usize,
    light_count: usize,
    anchor_count: usize,
    connector_count: usize,
    bounded_node_count: usize,
    clipping_plane_count: usize,
    structure_revision: u64,
    transform_revision: u64,
    interaction_revision: u64,
}

#[derive(Debug, Clone, PartialEq)]
pub struct SceneNodeInspection {
    node: NodeKey,
    parent: Option<NodeKey>,
    kind: &'static str,
    transform: Transform,
    world_transform: Transform,
    visible: bool,
    tags: Vec<String>,
    bounds: Option<Aabb>,
    mesh_geometry: Option<GeometryHandle>,
    mesh_material: Option<MaterialHandle>,
    material_preview: Option<SceneMaterialInspection>,
    camera: Option<CameraKey>,
    light: Option<LightKey>,
    layer_mask: u64,
    render_group: i16,
    helper_on_top: bool,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SceneDrawInspection {
    node: NodeKey,
    instance: Option<InstanceId>,
    geometry: GeometryHandle,
    material: MaterialHandle,
    material_preview: Option<SceneMaterialInspection>,
    topology: GeometryTopology,
    primitive_count: usize,
    vertex_count: usize,
    index_count: usize,
    local_bounds: Aabb,
    world_transform: Transform,
    visible: bool,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SceneCameraFrustumInspection {
    camera: CameraKey,
    node: NodeKey,
    near: f32,
    far: f32,
    corners: [Vec3; 8],
}

#[derive(Debug, Clone, PartialEq)]
pub struct SceneNormalInspection {
    node: NodeKey,
    instance: Option<InstanceId>,
    geometry: GeometryHandle,
    length: f32,
    segments: Vec<[Vec3; 2]>,
}

impl Scene {
    pub fn inspect(&self) -> SceneInspectionReport {
        self.inspect_inner(None::<&Assets>)
    }

    pub fn inspect_with_assets<F>(&self, assets: &Assets<F>) -> SceneInspectionReport {
        self.inspect_inner(Some(assets))
    }

    fn inspect_inner<F>(&self, assets: Option<&Assets<F>>) -> SceneInspectionReport {
        let dirty = self.dirty_state();
        SceneInspectionReport {
            nodes: self
                .nodes
                .iter()
                .map(|(node_key, node)| SceneNodeInspection {
                    node: node_key,
                    parent: node.parent,
                    kind: kind_name(&node.kind),
                    transform: node.transform,
                    world_transform: self.world_transform(node_key).unwrap_or(node.transform),
                    visible: node.visible,
                    tags: node.tags.iter().cloned().collect(),
                    bounds: self.node_bounds.get(&node_key).copied(),
                    mesh_geometry: mesh_geometry(&node.kind),
                    mesh_material: mesh_material(&node.kind),
                    material_preview: material_preview(&node.kind, assets),
                    camera: camera_key(&node.kind),
                    light: light_key(&node.kind),
                    layer_mask: node.layer_mask,
                    render_group: node.render_group,
                    helper_on_top: node.helper_on_top,
                })
                .collect(),
            draw_list: self.inspect_draw_list(assets),
            camera_frustums: self.inspect_camera_frustums(),
            normal_overlays: self.inspect_normal_overlays(assets),
            active_camera: self.active_camera,
            visible_drawable_count: self.visible_drawable_count(),
            camera_count: self.cameras.len(),
            light_count: self.lights.len(),
            anchor_count: self.anchors.len(),
            connector_count: self.connectors.len(),
            bounded_node_count: self.node_bounds.len(),
            clipping_plane_count: self.clipping_planes.len(),
            structure_revision: dirty.structure_revision,
            transform_revision: dirty.transform_revision,
            interaction_revision: dirty.interaction_revision,
        }
    }
}

impl SceneInspectionReport {
    pub fn nodes(&self) -> &[SceneNodeInspection] {
        &self.nodes
    }

    pub fn draw_list(&self) -> &[SceneDrawInspection] {
        &self.draw_list
    }

    pub fn camera_frustums(&self) -> &[SceneCameraFrustumInspection] {
        &self.camera_frustums
    }

    pub fn normal_overlays(&self) -> &[SceneNormalInspection] {
        &self.normal_overlays
    }

    pub fn node_count(&self) -> usize {
        self.nodes.len()
    }

    pub const fn active_camera(&self) -> Option<CameraKey> {
        self.active_camera
    }

    pub const fn visible_drawable_count(&self) -> usize {
        self.visible_drawable_count
    }

    pub const fn camera_count(&self) -> usize {
        self.camera_count
    }

    pub const fn light_count(&self) -> usize {
        self.light_count
    }

    pub const fn anchor_count(&self) -> usize {
        self.anchor_count
    }

    pub const fn connector_count(&self) -> usize {
        self.connector_count
    }

    pub const fn bounded_node_count(&self) -> usize {
        self.bounded_node_count
    }

    pub const fn clipping_plane_count(&self) -> usize {
        self.clipping_plane_count
    }

    pub const fn structure_revision(&self) -> u64 {
        self.structure_revision
    }

    pub const fn transform_revision(&self) -> u64 {
        self.transform_revision
    }

    pub const fn interaction_revision(&self) -> u64 {
        self.interaction_revision
    }
}

impl SceneNodeInspection {
    pub const fn node(&self) -> NodeKey {
        self.node
    }

    pub const fn parent(&self) -> Option<NodeKey> {
        self.parent
    }

    pub const fn kind(&self) -> &'static str {
        self.kind
    }

    pub const fn transform(&self) -> Transform {
        self.transform
    }

    pub const fn world_transform(&self) -> Transform {
        self.world_transform
    }

    pub const fn visible(&self) -> bool {
        self.visible
    }

    pub fn tags(&self) -> &[String] {
        &self.tags
    }

    pub const fn bounds(&self) -> Option<Aabb> {
        self.bounds
    }

    pub const fn mesh_geometry(&self) -> Option<GeometryHandle> {
        self.mesh_geometry
    }

    pub const fn mesh_material(&self) -> Option<MaterialHandle> {
        self.mesh_material
    }

    pub const fn material_preview(&self) -> Option<SceneMaterialInspection> {
        self.material_preview
    }

    pub const fn camera(&self) -> Option<CameraKey> {
        self.camera
    }

    pub const fn light(&self) -> Option<LightKey> {
        self.light
    }

    pub const fn layer_mask(&self) -> u64 {
        self.layer_mask
    }

    pub const fn render_group(&self) -> i16 {
        self.render_group
    }

    pub const fn helper_on_top(&self) -> bool {
        self.helper_on_top
    }
}

impl SceneDrawInspection {
    pub const fn node(&self) -> NodeKey {
        self.node
    }

    pub const fn instance(&self) -> Option<InstanceId> {
        self.instance
    }

    pub const fn geometry(&self) -> GeometryHandle {
        self.geometry
    }

    pub const fn material(&self) -> MaterialHandle {
        self.material
    }

    pub const fn material_preview(&self) -> Option<SceneMaterialInspection> {
        self.material_preview
    }

    pub const fn topology(&self) -> GeometryTopology {
        self.topology
    }

    pub const fn primitive_count(&self) -> usize {
        self.primitive_count
    }

    pub const fn vertex_count(&self) -> usize {
        self.vertex_count
    }

    pub const fn index_count(&self) -> usize {
        self.index_count
    }

    pub const fn local_bounds(&self) -> Aabb {
        self.local_bounds
    }

    pub const fn world_transform(&self) -> Transform {
        self.world_transform
    }

    pub const fn visible(&self) -> bool {
        self.visible
    }
}

impl SceneCameraFrustumInspection {
    pub const fn camera(&self) -> CameraKey {
        self.camera
    }

    pub const fn node(&self) -> NodeKey {
        self.node
    }

    pub const fn near(&self) -> f32 {
        self.near
    }

    pub const fn far(&self) -> f32 {
        self.far
    }

    pub const fn corners(&self) -> &[Vec3; 8] {
        &self.corners
    }
}

impl SceneNormalInspection {
    pub const fn node(&self) -> NodeKey {
        self.node
    }

    pub const fn instance(&self) -> Option<InstanceId> {
        self.instance
    }

    pub const fn geometry(&self) -> GeometryHandle {
        self.geometry
    }

    pub const fn length(&self) -> f32 {
        self.length
    }

    pub fn segments(&self) -> &[[Vec3; 2]] {
        &self.segments
    }
}

const fn mesh_geometry(kind: &NodeKind) -> Option<GeometryHandle> {
    match kind {
        NodeKind::Mesh(mesh) => Some(mesh.geometry()),
        _ => None,
    }
}

const fn mesh_material(kind: &NodeKind) -> Option<MaterialHandle> {
    match kind {
        NodeKind::Mesh(mesh) => Some(mesh.material()),
        _ => None,
    }
}

fn material_preview<F>(
    kind: &NodeKind,
    assets: Option<&Assets<F>>,
) -> Option<SceneMaterialInspection> {
    let material_handle = mesh_material(kind)?;
    let assets = assets?;
    let material = assets.material(material_handle)?;
    Some(SceneMaterialInspection::new(
        material_handle,
        material,
        assets,
    ))
}

const fn camera_key(kind: &NodeKind) -> Option<CameraKey> {
    match kind {
        NodeKind::Camera(camera) => Some(*camera),
        _ => None,
    }
}

const fn light_key(kind: &NodeKind) -> Option<LightKey> {
    match kind {
        NodeKind::Light(light) => Some(*light),
        _ => None,
    }
}

const fn kind_name(kind: &NodeKind) -> &'static str {
    match kind {
        NodeKind::Empty => "Empty",
        NodeKind::Renderable(_) => "Renderable",
        NodeKind::Mesh(_) => "Mesh",
        NodeKind::Model(_) => "Model",
        NodeKind::InstanceSet(_) => "InstanceSet",
        NodeKind::Label(_) => "Label",
        NodeKind::Camera(_) => "Camera",
        NodeKind::Light(_) => "Light",
    }
}
