//! Brush → Render-Geometrie.
//!
//! Ein Quake-Brush ist der Schnitt der Halbräume seiner Face-Ebenen. Aus
//! den Ebenen rekonstruieren wir das konvexe Polyeder (Standard-Verfahren:
//! alle Ebenen-Tripel schneiden, nur Punkte behalten, die in *allen*
//! Halbräumen liegen), bilden pro Face das Polygon und fächern es zu
//! Dreiecken. Indizes werden nach Textur gruppiert (ein Draw-Batch je
//! Textur).
//!
//! Koplanar-Elimination: stoßen zwei Brushes aneinander, teilen sie eine
//! Fläche — beide Faces liegen auf derselben Ebene, mit entgegengesetzten
//! Normalen, und sind nie sichtbar. Wir verwerfen jede Face, die von einer
//! solchen gegenüberliegenden Face vollständig überdeckt wird (hinter ihr
//! liegt dann garantiert solides Brush-Inneres). Zusammen mit dem Back-face
//! Culling der Pipeline bleibt so nur die Außenhaut übrig — ohne BSP.
//!
//! UVs nach dem Standard-Map-Format: die Textur-Achsen ergeben sich aus der
//! dominanten Flächennormale (Quake-`baseaxis`-Tabelle), dann Rotation,
//! Skalierung und Offset der Face. Das erzeugt das klassische Quake-
//! Verziehen auf schrägen Flächen — gewollt fürs PS1-Gefühl.
//!
//! Achsen: Quake ist Z-up, unsere Engine Y-up mit Blick -Z. UVs werden im
//! Quake-System gerechnet (so sind Offsets/Scale definiert), die Position
//! danach umgerechnet: `(qx, qy, qz) → (qx, qz, -qy)`, mal `MAP_SCALE`.

use crate::engine::map::{self, Face, Map};
use crate::render::scene::{Batch, Mesh, Vertex};

/// Punkt liegt „auf" einer Ebene / „innerhalb" eines Halbraums (Quake-Units).
const ON_EPS: f32 = 1e-2;

struct Plane {
    n: [f32; 3], // nach außen, im Quake-System
    d: f32,      // n · x = d
}

/// Ein fertig rekonstruiertes Face-Polygon (Quake-Koords, CCW um `n`),
/// vor der UV-/Engine-Umrechnung. Hält die Ebene für die Koplanar-Prüfung
/// und einen Verweis auf die Quell-Face (Textur, Offsets, Skalierung).
struct FacePoly<'a> {
    n:    [f32; 3],
    d:    f32,
    poly: Vec<[f32; 3]>,
    face: &'a Face,
}

/// Distinkte Texturnamen in der Map (sortiert) — die Indizes hier sind die
/// Textur-Indizes, auf die `Batch::texture` und der Bilder-Vec verweisen.
pub fn texture_names(map: &Map) -> Vec<String> {
    let mut names: Vec<String> = map.entities.iter()
        .flat_map(|e| &e.brushes)
        .flat_map(|b| &b.faces)
        .map(|f| f.texture.clone())
        .collect();
    names.sort_unstable();
    names.dedup();
    names
}

/// Alle Brushes zu einem Mesh. `tex_names` (aus [`texture_names`]) gibt die
/// Textur-Indizes, `dims` die Pixelmaße je Textur (für UV-Normalisierung).
pub fn build(map: &Map, tex_names: &[String], dims: &[(u32, u32)]) -> Mesh {
    // 1) Alle Face-Polygone rekonstruieren (noch in Quake-Koords).
    let mut faces: Vec<FacePoly> = Vec::new();
    for ent in &map.entities {
        for brush in &ent.brushes {
            collect_faces(&brush.faces, &mut faces);
        }
    }

    // 2) Koplanar verdeckte Faces markieren.
    let hidden = mark_hidden(&faces);

    // 3) Überlebende mit UV in den Vertex-Buffer ausgeben, nach Textur
    //    gruppiert.
    let mut verts = Vec::new();
    let mut per_tex: Vec<Vec<u32>> = vec![Vec::new(); tex_names.len()];
    for (i, fp) in faces.iter().enumerate() {
        if hidden[i] { continue; }
        emit_face(fp, tex_names, dims, &mut verts, &mut per_tex);
    }

    // Pro-Textur-Indexlisten zu einem Buffer + Batches verflachen.
    let mut indices = Vec::new();
    let mut batches = Vec::new();
    for (ti, list) in per_tex.into_iter().enumerate() {
        if list.is_empty() { continue; }
        batches.push(Batch { texture: ti, start: indices.len() as u32, count: list.len() as u32 });
        indices.extend_from_slice(&list);
    }
    Mesh { verts, indices, batches }
}

/// Die Face-Polygone *eines* Brushes rekonstruieren und an `out` anhängen.
fn collect_faces<'a>(faces: &'a [Face], out: &mut Vec<FacePoly<'a>>) {
    let planes: Vec<Plane> = faces.iter().map(|f| plane(&f.plane)).collect();
    let n = planes.len();

    // Gültige Eckpunkte: Schnitt je dreier Ebenen, sofern in allen
    // Halbräumen.
    let mut corners: Vec<[f32; 3]> = Vec::new();
    for i in 0..n {
        for j in (i + 1)..n {
            for k in (j + 1)..n {
                let Some(p) = intersect(&planes[i], &planes[j], &planes[k]) else { continue; };
                if planes.iter().all(|pl| dot(pl.n, p) <= pl.d + ON_EPS)
                    && !corners.iter().any(|q| dist2(*q, p) < ON_EPS * ON_EPS)
                {
                    corners.push(p);
                }
            }
        }
    }

    // Pro Face: Eckpunkte auf der Ebene sammeln, CCW um die Normale sortieren.
    for (fi, face) in faces.iter().enumerate() {
        let pl = &planes[fi];
        let mut poly: Vec<[f32; 3]> = corners.iter().copied()
            .filter(|&p| (dot(pl.n, p) - pl.d).abs() < ON_EPS)
            .collect();
        if poly.len() < 3 { continue; }

        let center = average(&poly);
        let axis_u = any_perp(pl.n);
        let axis_v = cross(pl.n, axis_u);
        poly.sort_by(|a, b| {
            angle(*a, center, axis_u, axis_v)
                .partial_cmp(&angle(*b, center, axis_u, axis_v))
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        out.push(FacePoly { n: pl.n, d: pl.d, poly, face });
    }
}

/// Markiert Faces, die von einer gegenüberliegenden, deckungsgleichen Face
/// (anderer Brush) vollständig überdeckt werden — also nie sichtbar sind.
fn mark_hidden(faces: &[FacePoly]) -> Vec<bool> {
    let mut hidden = vec![false; faces.len()];
    for i in 0..faces.len() {
        let a = &faces[i];
        for (j, b) in faces.iter().enumerate() {
            if i == j { continue; }
            // Gegenüberliegende, koplanare Ebene? (n_a ≈ -n_b und d_a ≈ -d_b,
            // denn n_b·x = d_b ⇒ n_a·x = -d_b muss a.d treffen.)
            if dot(a.n, b.n) > -0.999 { continue; }
            if (a.d + b.d).abs() > ON_EPS { continue; }
            if covered_by(&a.poly, &b.poly, a.n) {
                hidden[i] = true;
                break;
            }
        }
    }
    hidden
}

/// Liegt jeder Eckpunkt von `inner` innerhalb des Polygons `outer`? Beide
/// liegen (per Aufruf) auf derselben Ebene mit Normale `n`; wir projizieren
/// in deren 2D-Basis. Testpunkte werden minimal zur Polygonmitte gezogen,
/// damit deckungsgleiche Ränder/Ecken eindeutig als „innen" zählen.
fn covered_by(inner: &[[f32; 3]], outer: &[[f32; 3]], n: [f32; 3]) -> bool {
    let u = any_perp(n);
    let v = cross(n, u);
    let c = average(inner);
    let proj = |p: [f32; 3]| -> [f32; 2] { let d = sub(p, c); [dot(d, u), dot(d, v)] };
    let outer2: Vec<[f32; 2]> = outer.iter().map(|&p| proj(p)).collect();
    inner.iter().all(|&p| {
        let pp = [
            p[0] + 1e-3 * (c[0] - p[0]),
            p[1] + 1e-3 * (c[1] - p[1]),
            p[2] + 1e-3 * (c[2] - p[2]),
        ];
        in_poly_2d(proj(pp), &outer2)
    })
}

/// Crossing-Number-Punkt-in-Polygon in 2D (orientierungsunabhängig).
fn in_poly_2d(p: [f32; 2], poly: &[[f32; 2]]) -> bool {
    let mut inside = false;
    let mut j = poly.len() - 1;
    for i in 0..poly.len() {
        let (a, b) = (poly[i], poly[j]);
        if (a[1] > p[1]) != (b[1] > p[1]) {
            let t = (p[1] - a[1]) / (b[1] - a[1]);
            if p[0] < a[0] + t * (b[0] - a[0]) { inside = !inside; }
        }
        j = i;
    }
    inside
}

/// Ein Face-Polygon mit UV in den Vertex-Buffer fächern.
fn emit_face(
    fp: &FacePoly,
    tex_names: &[String],
    dims: &[(u32, u32)],
    verts: &mut Vec<Vertex>,
    per_tex: &mut [Vec<u32>],
) {
    let Some(ti) = tex_names.iter().position(|nm| nm == &fp.face.texture) else { return; };
    let (tw, th) = dims[ti];
    let (su, sv) = tex_axes(fp.n, fp.face); // skalierte Textur-Achsen (Quake)
    let base = verts.len() as u32;
    for p in &fp.poly {
        // UV in Texeln, dann auf 0..1 normalisiert.
        let u = (dot(*p, su) + fp.face.offset[0]) / tw as f32;
        let v = (dot(*p, sv) + fp.face.offset[1]) / th as f32;
        verts.push(Vertex { pos: map::to_engine(*p), uv: [u, v] });
    }
    for t in 1..(fp.poly.len() as u32 - 1) {
        per_tex[ti].extend([base, base + t, base + t + 1]);
    }
}

/// Ebene aus drei Face-Punkten. Quake listet sie im Uhrzeigersinn von vorn,
/// damit `cross(c-a, b-a)` nach außen zeigt.
fn plane(p: &[[f32; 3]; 3]) -> Plane {
    let n = normalize(cross(sub(p[2], p[0]), sub(p[1], p[0])));
    Plane { n, d: dot(n, p[0]) }
}

/// Schnittpunkt dreier Ebenen (Cramer); `None`, wenn sie ~parallel sind.
fn intersect(a: &Plane, b: &Plane, c: &Plane) -> Option<[f32; 3]> {
    let denom = dot(a.n, cross(b.n, c.n));
    if denom.abs() < 1e-6 { return None; }
    let mut x = scale(cross(b.n, c.n), a.d);
    x = add(x, scale(cross(c.n, a.n), b.d));
    x = add(x, scale(cross(a.n, b.n), c.d));
    Some(scale(x, 1.0 / denom))
}

/// Textur-Achsen (skaliert) im Quake-System nach dem Standard-Map-Format:
/// dominante Achse aus der Normale, dann Rotation und Skalierung.
fn tex_axes(n: [f32; 3], face: &Face) -> ([f32; 3], [f32; 3]) {
    let (mut u, mut v) = base_axes(n);

    let (sinv, cosv) = face.rotation.to_radians().sin_cos();
    // Basis-Achsen sind achsen-ausgerichtet (genau eine Komponente ≠ 0);
    // wir rotieren ihre beiden in-plane-Komponenten.
    let su = nonzero_axis(u);
    let sv = nonzero_axis(v);
    for vec in [&mut u, &mut v] {
        let a = cosv * vec[su] - sinv * vec[sv];
        let b = sinv * vec[su] + cosv * vec[sv];
        vec[su] = a;
        vec[sv] = b;
    }

    let sx = if face.scale[0] == 0.0 { 1.0 } else { face.scale[0] };
    let sy = if face.scale[1] == 0.0 { 1.0 } else { face.scale[1] };
    (scale(u, 1.0 / sx), scale(v, 1.0 / sy))
}

/// Quake-`baseaxis`-Tabelle: (Normalen-Kandidat, U-Achse, V-Achse) × 6.
/// Die U/V-Achsen der am besten passenden Normale bilden die Textur-Ebene.
fn base_axes(n: [f32; 3]) -> ([f32; 3], [f32; 3]) {
    const BA: [[[f32; 3]; 3]; 6] = [
        [[0.0, 0.0,  1.0], [1.0, 0.0, 0.0], [0.0, -1.0,  0.0]], // Boden  (+Z)
        [[0.0, 0.0, -1.0], [1.0, 0.0, 0.0], [0.0, -1.0,  0.0]], // Decke  (-Z)
        [[ 1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0,  0.0, -1.0]], // West   (+X)
        [[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0,  0.0, -1.0]], // Ost    (-X)
        [[0.0,  1.0, 0.0], [1.0, 0.0, 0.0], [0.0,  0.0, -1.0]], // Süd    (+Y)
        [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0,  0.0, -1.0]], // Nord   (-Y)
    ];
    let mut best = -1.0;
    let mut bi = 0;
    for (i, ax) in BA.iter().enumerate() {
        let d = dot(n, ax[0]);
        if d > best { best = d; bi = i; }
    }
    (BA[bi][1], BA[bi][2])
}

// --- kleine Vektor-Helfer auf [f32; 3] ---------------------------------------

fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] }
fn add(a: [f32; 3], b: [f32; 3]) -> [f32; 3] { [a[0] + b[0], a[1] + b[1], a[2] + b[2]] }
fn scale(a: [f32; 3], s: f32) -> [f32; 3] { [a[0] * s, a[1] * s, a[2] * s] }
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 { a[0] * b[0] + a[1] * b[1] + a[2] * b[2] }
fn dist2(a: [f32; 3], b: [f32; 3]) -> f32 { let d = sub(a, b); dot(d, d) }

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 normalize(a: [f32; 3]) -> [f32; 3] {
    let len = dot(a, a).sqrt();
    if len > 0.0 { scale(a, 1.0 / len) } else { a }
}

fn average(pts: &[[f32; 3]]) -> [f32; 3] {
    let mut c = [0.0; 3];
    for p in pts { c = add(c, *p); }
    scale(c, 1.0 / pts.len() as f32)
}

/// Index der (einzigen) Nicht-Null-Komponente einer achsen-ausgerichteten Achse.
fn nonzero_axis(a: [f32; 3]) -> usize {
    if a[0] != 0.0 { 0 } else if a[1] != 0.0 { 1 } else { 2 }
}

/// Achse in der Ebene, möglichst weit weg von der Normale, als Sortier-Basis.
fn any_perp(n: [f32; 3]) -> [f32; 3] {
    let a = if n[0].abs() < 0.9 { [1.0, 0.0, 0.0] } else { [0.0, 1.0, 0.0] };
    normalize(cross(n, a))
}

/// Winkel von `p` um `center` in der (u, v)-Basis — für CCW-Sortierung.
fn angle(p: [f32; 3], center: [f32; 3], u: [f32; 3], v: [f32; 3]) -> f32 {
    let d = sub(p, center);
    dot(d, v).atan2(dot(d, u))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::engine::map;

    // Ein achsenparalleler Quader-Brush (x[-48,80], y[-64,64], z[-48,-16]).
    const BOX: &str = r#"
{
"classname" "worldspawn"
{
( -48 -64 -48 ) ( -48 -63 -48 ) ( -48 -64 -47 ) t 0 0 0 1 1
( -48 -64 -48 ) ( -48 -64 -47 ) ( -47 -64 -48 ) t 0 0 0 1 1
( -48 -64 -48 ) ( -47 -64 -48 ) ( -48 -63 -48 ) t 0 0 0 1 1
( 80 64 -16 ) ( 80 65 -16 ) ( 81 64 -16 ) t 0 0 0 1 1
( 80 64 -16 ) ( 81 64 -16 ) ( 80 64 -15 ) t 0 0 0 1 1
( 80 64 -16 ) ( 80 64 -15 ) ( 80 65 -16 ) t 0 0 0 1 1
}
}
"#;

    #[test]
    fn box_brush_has_cuboid_geometry() {
        let m = map::parse(BOX);
        let names = texture_names(&m);
        assert_eq!(names, vec!["t".to_string()]);
        let mesh = build(&m, &names, &[(64, 64)]);

        // Quader: 6 Faces · 4 Ecken = 24 Vertices, 6 · 2 Dreiecke · 3 = 36 Indizes.
        assert_eq!(mesh.verts.len(), 24);
        assert_eq!(mesh.indices.len(), 36);
        // Eine Textur → ein Batch über alle Indizes.
        assert_eq!(mesh.batches.len(), 1);
        assert_eq!((mesh.batches[0].texture, mesh.batches[0].start, mesh.batches[0].count), (0, 0, 36));

        // Engine-Bounds: x[-1.5,2.5], y(=quake z)[-1.5,-0.5], z(=-quake y)[-2,2].
        let (mut mn, mut mx) = ([f32::MAX; 3], [f32::MIN; 3]);
        for v in &mesh.verts {
            for k in 0..3 {
                mn[k] = mn[k].min(v.pos[k]);
                mx[k] = mx[k].max(v.pos[k]);
            }
        }
        let approx = |a: f32, b: f32| (a - b).abs() < 1e-4;
        assert!(approx(mn[0], -1.5) && approx(mx[0], 2.5), "x: {:?}..{:?}", mn[0], mx[0]);
        assert!(approx(mn[1], -1.5) && approx(mx[1], -0.5), "y: {:?}..{:?}", mn[1], mx[1]);
        assert!(approx(mn[2], -2.0) && approx(mx[2], 2.0), "z: {:?}..{:?}", mn[2], mx[2]);
    }

    // Zwei achsenparallele Würfel, die sich die Ebene x=64 teilen:
    // A = x[0,64], B = x[64,128], beide y[0,64] z[0,64].
    const TWO_BOXES: &str = r#"
{
"classname" "worldspawn"
{
( 0 0 0 ) ( 0 1 0 ) ( 0 0 1 ) t 0 0 0 1 1
( 0 0 0 ) ( 0 0 1 ) ( 1 0 0 ) t 0 0 0 1 1
( 0 0 0 ) ( 1 0 0 ) ( 0 1 0 ) t 0 0 0 1 1
( 64 64 64 ) ( 64 65 64 ) ( 65 64 64 ) t 0 0 0 1 1
( 64 64 64 ) ( 65 64 64 ) ( 64 64 65 ) t 0 0 0 1 1
( 64 64 64 ) ( 64 64 65 ) ( 64 65 64 ) t 0 0 0 1 1
}
{
( 64 0 0 ) ( 64 1 0 ) ( 64 0 1 ) t 0 0 0 1 1
( 64 0 0 ) ( 64 0 1 ) ( 65 0 0 ) t 0 0 0 1 1
( 64 0 0 ) ( 65 0 0 ) ( 64 1 0 ) t 0 0 0 1 1
( 128 64 64 ) ( 128 65 64 ) ( 129 64 64 ) t 0 0 0 1 1
( 128 64 64 ) ( 129 64 64 ) ( 128 64 65 ) t 0 0 0 1 1
( 128 64 64 ) ( 128 64 65 ) ( 128 65 64 ) t 0 0 0 1 1
}
}
"#;

    #[test]
    fn shared_faces_are_eliminated() {
        let m = map::parse(TWO_BOXES);
        let names = texture_names(&m);
        let mesh = build(&m, &names, &[(64, 64)]);

        // 2 Würfel · 6 Faces = 12, minus die 2 deckungsgleichen an x=64 → 10.
        // 10 Faces · 4 Ecken = 40 Vertices, 10 · 2 Dreiecke · 3 = 60 Indizes.
        // (Ohne Elimination wären es 48 Vertices / 72 Indizes.)
        assert_eq!(mesh.verts.len(), 40);
        assert_eq!(mesh.indices.len(), 60);
    }

    #[test]
    fn uvs_scale_with_texture_size() {
        // Auf der +Z-Boden-Face (baseaxis U=+X, V=-Y) ist U = qx/tw, V = -qy/th
        // bei Offset 0, Scale 1. Prüft, dass UVs in Texeln/Größe gerechnet werden.
        let m = map::parse(BOX);
        let names = texture_names(&m);
        let mesh = build(&m, &names, &[(64, 64)]);
        // Alle UVs endlich und im erwarteten groben Bereich (Brush ~128 units
        // breit / 64 Texel → bis ~2).
        assert!(mesh.verts.iter().all(|v| v.uv[0].is_finite() && v.uv[1].is_finite()));
        assert!(mesh.verts.iter().any(|v| v.uv[0].abs() > 0.5));
    }
}
