use glow::HasContext;

use crate::{
    components::{self, CodeType, Program, Snippet},
    errors::*,
    graphics::AttaWithProgram,
};
#[derive(Debug, Clone)]
pub struct Viewport {
    snippet: Snippet,
    viewport_global: [f32; 4],
    viewport_local: Option<[f32; 4]>,
}

impl Viewport {
    pub fn new() -> Result<Self> {
        let snippets = Snippet::new(
            "viewport",
            CodeType::<&'static str>::Path("transform/viewport.glsl".into()),
            true,
            None,
        )?;

        Ok(Self {
            snippet: snippets,
            viewport_global: [0.0, 0.0, 0.0, 0.0],
            viewport_local: None,
        })
    }

    pub fn snippet(&self) -> &Snippet {
        &self.snippet
    }

    pub fn set_global(&mut self, x: f32, y: f32, width: f32, height: f32) {
        self.viewport_global = [x, y, width, height];
    }

    pub fn create_local(&self, x: f32, y: f32, width: f32, height: f32) -> Self {
        let mut l = self.clone();
        l.viewport_local = Some([x, y, width, height]);
        l
    }

    fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result<()> {
        unsafe {
            let global = self.snippet.find_symbol("viewport_global").unwrap();
            let l = program.get_uniform_location(gl, &global);
            gl.uniform_4_f32(
                l.as_ref(),
                self.viewport_global[0],
                self.viewport_global[1],
                self.viewport_global[2],
                self.viewport_global[3],
            );

            let local = self.snippet.find_symbol("viewport_local").unwrap();

            if let Some([x, y, z, w]) = self.viewport_local {
                let l = program.get_uniform_location(gl, &local);
                let l = l.as_ref();
                gl.uniform_4_f32(l, x, y, z, w);
            } else {
                let l = program.get_uniform_location(gl, &local);
                let l = l.as_ref();
                gl.uniform_4_f32(
                    l,
                    self.viewport_global[0],
                    self.viewport_global[1],
                    self.viewport_global[2],
                    self.viewport_global[3],
                );
            }

            let viewport = self.snippet.find_symbol("viewport_transform").unwrap();
            let l = program.get_uniform_location(gl, &viewport);
            gl.uniform_1_f32(l.as_ref(), 0.0);

            let viewport = self.snippet.find_symbol("viewport_clipping").unwrap();
            let l = program.get_uniform_location(gl, &viewport);
            gl.uniform_1_f32(l.as_ref(), 0.0);
        }
        Ok(())
    }
}

impl AttaWithProgram for Viewport {
    fn attach_with_program(&self, gl: &glow::Context, program: &Program) -> Result<()> {
        self.attach_with_program(gl, program)
    }
}
