use crate::{
    material::graph::MaterialGraph,
    material_graph,
    math::*,
    mesh::{
        vertex_factory::{StaticVertexFactory, VertexType},
        MeshDrawMode, MeshError,
    },
    vertex_type,
};
use serde::{Deserialize, Serialize};

pub fn default_gizmo_color_material_graph() -> MaterialGraph {
    material_graph! {
        inputs {
            [vertex] domain TintColor: vec4 = {vec4(1.0, 1.0, 1.0, 1.0)};
        }

        outputs {
            [fragment] domain BaseColor: vec4;
        }

        [[TintColor => vColor] -> BaseColor]
    }
}

pub fn gizmo_domain_graph() -> MaterialGraph {
    material_graph! {
        inputs {
            [fragment] domain BaseColor: vec4 = {vec4(1.0, 1.0, 1.0, 1.0)};

            [vertex] uniform model: mat4;
            [vertex] uniform view: mat4;
            [vertex] uniform projection: mat4;

            [vertex] in position: vec3 = vec3(0.0, 0.0, 0.0);
            [vertex] in phase: float = 0.0;
            [vertex] in color: vec4 = vec4(1.0, 1.0, 1.0, 1.0);
        }

        outputs {
            [vertex] domain LocalPosition: vec3;
            [vertex] domain WorldPosition: vec3;
            [vertex] domain ScreenPosition: vec3;
            [vertex] domain Phase: float;
            [vertex] domain TintColor: vec4;

            [vertex] builtin gl_Position: vec4;
            [fragment] out finalColor: vec4;
        }

        [view_projection = (mul_mat4, a: projection, b: view)]
        [pos = (append_vec4, a: position, b: {1.0})]
        [world_position = (truncate_vec4, v: (mul_mat4_vec4, a: model, b: pos))]
        [pos = (append_vec4, a: world_position, b: {1.0})]
        [screen_position = (truncate_vec4, v: (mul_mat4_vec4, a: view_projection, b: pos))]

        [position -> LocalPosition]
        [world_position -> WorldPosition]
        [screen_position -> ScreenPosition]
        [phase -> Phase]
        [color -> TintColor]
        [(append_vec4, a: screen_position, b: {1.0}) -> gl_Position]
        [BaseColor -> finalColor]
    }
}

fn default_position() -> vek::Vec3<f32> {
    vec3(0.0, 0.0, 0.0)
}

fn default_color() -> vek::Vec4<f32> {
    vec4(1.0, 1.0, 1.0, 1.0)
}

pub trait GizmoDomain: VertexType {}

vertex_type! {
    #[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
    pub struct GizmoVertex {
        #[serde(default = "default_position")]
        pub position: vec3 = position(0),
        #[serde(default)]
        pub phase: float = phase(0),
        #[serde(default = "default_color")]
        pub color: vec4 = color(0),
    }
}

impl GizmoDomain for GizmoVertex {}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GizmoFactory {
    vertices: Vec<GizmoVertex>,
    indices: Vec<u32>,
}

impl Default for GizmoFactory {
    fn default() -> Self {
        Self::with_capacity(0, 0)
    }
}

impl GizmoFactory {
    pub fn with_capacity(vertex_capacity: usize, index_capacity: usize) -> Self {
        Self {
            vertices: Vec::with_capacity(vertex_capacity),
            indices: Vec::with_capacity(index_capacity),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.indices.is_empty()
    }

    pub fn clear(&mut self) {
        self.vertices.clear();
        self.indices.clear();
    }

    pub fn line(&mut self, color: vek::Vec4<f32>, from: vek::Vec3<f32>, to: vek::Vec3<f32>) {
        self.vertices.reserve(2);
        self.indices.reserve(2);
        let offset = self.vertices.len() as u32;
        self.vertices.push(GizmoVertex {
            position: from,
            phase: 0.0,
            color,
        });
        self.vertices.push(GizmoVertex {
            position: to,
            phase: 1.0,
            color,
        });
        self.indices.push(offset);
        self.indices.push(offset + 1);
    }

    pub fn lines(
        &mut self,
        color: vek::Vec4<f32>,
        iter: impl Iterator<Item = (vek::Vec3<f32>, vek::Vec3<f32>)>,
    ) {
        let size = iter.size_hint();
        let size = size.1.unwrap_or(size.0);
        self.vertices.reserve(size * 2);
        self.indices.reserve(size * 2);
        let mut phase = false;
        let mut offset = self.vertices.len() as u32;
        for (from, to) in iter {
            let (p1, p2) = if phase { (1.0, 0.0) } else { (0.0, 1.0) };
            self.vertices.push(GizmoVertex {
                position: from,
                phase: p1,
                color,
            });
            self.vertices.push(GizmoVertex {
                position: to,
                phase: p2,
                color,
            });
            self.indices.push(offset);
            self.indices.push(offset + 1);
            offset += 2;
            phase = !phase;
        }
    }

    pub fn line_string(
        &mut self,
        color: vek::Vec4<f32>,
        mut iter: impl Iterator<Item = vek::Vec3<f32>>,
    ) -> bool {
        let size = iter.size_hint();
        let size = size.1.unwrap_or(size.0);
        self.vertices.reserve(size);
        self.indices.reserve(size.saturating_sub(1) * 2);
        let mut phase = false;
        let mut offset = self.vertices.len() as u32;
        let position = match iter.next() {
            Some(position) => position,
            None => return false,
        };
        self.vertices.push(GizmoVertex {
            position,
            phase: 0.0,
            color,
        });
        for position in iter {
            phase = !phase;
            let p = if phase { 1.0 } else { 0.0 };
            self.vertices.push(GizmoVertex {
                position,
                phase: p,
                color,
            });
            self.indices.push(offset);
            self.indices.push(offset + 1);
            offset += 1;
        }
        true
    }

    pub fn polygon(
        &mut self,
        color: vek::Vec4<f32>,
        mut iter: impl Iterator<Item = vek::Vec3<f32>>,
    ) -> bool {
        let size = iter.size_hint();
        let size = size.1.unwrap_or(size.0);
        self.vertices.reserve(size);
        self.indices.reserve(size * 2);
        let mut phase = false;
        let first = self.vertices.len() as u32;
        let mut offset = first;
        let position = match iter.next() {
            Some(position) => position,
            None => return false,
        };
        self.vertices.push(GizmoVertex {
            position,
            phase: 0.0,
            color,
        });
        for position in iter {
            phase = !phase;
            let p = if phase { 1.0 } else { 0.0 };
            self.vertices.push(GizmoVertex {
                position,
                phase: p,
                color,
            });
            self.indices.push(offset);
            self.indices.push(offset + 1);
            offset += 1;
        }
        self.indices.push(offset);
        self.indices.push(first);
        true
    }

    pub fn factory(&self) -> Result<StaticVertexFactory, MeshError> {
        let mut result = StaticVertexFactory::new(
            GizmoVertex::vertex_layout()?,
            self.vertices.len(),
            self.indices.len(),
            MeshDrawMode::Lines,
        );
        let positions = self.vertices.iter().map(|v| v.position).collect::<Vec<_>>();
        result.vertices_vec3f("position", &positions, None)?;
        let phases = self.vertices.iter().map(|v| v.phase).collect::<Vec<_>>();
        result.vertices_scalar("phase", &phases, None)?;
        let colors = self.vertices.iter().map(|v| v.color).collect::<Vec<_>>();
        result.vertices_vec4f("color", &colors, None)?;
        let indices = self
            .indices
            .chunks_exact(2)
            .map(|c| (c[0], c[1]))
            .collect::<Vec<_>>();
        result.lines(&indices, None)?;
        Ok(result)
    }
}
