use cgmath::EuclideanSpace;
use cgmath::SquareMatrix;
use crevice::std430::AsStd430;
use wgpu::Buffer;
use wgpu::BufferDescriptor;
use wgpu::BufferUsages;
use wgpu::Device;
use wgpu::PushConstantRange;
use wgpu::RenderPass;
use wgpu::RenderPipeline;
use wgpu::ShaderStages;
use wgpu::TextureFormat;
use wgpu::VertexAttribute;
use wgpu::VertexBufferLayout;
use wgpu::VertexFormat;
use wgpu::include_wgsl;
use wgpu::util::BufferInitDescriptor;
use wgpu::util::DeviceExt;

use crate::state::Camera;

pub struct VoxelRenderer
{
    chunk_instances_capacity: u32,
    chunk_instances: Buffer,
    chunk_pipeline: RenderPipeline,
}

impl VoxelRenderer
{
    pub fn new(device: &Device, surface_format: TextureFormat) -> Self
    {
        let chunk_shader_module =
            device.create_shader_module(include_wgsl!("../shaders/chunk.wgsl"));
        let chunk_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Render3D Mesh Pipeline Layout"),
                bind_group_layouts: &[],
                push_constant_ranges: &[PushConstantRange {
                    stages: ShaderStages::VERTEX,
                    range: 0..ChunkPushConstants::std430_size_static() as u32,
                }],
            });

        let chunk_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("chunk_pipeline"),
            layout: Some(&chunk_pipeline_layout),
            vertex: wgpu::VertexState {
                module: &chunk_shader_module,
                entry_point: Some("vertex_main"),
                buffers: &[VertexBufferLayout {
                    array_stride: size_of::<i32>() as u64 * 3,
                    step_mode: wgpu::VertexStepMode::Instance,
                    attributes: &[VertexAttribute {
                        format: VertexFormat::Sint32x3,
                        offset: 0,
                        shader_location: 0,
                    }],
                }],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &chunk_shader_module,
                entry_point: Some("fragment_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: surface_format,
                    blend: None,
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                ..Default::default()
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: TextureFormat::Depth24Plus,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::Less,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState {
                count: 1,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            multiview: None,

            cache: None,
        });

        Self {
            chunk_instances_capacity: 1,
            chunk_instances: device.create_buffer(&BufferDescriptor {
                label: Some("chunk_instances_buffer"),
                size: size_of::<ChunkInstance>() as u64,
                usage: BufferUsages::VERTEX,
                mapped_at_creation: false,
            }),

            chunk_pipeline,
        }
    }

    pub fn set_chunks(&mut self, device: &Device, chunks: &[cgmath::Vector3<i32>])
    {
        self.chunk_instances = device.create_buffer_init(&BufferInitDescriptor {
            label: Some("chunk_instances_buffer"),
            contents: unsafe {
                std::slice::from_raw_parts(
                    chunks.as_ptr() as *const u8,
                    std::mem::size_of_val(chunks),
                )
            },
            usage: BufferUsages::VERTEX,
        });
        self.chunk_instances_capacity = chunks.len() as u32;
    }

    pub fn render(&mut self, render_pass: &mut RenderPass, camera: &Camera)
    {
        render_pass.set_pipeline(&self.chunk_pipeline);

        //renderpass.set_vertex_buffer(0, self.positions_buffer.slice(..));
        render_pass.set_push_constants(
            ShaderStages::VERTEX,
            0,
            ChunkPushConstants {
                view_projection: camera.view_proj(),
                transform: cgmath::Matrix4::identity(),
                eye_position: camera.eye.to_vec(),
            }
            .as_std430()
            .as_bytes(),
        );
        render_pass.set_vertex_buffer(0, self.chunk_instances.slice(..));
        render_pass.draw(0..(6 * 2 * 3), 0..(self.chunk_instances_capacity));
    }
}

struct ChunkInstance
{
    location: cgmath::Vector3<i32>,
}

#[derive(crevice::std430::AsStd430)]
pub struct ChunkPushConstants
{
    view_projection: cgmath::Matrix4<f32>,
    transform: cgmath::Matrix4<f32>,
    eye_position: cgmath::Vector3<f32>,
}
