use std::sync::Arc;
use wgpu::{CurrentSurfaceTexture, util::DeviceExt};
use winit::window::{Window};

use crate::texture::Texture;

pub mod pipeline;

pub struct WgpuCtx
{
    surface: wgpu::Surface<'static>,
    surface_conf: wgpu::SurfaceConfiguration,
    surface_texture: Option<wgpu::SurfaceTexture>,
    is_configured: bool,

    adapter: wgpu::Adapter,
    device: wgpu::Device,
    queue: wgpu::Queue,
}

pub struct SceneCtx<'view>
{
    view: &'view wgpu::TextureView,
    encoder: wgpu::CommandEncoder
}

impl WgpuCtx
{
    pub async fn new(
        instance: &wgpu::Instance,
        window: Arc<Window>,
    ) -> Self {
        let surface = instance.create_surface(window.clone()).unwrap();

        let adapter = instance
        .request_adapter(&wgpu::RequestAdapterOptions {
                compatible_surface: Some(&surface),
                ..Default::default()
            }).await.expect("Failed to find valid adapter");

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: None,
                required_features: wgpu::Features::empty(),
                required_limits: wgpu::Limits::downlevel_defaults(),
                experimental_features: wgpu::ExperimentalFeatures::disabled(),
                memory_hints: wgpu::MemoryHints::Performance,
                trace: wgpu::Trace::Off
            }).await.expect("Failed to find device and queue");

        let size = window.inner_size();
        let surface_config = surface.get_default_config(&adapter, size.width, size.height).unwrap();

        Self {
            surface: surface,
            surface_conf: surface_config,
            surface_texture: None,
            is_configured: false,
            adapter: adapter,
            device: device,
            queue: queue
        }
    }

    pub fn resize(
        &mut self,
        width: u32,
        height: u32)
    {
        self.surface_conf.width = width;
        self.surface_conf.height = height;
        self.surface.configure(&self.device, &self.surface_conf);
        self.is_configured = true;
    }

    pub fn is_ready(
        &self)
    -> bool {
        self.is_configured
    }

    pub fn surface_config(
        &self)
    -> wgpu::SurfaceConfiguration {
        self.surface_conf.clone()
    }

    pub fn adapter(
        &self)
    -> &wgpu::Adapter {
        &self.adapter
    }

    pub fn device(
        &self)
    -> &wgpu::Device {
        &self.device
    }

    pub fn queue(
        &self)
    -> &wgpu::Queue {
        &self.queue
    }

    pub fn prepare_surface(
        &mut self,
        view_descr: &wgpu::TextureViewDescriptor)
    -> Result<wgpu::TextureView, CurrentSurfaceTexture>
    {
        let surface_texture = self.surface.get_current_texture();
        match surface_texture {
            CurrentSurfaceTexture::Success(texture) => {
                let view = texture.texture.create_view(view_descr);
                self.surface_texture = Some(texture);
                Ok(view)
            },
            _ => Err(surface_texture)
        }
    }

    pub fn create_encoder(
        &self,
        descr: &wgpu::CommandEncoderDescriptor)
    -> wgpu::CommandEncoder
    {
        self.device.create_command_encoder(descr)
    }

    pub fn create_default_encoder(
        &self,
        label: Option<&'static str>)
    -> wgpu::CommandEncoder {
        self.create_encoder(
            &wgpu::CommandEncoderDescriptor {
                label: label
            }
        )
    }

    pub fn submit_encoder(
        &self,
        encoder: wgpu::CommandEncoder)
    {
        self.queue.submit(std::iter::once(encoder.finish()));
    }

    pub fn present_surface(
        &mut self)
    {
        let texture = self.surface_texture.take();
        match texture {
            Some(t) => t.present(),
            None => {}
        }
    }

    pub fn create_shader(
        &self,
        module_descr: wgpu::ShaderModuleDescriptor)
    -> wgpu::ShaderModule {
        self.device.create_shader_module(module_descr)
    }

    pub fn create_pipeline_layout(
        &self,
        layout_descr: &wgpu::PipelineLayoutDescriptor)
    -> wgpu::PipelineLayout {
        self.device.create_pipeline_layout(layout_descr)
    }

    pub fn create_render_pipeline(
        &self,
        pipeline_layout: &wgpu::RenderPipelineDescriptor)
    -> wgpu::RenderPipeline {
        self.device.create_render_pipeline(pipeline_layout)
    }

    pub fn create_buffer_init(
        &self,
        descr: &wgpu::util::BufferInitDescriptor
    ) -> wgpu::Buffer {
        self.device.create_buffer_init(descr)
    }

    pub fn create_buffer(
        &self,
        descr: &wgpu::BufferDescriptor)
    -> wgpu::Buffer {
        self.device.create_buffer(descr)
    }

    pub fn new_texture(
        &self,
        descr: wgpu::TextureDescriptor)
    -> Texture
    {
        Texture::new(&self.device, descr)
    }
     
} //impl WgpuCtx

impl<'view> SceneCtx<'view>
{
    pub fn from_view_default(
        wgpuctx: &WgpuCtx,
        view: &'view wgpu::TextureView,
        label: Option<&'static str>)
    -> Self {
        Self {
            view: view,
            encoder: wgpuctx.create_default_encoder(label)
        }
    }

    pub fn view(&self) -> &'view wgpu::TextureView
    { self.view }

    pub fn encoder(&self) -> &wgpu::CommandEncoder
    { &self.encoder }

    pub fn encoder_mut(&mut self) -> &mut wgpu::CommandEncoder
    { &mut self.encoder }

    pub fn submit(
        self, 
        wgpuctx: &WgpuCtx)
    {
        wgpuctx.submit_encoder(self.encoder);
    }
}

pub struct RenderPassBuilder<'encoder>
{
    label: Option<&'static str>,
    view: &'encoder wgpu::TextureView,
    clear_color: Option<wgpu::Color>
}

impl<'encoder> RenderPassBuilder<'encoder>
{
    pub fn new(
        label: Option<&'static str>,
        view: &'encoder wgpu::TextureView)
    -> Self {
        Self {
            label: label,
            view: view,
            clear_color: None
        }
    }

    pub fn clear_color(
        mut self,
        color: wgpu::Color)
    -> Self {
        self.clear_color = Some(color);
        self
    }

    pub fn build_from_encoder(
        self,
        encoder: &'encoder mut wgpu::CommandEncoder)
    -> wgpu::RenderPass<'encoder>
    {
        encoder.begin_render_pass(
            &wgpu::RenderPassDescriptor {
                label: self.label,
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: self.view,
                    resolve_target: None,
                    depth_slice: None,
                    ops: wgpu::Operations {
                        load: match self.clear_color {
                            Some(color) => wgpu::LoadOp::Clear(color),
                            None => wgpu::LoadOp::Load
                        },
                        store: wgpu::StoreOp::Store
                    }
                })],
                depth_stencil_attachment: None,
                occlusion_query_set: None,
                timestamp_writes: None,
                multiview_mask: None,
            }
        )
    }

    pub fn build_from_scene(
        self,
        scene: &'encoder mut SceneCtx)
    -> wgpu::RenderPass<'encoder>
    { self.build_from_encoder(scene.encoder_mut()) }
}// impl RenderPassBuilder
