use bytemuck::cast_slice;
use glam::{Mat4, Vec3};
use wgpu::util::DeviceExt;

use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceRaw, Mesh, Vertex};
use crate::render::{self, DrawCall, Pipeline};
use crate::utility::transform::Transform;
use crate::world::Storage;

const GIZMO_DISTANCE_SCALE: f32 = 0.1;
const ARROW_BASE: f32 = 0.85;
const ARROW_SPREAD: f32 = 0.08;

pub fn create_gizmo_mesh(device: &wgpu::Device) -> Mesh
{
    let vertices = vec![
        Vertex {
            position: [0.0, 0.0, 0.0],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, 0.0, 0.0],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, 0.0, 0.0],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, 1.0, 0.0],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, 0.0, 0.0],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, 0.0, 1.0],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_BASE, ARROW_SPREAD, 0.0],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_BASE, -ARROW_SPREAD, 0.0],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_BASE, 0.0, ARROW_SPREAD],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_BASE, 0.0, -ARROW_SPREAD],
            normal: [1.0, 0.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_SPREAD, ARROW_BASE, 0.0],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [-ARROW_SPREAD, ARROW_BASE, 0.0],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, ARROW_BASE, ARROW_SPREAD],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, ARROW_BASE, -ARROW_SPREAD],
            normal: [0.0, 1.0, 0.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [ARROW_SPREAD, 0.0, ARROW_BASE],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [-ARROW_SPREAD, 0.0, ARROW_BASE],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, ARROW_SPREAD, ARROW_BASE],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
        Vertex {
            position: [0.0, -ARROW_SPREAD, ARROW_BASE],
            normal: [0.0, 0.0, 1.0],
            uv: [0.0, 0.0],
        },
    ];

    let indices = vec![
        0, 1, 2, 3, 4, 5, 6, 1, 7, 1, 8, 1, 9, 1, 10, 3, 11, 3, 12, 3, 13, 3, 14, 5, 15, 5, 16, 5,
        17, 5,
    ];

    Mesh::new(device, &vertices, &indices)
}

pub fn create_gizmo_instance_buffer(device: &wgpu::Device) -> wgpu::Buffer
{
    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some("Gizmo Instance Buffer"),
        contents: cast_slice(&[InstanceRaw {
            model: Mat4::IDENTITY.to_cols_array_2d(),
            dissolve_amount: 0.0,
            _padding: [0.0; 3],
        }]),
        usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
    })
}

pub fn render_transform_gizmo(
    entity: EntityHandle,
    transforms: &Storage<Transform>,
    camera_position: Vec3,
    gizmo_mesh: &Mesh,
    instance_buffer: &wgpu::Buffer,
) -> Vec<DrawCall>
{
    let transform = match transforms.get(entity)
    {
        Some(t) => t,
        None => return Vec::new(),
    };

    let distance = camera_position.distance(transform.position);
    let scale = (distance * GIZMO_DISTANCE_SCALE).max(0.1);

    let model = Mat4::from_scale_rotation_translation(
        Vec3::splat(scale),
        transform.rotation,
        transform.position,
    );

    let instance_data = InstanceRaw {
        model: model.to_cols_array_2d(),
        dissolve_amount: 0.0,
        _padding: [0.0; 3],
    };

    render::with_queue(|queue| {
        queue.write_buffer(instance_buffer, 0, cast_slice(&[instance_data]));
    });

    vec![DrawCall {
        vertex_buffer: gizmo_mesh.vertex_buffer.clone(),
        index_buffer: gizmo_mesh.index_buffer.clone(),
        num_indices: gizmo_mesh.num_indices,
        model,
        pipeline: Pipeline::GizmoLines,
        instance_buffer: Some(instance_buffer.clone()),
        num_instances: 1,
        tile_scale: 4.0,
        enable_dissolve: false,
        enable_snow_light: false,
        displacement_bind_group: None,
        entity: None,
    }]
}
