// Demonstrates the stateful Renderer by orbiting a camera around the test
// sphere across four frames. The renderer is constructed once; only the
// view matrix changes between renders. That's the shape a viewport in a
// painter app would use: hold a Renderer, swap camera matrices on the user
// dragging the viewport, call render() per frame.

use std::env;
use std::fs;
use std::process;

use hydra_rs::Renderer;

const DEFAULT_ASSET: &str = "examples/hydra_test.usda";
const WIDTH: u32 = 256;
const HEIGHT: u32 = 256;
const FRAMES: usize = 4;

fn main() {
    let path = env::args().nth(1).unwrap_or_else(|| DEFAULT_ASSET.to_string());

    let mut renderer = Renderer::new(&path).unwrap_or_else(|e| {
        eprintln!("renderer construction failed: {}", e.what());
        process::exit(1);
    });
    renderer.set_size(WIDTH, HEIGHT);

    // Override the default light with a key + fill so the orbit is more legible.
    renderer.clear_lights();
    renderer.add_distant_light([1.0, 1.0, 0.5], [1.0, 1.0, 0.95], 1.5);
    renderer.add_distant_light([-1.0, -0.3, 1.0], [0.4, 0.5, 0.8], 0.8);

    let radius = 5.0_f32;
    for i in 0..FRAMES {
        let theta = (i as f32) * std::f32::consts::TAU / FRAMES as f32;
        let eye = [radius * theta.cos(), 3.0, radius * theta.sin()];
        let center = [0.0, 0.0, 0.0];
        let up = [0.0, 1.0, 0.0];

        let view = look_at_row_major(eye, center, up);
        let proj = perspective_row_major(45.0, WIDTH as f32 / HEIGHT as f32, 0.1, 1000.0);

        renderer.set_camera_matrices(&view, &proj);
        let pixels = renderer.render().unwrap_or_else(|e| {
            eprintln!("frame {} render failed: {}", i, e.what());
            process::exit(1);
        });

        let out = format!("examples/hydra_orbit_{:02}.rgba", i);
        fs::write(&out, &pixels).expect("write rgba");
        println!(
            "frame {}/{}: eye=({:5.2}, {:5.2}, {:5.2}) → {}",
            i + 1,
            FRAMES,
            eye[0],
            eye[1],
            eye[2],
            out
        );
    }

    println!(
        "convert with: ffmpeg -y -f rawvideo -pixel_format rgba -video_size {}x{} -i examples/hydra_orbit_NN.rgba examples/hydra_orbit_NN.png",
        WIDTH, HEIGHT
    );
}

// USD's GfMatrix4d uses row-vector convention: vec_world * view = vec_camera.
// These helpers emit row-major 16-float arrays in that convention.

fn look_at_row_major(eye: [f32; 3], center: [f32; 3], up: [f32; 3]) -> [f32; 16] {
    let fwd = sub(center, eye);
    let z = neg(normalize(fwd));
    let x = normalize(cross(up, z));
    let y = cross(z, x);

    let tx = -dot(x, eye);
    let ty = -dot(y, eye);
    let tz = -dot(z, eye);

    // Row-vector convention: the rotation goes in the upper 3x3 with rows
    // (x, y, z) and translation in the bottom row.
    [
        x[0], y[0], z[0], 0.0,
        x[1], y[1], z[1], 0.0,
        x[2], y[2], z[2], 0.0,
        tx, ty, tz, 1.0,
    ]
}

fn perspective_row_major(fov_y_deg: f32, aspect: f32, near: f32, far: f32) -> [f32; 16] {
    let fov_y = fov_y_deg.to_radians();
    let f = 1.0 / (fov_y * 0.5).tan();
    let nf = 1.0 / (near - far);
    [
        f / aspect, 0.0, 0.0, 0.0,
        0.0, f, 0.0, 0.0,
        0.0, 0.0, (far + near) * nf, -1.0,
        0.0, 0.0, 2.0 * far * near * nf, 0.0,
    ]
}

fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}

fn neg(a: [f32; 3]) -> [f32; 3] {
    [-a[0], -a[1], -a[2]]
}

fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
    [
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0],
    ]
}

fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}

fn normalize(a: [f32; 3]) -> [f32; 3] {
    let len = (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt();
    [a[0] / len, a[1] / len, a[2] / len]
}
