use std::rc::Rc;

use super::transforms::Transform;
use super::transforms::{position::Position, trackball::Trackball, ChainedTransform};
use super::{AttaWithProgram, AttachWithMouse};
use crate::camera::Camera;
use crate::errors::*;
use glow::HasContext;
use nalgebra_glm::Vec3;

#[derive(Clone)]
pub struct ThreeD {
    transform: Rc<ChainedTransform>,
    projection: nalgebra_glm::Mat4,
    trackball: Trackball,
    camera: Camera,
}

impl ThreeD {
    pub fn new(aspect: f32, z_near: f32, z_far: f32, fov: f32) -> Result<Self> {
        let trackball = Trackball::new()?;
        let transform = ChainedTransform::from(&trackball).chain(&Position::new()?);

        let camera = Camera::new(
            Vec3::new(0.0, -1.0, 0.0),
            Vec3::new(0.0, 1.0, 0.0),
            Vec3::new(0.0, 1.0, 0.0),
        );

        let projection = nalgebra_glm::perspective(aspect, fov.to_radians(), z_near, z_far);

        Ok(Self {
            transform: Rc::new(transform),
            trackball,
            camera,
            projection,
        })
    }
}

impl Default for ThreeD {
    fn default() -> Self {
        Self::new(1.0, 2.0, 1000.0, 45.0).unwrap()
    }
}

impl AttaWithProgram for ThreeD {
    fn attach_with_program(
        &self,
        gl: &glow::Context,
        program: &crate::components::Program,
    ) -> Result<()> {
        unsafe {
            let view = program.get_uniform_location(gl, "trackball_view");
            let projection = program.get_uniform_location(gl, "trackball_projection");

            let model = program.get_uniform_location(gl, "trackball_model");
            // self.trackball.attach_with_program(gl, program)?;

            let view_mat = nalgebra_glm::translation(&nalgebra_glm::vec3(0.0, 0.0, -3.0));

            gl.uniform_matrix_4_f32_slice(
                view.as_ref(),
                false,
                // nalgebra::Matrix4::identity().as_slice(),
                view_mat.as_slice(),
                // self.camera.get_view_matrix().as_slice(),
            );

            println!("projection: {:?}", self.projection);

            let id: nalgebra_glm::Mat4 = nalgebra_glm::identity();
            let scale_factor = nalgebra_glm::vec3(1.5, 1.5, 1.0);
            let res = nalgebra_glm::scaling(&scale_factor);

            let rotate =
                nalgebra_glm::rotation(45.0f32.to_radians(), &nalgebra_glm::vec3(1.0, 0.0, 0.0));

            gl.uniform_matrix_4_f32_slice(
                model.as_ref(),
                false,
                res.as_slice(), // nalgebra::Matrix4::identity().as_slice(),
            );

            gl.uniform_matrix_4_f32_slice(projection.as_ref(), false, self.projection.as_slice());

            // gl.uniform_matrix_4_f32_slice(
            //     projection.as_ref(),
            //     false,
            //     nalgebra::Matrix4::identity().as_slice(),
            // );
        }
        Ok(())
    }
}

impl Transform for ThreeD {
    fn snippet(&self) -> &crate::components::Snippet {
        self.transform.snippet()
    }
}

impl AttachWithMouse for ThreeD {
    fn attach_with_mouse(&mut self, state: &super::MouseState) {
        if let super::MouseState::Drag { from, delta } = state {
            self.trackball
                .on_mouse_drag(from[0], from[1], delta[0], delta[1]);
        }
    }
}
