//! Stage 4 example: load primitive meshes and render them lit, in 3D.
//!
//! Run with:
//!   cargo run -p oxide-examples --bin hello_mesh
//!
//! Shows a spinning cube, a sphere, and a ground plane drawn through the
//! [`ForwardRenderer`] with a single directional light. Esc quits.

#![deny(warnings)]

use oxide_engine::math::Quat;
use oxide_engine::prelude::*;
use oxide_engine::window::event::{ElementState, Key, NamedKey, WindowEvent};
use oxide_engine::window::RenderCtx;

/// GPU resources, built lazily on the first frame (once the surface format is
/// known) and reused thereafter.
struct Gpu3d {
    pipeline: RenderPipeline,
    cube: GpuMesh,
    sphere: GpuMesh,
    plane: GpuMesh,
}

#[derive(Default)]
struct HelloMesh {
    angle: f32,
    gpu: Option<Gpu3d>,
}

impl WindowApp for HelloMesh {
    fn init(&mut self, ctx: &mut AppCtx<'_>) {
        ctx.set_clear_color(Color::rgb(0.05, 0.06, 0.09));
        log::info!("hello_mesh: spinning cube + sphere + ground plane (Esc quits)");
    }

    fn event(&mut self, ctx: &mut AppCtx<'_>, event: &WindowEvent) {
        if let WindowEvent::KeyboardInput { event: key, .. } = event {
            if key.state == ElementState::Pressed && key.logical_key == Key::Named(NamedKey::Escape)
            {
                ctx.request_exit();
            }
        }
    }

    fn update(&mut self, ctx: &mut AppCtx<'_>) {
        self.angle += ctx.dt;
    }

    fn render(&mut self, ctx: &RenderCtx<'_>) {
        let device = ctx.gpu.device();
        let queue = ctx.gpu.queue();

        let gpu = self.gpu.get_or_insert_with(|| {
            // The window runner already clears the surface to the configured
            // clear color before `render`, so the viewport pipeline is just the
            // forward pass (no clear pass needed here).
            let mut pipeline = RenderPipeline::new();
            pipeline.add_pass("forward", ForwardPass::new(device, ctx.surface_format));
            Gpu3d {
                pipeline,
                cube: Mesh::cube().upload(device, "cube"),
                sphere: Mesh::uv_sphere(0.8, 32, 16).upload(device, "sphere"),
                plane: Mesh::plane(12.0).upload(device, "plane"),
            }
        });

        // Orbit the camera slowly around the scene.
        let eye = Vec3::new(
            4.0 * (self.angle * 0.3).cos(),
            2.6,
            4.0 * (self.angle * 0.3).sin(),
        );
        let view = Transform::looking_at(eye, Vec3::new(0.0, 0.2, 0.0), Vec3::Y);
        let camera = Camera::default();

        let objects = [
            RenderObject {
                mesh: &gpu.plane,
                material: Material::diffuse(Color::rgb(0.25, 0.27, 0.30)),
                transform: Transform::from_translation(Vec3::new(0.0, -1.0, 0.0)),
            },
            RenderObject {
                mesh: &gpu.cube,
                material: Material::diffuse(Color::rgb(0.85, 0.20, 0.15)),
                transform: Transform::from_trs(
                    Vec3::new(-1.3, 0.0, 0.0),
                    Quat::from_euler(oxide_engine::math::EulerRot::YXZ, self.angle, 0.4, 0.0),
                    Vec3::ONE,
                ),
            },
            RenderObject {
                mesh: &gpu.sphere,
                material: Material::metal(Color::rgb(0.55, 0.65, 0.95), 0.35),
                transform: Transform::from_translation(Vec3::new(1.3, 0.2, 0.0)),
            },
        ];

        let lighting = Lighting::default();
        gpu.pipeline.render(&mut FrameContext {
            device,
            queue,
            color: ctx.view,
            size: ctx.size,
            viewport_rect: None,
            clear_color: Color::rgb(0.05, 0.06, 0.09),
            camera: &camera,
            view_transform: &view,
            lighting: &lighting,
            objects: &objects,
        });
    }
}

fn main() -> anyhow::Result<()> {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
    let config = WindowConfig {
        title: "Oxide — hello_mesh".to_string(),
        width: 960,
        height: 540,
        ..Default::default()
    };
    run(config, HelloMesh::default())
}
