//! Diagnostic: assemble a ship from the BAKED capture table (or the static
//! scene graph with --static) and render orthographic PNGs + print per-part
//! world bounds, to see exactly what the viewer shows.
//!   cargo run --release --example ship_render -- <Stage_SNN.xpr path> <ship_id> <out_prefix> [--static]
use std::collections::HashSet;
use sylpheed_formats::mesh::{ScenePart, Xbg7Model};
use sylpheed_formats::ship::assemble_ship;
use sylpheed_formats::ship_capture::{embedded_placement, to_scene_parts};

fn main() {
    let a: Vec<String> = std::env::args().collect();
    let use_static = a.iter().any(|s| s == "--static");
    let (path, id, out) = (&a[1], &a[2], &a[3]);
    let bytes = std::fs::read(path).unwrap();

    let placed: Vec<ScenePart> = if use_static {
        assemble_ship(&bytes, id, true)
    } else {
        let cap = embedded_placement(id).expect("ship not in baked table");
        to_scene_parts(&cap)
    };
    let want: HashSet<String> = placed.iter().map(|p| p.resource.clone()).collect();
    let models = Xbg7Model::models_named(&bytes, &want, &|| false);

    // World triangles + per-part bounds.
    let mut tris: Vec<[[f32; 3]; 3]> = Vec::new();
    for p in &placed {
        let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue };
        let mut lo = [f32::MAX; 3];
        let mut hi = [f32::MIN; 3];
        for sub in &m.meshes {
            let w: Vec<[f32; 3]> = sub.positions.iter().map(|v| p.apply(*v)).collect();
            for v in &w {
                for k in 0..3 {
                    lo[k] = lo[k].min(v[k]);
                    hi[k] = hi[k].max(v[k]);
                }
            }
            for t in sub.indices.chunks_exact(3) {
                tris.push([w[t[0] as usize], w[t[1] as usize], w[t[2] as usize]]);
            }
        }
        println!(
            "{:20} X[{:8.1},{:8.1}] Y[{:8.1},{:8.1}] Z[{:8.1},{:8.1}]",
            p.resource, lo[0], hi[0], lo[1], hi[1], lo[2], hi[2]
        );
    }
    // Exhaust cones at the GN_Jet/GN_SJet frames (the game's visible thrusters).
    for f in sylpheed_formats::ship::exhaust_frames(&bytes, id) {
        const SEG: usize = 12;
        let (r, len) = (22.0f32, 140.0f32);
        let ring: Vec<[f32; 3]> = (0..SEG)
            .map(|s| {
                let a = s as f32 / SEG as f32 * std::f32::consts::TAU;
                f.apply([a.cos() * r, a.sin() * r, 0.0])
            })
            .collect();
        let apex = f.apply([0.0, 0.0, len]);
        for s in 0..SEG {
            tris.push([apex, ring[(s + 1) % SEG], ring[s]]);
        }
        println!("  exhaust frame {:16} T=[{:8.1}{:8.1}{:8.1}]", f.resource, f.t[0], f.t[1], f.t[2]);
    }
    println!("total {} tris from {} placements", tris.len(), placed.len());

    // Orthographic z-buffered flat renders: top (X/Z, look down −Y) and side (Z/Y, look down −X).
    for (name, ax, ay, az) in [("top", 0usize, 2usize, 1usize), ("side", 2usize, 1usize, 0usize)] {
        let size = 900usize;
        let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
        for t in &tris {
            for v in t {
                for k in 0..3 {
                    lo[k] = lo[k].min(v[k]);
                    hi[k] = hi[k].max(v[k]);
                }
            }
        }
        let cx = (lo[ax] + hi[ax]) * 0.5;
        let cy = (lo[ay] + hi[ay]) * 0.5;
        let ext = (hi[ax] - lo[ax]).max(hi[ay] - lo[ay]) * 0.55;
        let scale = (size as f32 * 0.5) / ext;
        let mut img = vec![0u8; size * size * 3];
        let mut zbuf = vec![f32::MIN; size * size];
        for t in &tris {
            // screen coords
            let p: Vec<[f32; 3]> = t
                .iter()
                .map(|v| {
                    [
                        (v[ax] - cx) * scale + size as f32 * 0.5,
                        (cy - v[ay]) * scale + size as f32 * 0.5,
                        v[az],
                    ]
                })
                .collect();
            // flat shade by triangle normal Z-ish
            let e1 = [t[1][0] - t[0][0], t[1][1] - t[0][1], t[1][2] - t[0][2]];
            let e2 = [t[2][0] - t[0][0], t[2][1] - t[0][1], t[2][2] - t[0][2]];
            let n = [
                e1[1] * e2[2] - e1[2] * e2[1],
                e1[2] * e2[0] - e1[0] * e2[2],
                e1[0] * e2[1] - e1[1] * e2[0],
            ];
            let nl = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt().max(1e-6);
            let lum = (n[az].abs() / nl * 200.0 + 40.0) as u8;
            // bbox raster
            let minx = p.iter().map(|v| v[0]).fold(f32::MAX, f32::min).max(0.0) as usize;
            let maxx = (p.iter().map(|v| v[0]).fold(f32::MIN, f32::max).min(size as f32 - 1.0)) as usize;
            let miny = p.iter().map(|v| v[1]).fold(f32::MAX, f32::min).max(0.0) as usize;
            let maxy = (p.iter().map(|v| v[1]).fold(f32::MIN, f32::max).min(size as f32 - 1.0)) as usize;
            let det = (p[1][0] - p[0][0]) * (p[2][1] - p[0][1]) - (p[2][0] - p[0][0]) * (p[1][1] - p[0][1]);
            if det.abs() < 1e-6 {
                continue;
            }
            for y in miny..=maxy {
                for x in minx..=maxx {
                    let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5);
                    let w0 = ((p[1][0] - fx) * (p[2][1] - fy) - (p[2][0] - fx) * (p[1][1] - fy)) / det;
                    let w1 = ((p[2][0] - fx) * (p[0][1] - fy) - (p[0][0] - fx) * (p[2][1] - fy)) / det;
                    let w2 = 1.0 - w0 - w1;
                    if w0 < 0.0 || w1 < 0.0 || w2 < 0.0 {
                        continue;
                    }
                    let z = w0 * p[0][2] + w1 * p[1][2] + w2 * p[2][2];
                    let idx = y * size + x;
                    if z > zbuf[idx] {
                        zbuf[idx] = z;
                        img[idx * 3] = lum;
                        img[idx * 3 + 1] = lum;
                        img[idx * 3 + 2] = lum;
                    }
                }
            }
        }
        let file = format!("{out}_{name}.ppm");
        let mut ppm = format!("P6\n{size} {size}\n255\n").into_bytes();
        ppm.extend_from_slice(&img);
        std::fs::write(&file, ppm).unwrap();
        println!("wrote {file}");
    }
}
