#![allow(dead_code)]

use glc::{
    array_buffer::{ArrayBuffer, Target, Usage},
    error::Error,
    matrix::Matrix4f,
    vector::Vector2f,
};

pub struct Camera {
    buffer: ArrayBuffer,
}

impl Camera {
    pub fn new() -> Result<Self, Error> {
        let mut buffer = ArrayBuffer::new(Target::UniformBuffer)?;
        buffer.buffer_data(
            Usage::StaticDraw,
            &[Data {
                projection: Matrix4f::identity(),
                view: Matrix4f::identity(),
                size: Vector2f::default(),
            }],
        )?;

        Ok(Self { buffer })
    }

    pub fn resize(&mut self, w: f32, h: f32) -> Result<(), Error> {
        let mut data = self.buffer.map_mut::<Data>(true)?;

        data[0].projection = Matrix4f::ortho(-w / 2.0, w / 2.0, -h / 2.0, h / 2.0, -100.0, 100.0);
        data[0].size = (w, h).into();

        Ok(())
    }

    pub fn update(&mut self, view: Matrix4f) -> Result<(), Error> {
        let mut data = self.buffer.map_mut::<Data>(true)?;

        data[0].view = data[0].view * view;

        Ok(())
    }

    pub fn update_with<F>(&mut self, f: F) -> Result<(), Error>
    where
        F: FnOnce(&Matrix4f) -> Matrix4f,
    {
        let mut data = self.buffer.map_mut::<Data>(true)?;

        data[0].view = f(&data[0].view);

        Ok(())
    }

    pub fn bind(&self, index: gl::GLuint) -> Result<(), Error> {
        Error::checked(|| self.buffer.bind_buffer_base(index))
    }
}

#[repr(C, packed)]
struct Data {
    projection: Matrix4f,
    view: Matrix4f,
    size: Vector2f,
}
