use std::{fmt::Debug, rc::Rc};

use anyhow::Result;
use glam::{UVec2, Vec2};
use image::DynamicImage;

use crate::{
    font::FontManager,
    renderer::post::ScreenShader,
    renderer::traits::{PostShader, Shader},
    text::Text,
};

#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum ScaleMode {
    #[default]
    Window,
    Fixed {
        width: u32,
        height: u32,
    },
    FixedHeight(u32),
    FixedWidth(u32),
}

/// Backend state, used to create shaders or other wgpu resources
#[derive(Debug, Clone)]
pub struct BackendState {
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
    pub surface_config: wgpu::SurfaceConfiguration,
    pub surface_view_format: wgpu::TextureFormat,
}

/// Render subsystem
pub(crate) struct Render {
    pub(crate) screen_scale: Vec2,
    pub(crate) inv_screen_scale: Vec2,
    pub(crate) screen_size: Vec2,
    logical_size: Vec2,
    scale_mode: ScaleMode,
    pub(crate) dpi_scale_factor: f64,
    pub(crate) backend_state: BackendState,
    pub(crate) shader: Rc<dyn Shader>,
    pub(crate) send_shader_data: Option<Vec<u8>>,
    pub(crate) post_shader: Box<dyn PostShader>,
    pub(crate) solid_shader: Rc<dyn Shader>,
    pub(crate) circle_shader: Rc<dyn Shader>,
}

impl Render {
    pub(crate) fn new(backend_state: BackendState) -> Self {
        let renderer = Rc::new(crate::renderer::shader2d::default::DefaultShader::new(
            &backend_state,
        ));
        let solid_shader = Rc::new(crate::renderer::shader2d::solid::SolidShader::new(
            &backend_state,
        ));
        let circle_shader = Rc::new(crate::renderer::shader2d::circle::CircleShader::new(
            &backend_state,
        ));
        let post_shader = Box::new(ScreenShader::setup(&backend_state));
        Self {
            screen_scale: Vec2::splat(1.0),
            inv_screen_scale: Vec2::splat(1.0),
            screen_size: Vec2::default(),
            logical_size: Vec2::default(),
            scale_mode: ScaleMode::default(),
            dpi_scale_factor: 1.0,
            backend_state,
            shader: renderer,
            send_shader_data: None,
            post_shader,
            solid_shader,
            circle_shader,
        }
    }

    /// Set the sprite shader
    pub(crate) fn set_shader(&mut self, shader: Rc<dyn Shader>) -> Rc<dyn Shader> {
        self.send_shader_data = None;
        core::mem::replace(&mut self.shader, shader)
    }

    /// Send the shader args
    pub(crate) fn send_shader_data(&mut self, data: Vec<u8>) {
        self.send_shader_data = Some(data);
    }

    pub(crate) fn set_post_shader(&mut self, post_shader: Box<dyn PostShader>) {
        self.post_shader = post_shader;
    }

    pub(crate) fn resize(&mut self, size: UVec2, dpi_scale_factor: f64) {
        self.screen_size = Vec2::new(size.x as f32, size.y as f32);
        self.dpi_scale_factor = dpi_scale_factor;
        // calculate scale
        match self.scale_mode {
            ScaleMode::Window => {
                self.screen_scale = Vec2::splat(1.0);
            }
            ScaleMode::Fixed { width, height } => {
                self.screen_scale =
                    Vec2::new(size.x as f32 / width as f32, size.y as f32 / height as f32);
            }
            ScaleMode::FixedHeight(height) => {
                self.screen_scale = Vec2::splat(size.y as f32 / height as f32);
            }
            ScaleMode::FixedWidth(width) => {
                self.screen_scale = Vec2::splat(size.x as f32 / width as f32);
            }
        }

        self.logical_size = (self.screen_size / self.screen_scale).ceil();
        self.inv_screen_scale = 1.0 / self.screen_scale;
    }

    pub(crate) fn render_text_texture(
        &mut self,
        font_manager: &FontManager,
        text: &Text,
    ) -> Result<(DynamicImage, UVec2)> {
        let (buffer, size) = font_manager.render_text_texture(text, self.dpi_scale_factor as f32);
        Ok((buffer.into(), size))
    }

    pub(crate) fn scale_mode(&self) -> ScaleMode {
        self.scale_mode
    }

    pub(crate) fn set_scale_mode(&mut self, mode: ScaleMode) {
        self.scale_mode = mode;
    }

    pub(crate) fn logical_size(&self) -> Vec2 {
        self.logical_size
    }

    pub(crate) fn screen_size(&self) -> Vec2 {
        self.screen_size
    }
}
