use wgpu::RenderPipeline;

use crate::wgpuctx::WgpuCtx;
use crate::texture::Texture;
use crate::vertex::Vertex;
use crate::wgpuctx::pipeline::RenderPipelineBuilder;

struct CanvasTexture
{
    pub texture: Texture,
    buffer: wgpu::Buffer
}

pub struct Canvas
{
    pipeline: wgpu::RenderPipeline,
    vertex_buffer: wgpu::Buffer,
    texture: CanvasTexture,
    
    position: winit::dpi::LogicalPosition<f32>,
    size: winit::dpi::LogicalSize<f32>
}

impl Canvas
{
    fn create_canvas_texture(
        wgpuctx: &WgpuCtx,
        canvas_size: winit::dpi::LogicalSize<f32>,
        window_size: winit::dpi::PhysicalSize<u32>)
    -> CanvasTexture
    {
        let canvas_physical_size = winit::dpi::PhysicalSize::<u32>::new(
            (window_size.width as f32 * canvas_size.width).floor() as u32,
            (window_size.height as f32 * canvas_size.height).floor() as u32
        );

        CanvasTexture::new(wgpuctx, canvas_physical_size)
    }

    pub fn new(
        wgpuctx: &WgpuCtx,
        window_size: winit::dpi::PhysicalSize<u32>,
        position: winit::dpi::LogicalPosition<f32>,
        size: winit::dpi::LogicalSize<f32>
    ) -> Result<Self, ()> {

        let pos_x = position.x * 2.0 - 1.0;
        let pos_y = position.y * 2.0 - 1.0;
        let size_x = size.width * 2.0;
        let size_y = size.height * 2.0;

        let vertices: &[Vertex] = &[
            Vertex { position: [ pos_x,        pos_y,        0.0 ], uv: [ 0.0, 1.0 ] },
            Vertex { position: [ pos_x+size_x, pos_y,        0.0 ], uv: [ 1.0, 1.0 ] },
            Vertex { position: [ pos_x+size_x, pos_y+size_y, 0.0 ], uv: [ 1.0, 0.0 ] },
            Vertex { position: [ pos_x+size_x, pos_y+size_y, 0.0 ], uv: [ 1.0, 0.0 ] },
            Vertex { position: [ pos_x,        pos_y+size_y, 0.0 ], uv: [ 0.0, 0.0 ] },
            Vertex { position: [ pos_x,        pos_y,        0.0 ], uv: [ 0.0, 1.0 ] },
        ];

        let vertex_buffer = wgpuctx.create_buffer_init(
            &wgpu::util::BufferInitDescriptor {
                label: Some("Vertex Buffer"),
                contents: bytemuck::cast_slice(vertices),
                usage: wgpu::BufferUsages::VERTEX
            }
        );

        let shader = wgpuctx.create_shader(
            wgpu::include_wgsl!(concat!(
                    env!("CARGO_MANIFEST_DIR"), 
                    "/assets/shaders/canvas.wgsl")
        ));

        let canvas_texture = Canvas::create_canvas_texture(wgpuctx, size, window_size);

        let render_pipeline = RenderPipelineBuilder::new(
            &shader)
            .add_bindgroup(&canvas_texture.texture.bindgrouplayout)
            .add_vertex_layout(Vertex::descr())
            .build(Some("Canvas render pipleine"), wgpuctx);
        
        Ok(Self {
            pipeline: render_pipeline,
            vertex_buffer: vertex_buffer,
            texture: canvas_texture,
            position: position,
            size: size
        })
    }

    pub fn resize(
        &mut self,
        wgpuctx: &WgpuCtx,
        width: u32,
        height: u32)
    {
        let window_size = winit::dpi::PhysicalSize::new(width, height);
        self.texture = Canvas::create_canvas_texture(wgpuctx, self.size, window_size);
    }

    pub fn present(
        &mut self,
        screen_pass: &mut wgpu::RenderPass
        )
    -> Result<(), wgpu::SurfaceError> {

        screen_pass.set_pipeline(&self.pipeline);
        self.texture.texture.use_on_pass(screen_pass, 0);
        screen_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
        screen_pass.draw(0..6,0..1);
        Ok(())
    }

    pub fn view(&self)
    -> &wgpu::TextureView
    {
        self.texture.texture.view()
    }
} //impl Canvas

impl CanvasTexture
{
    pub fn new(
        wgpuctx: &WgpuCtx,
        size: winit::dpi::PhysicalSize<u32>)
    -> Self
    {
        let u32_size = std::mem::size_of::<u32>() as u32;
        
        let buffer_size = (u32_size * size.width * size.height) as wgpu::BufferAddress;
        let buffer_descr = wgpu::BufferDescriptor {
            size: buffer_size,
            usage: wgpu::BufferUsages::COPY_DST,
            label: None,
            mapped_at_creation: false
        };

        let texture = wgpuctx.new_render_texture(size);
        let buffer = wgpuctx.create_buffer(&buffer_descr);

        Self {
            texture: texture,
            buffer: buffer
        }
    }
}

impl WgpuCtx
{
    pub fn new_render_texture(
        &self,
        size: winit::dpi::PhysicalSize<u32>)
    -> Texture
    {
       let descr = wgpu::TextureDescriptor {
            size: wgpu::Extent3d {
                width: size.width,
                height: size.height,
                depth_or_array_layers: 1
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Bgra8UnormSrgb,
            view_formats: &[
                wgpu::TextureFormat::Bgra8UnormSrgb
            ],
            usage: wgpu::TextureUsages::TEXTURE_BINDING |
                   wgpu::TextureUsages::COPY_SRC |
                   wgpu::TextureUsages::RENDER_ATTACHMENT,
            label: None
        };
        self.new_texture(descr)
    }
}
