use crate::errors::{AleaticoError, AleaticoResult};
use log::debug;
use std::sync::Arc;
use wgpu::{Adapter, CurrentSurfaceTexture, PresentMode};
use winit::window::Window;

/// Structure representing a surface
pub struct Surface {
    /// [`wgpu`] internal surface
    pub(crate) inner: wgpu::Surface<'static>,
    /// [`wgpu`] surface config
    pub(crate) config: wgpu::SurfaceConfiguration,
    /// Is this surface configured?
    pub(crate) is_configured: bool,
}

impl Surface {
    /// Create a new instance of [`Surface]
    pub fn new(
        window: Arc<Window>,
        raw_surface: wgpu::Surface<'static>,
        adapter: &mut Adapter,
    ) -> AleaticoResult<Self> {
        let size = window.inner_size();

        let surface_caps = raw_surface.get_capabilities(adapter);

        let surface_format = surface_caps
            .formats
            .iter()
            .find(|f| f.is_srgb())
            .copied()
            .unwrap_or(surface_caps.formats[0]);
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: size.width,
            height: size.height,
            present_mode: PresentMode::AutoVsync,
            alpha_mode: surface_caps.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };

        Ok(Self {
            inner: raw_surface,
            config,
            is_configured: false,
        })
    }

    /// Handle resize of a window in this surface
    pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
        debug!("can we pretend to leave and then we'll meet again when both our cars collide?");
        if width > 0 && height > 0 {
            self.config.width = width;
            self.config.height = height;
            self.inner.configure(&device, &self.config);
            self.is_configured = true;
        }
    }

    /// Acquire a [`SurfaceFrame`]
    pub(crate) fn acquire_frame(
        &mut self,
        device: &wgpu::Device,
    ) -> AleaticoResult<Option<SurfaceFrame>> {
        if !self.is_configured {
            return Ok(None);
        }

        let output = match self.inner.get_current_texture() {
            CurrentSurfaceTexture::Success(surface_texture) => surface_texture,
            CurrentSurfaceTexture::Suboptimal(surface_texture) => {
                drop(surface_texture);

                self.inner.configure(&device, &self.config);

                match self.inner.get_current_texture() {
                    CurrentSurfaceTexture::Success(surface_texture) => surface_texture,
                    CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture,
                    _ => return Ok(None),
                }
            }
            CurrentSurfaceTexture::Timeout
            | CurrentSurfaceTexture::Occluded
            | CurrentSurfaceTexture::Validation => {
                return Ok(None);
            }
            CurrentSurfaceTexture::Outdated => {
                self.inner.configure(&device, &self.config);
                return Ok(None);
            }
            CurrentSurfaceTexture::Lost => {
                return Err(AleaticoError::LostDevice);
            }
        };

        let view = output
            .texture
            .create_view(&wgpu::TextureViewDescriptor::default());

        Ok(Some(SurfaceFrame { output, view }))
    }
}

/// A single surface frame used during a render pass
pub(crate) struct SurfaceFrame {
    output: wgpu::SurfaceTexture,
    view: wgpu::TextureView,
}

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

    pub fn present(self) {
        self.output.present();
    }
}
