//! Primitive processor — normalize Xenos primitives into host-GPU forms.
//!
//! wgpu only exposes `PrimitiveTopology::{PointList, LineList, LineStrip,
//! TriangleList, TriangleStrip}`. For everything else (fans, quads,
//! rectangles) we rewrite indices on the CPU side so the host just sees a
//! triangle list. Ground truth: `xenia-canary/src/xenia/gpu/primitive_processor.h/cc`.
//!
//! Scope: list, strip, fan, quad, and rectangle expansions are all handled
//! (rectangles via CPU triangle-list rewrite — see `expand_rectangles`).

use crate::draw_state::{IndexSize, PrimitiveType};

/// Host primitive topology — a subset of wgpu's that we commit to.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostTopology {
    PointList,
    LineList,
    LineStrip,
    TriangleList,
    TriangleStrip,
}

/// Result of primitive processing.
#[derive(Debug, Clone)]
pub struct ProcessedPrimitive {
    pub topology: HostTopology,
    /// When the Xenos primitive needed client-side rewriting (fans, quads),
    /// this buffer holds the rewritten 16-bit or 32-bit index sequence.
    /// `None` means the input index buffer is usable as-is.
    pub rewritten_indices: Option<Vec<u32>>,
    /// Post-processing vertex count — equals the input count when indices
    /// pass through unchanged.
    pub host_vertex_count: u32,
    /// `true` if we rejected the primitive (unsupported shape) and the
    /// caller should skip this draw. Logged via `tracing::warn!`.
    pub rejected: bool,
}

/// Normalize a draw.
///
/// `indices` is `None` for `AutoIndex` draws; otherwise it's the decoded
/// index stream (already endian-converted / widened to u32 by the caller).
pub fn process(
    primitive: PrimitiveType,
    vertex_count: u32,
    indices: Option<&[u32]>,
) -> ProcessedPrimitive {
    match primitive {
        PrimitiveType::PointList => pass_through(HostTopology::PointList, vertex_count),
        PrimitiveType::LineList => pass_through(HostTopology::LineList, vertex_count),
        PrimitiveType::LineStrip => pass_through(HostTopology::LineStrip, vertex_count),
        PrimitiveType::TriangleList => pass_through(HostTopology::TriangleList, vertex_count),
        PrimitiveType::TriangleStrip => pass_through(HostTopology::TriangleStrip, vertex_count),
        PrimitiveType::TriangleFan => expand_fan(indices, vertex_count),
        PrimitiveType::RectangleList => expand_rectangles(indices, vertex_count),
        PrimitiveType::QuadList => expand_quads(indices, vertex_count),
        PrimitiveType::None | PrimitiveType::Unknown(_) => {
            tracing::warn!(?primitive, "gpu: rejecting unsupported primitive");
            metrics::counter!("gpu.primitive.rejected").increment(1);
            ProcessedPrimitive {
                topology: HostTopology::TriangleList,
                rewritten_indices: None,
                host_vertex_count: 0,
                rejected: true,
            }
        }
    }
}

fn pass_through(topology: HostTopology, vertex_count: u32) -> ProcessedPrimitive {
    ProcessedPrimitive {
        topology,
        rewritten_indices: None,
        host_vertex_count: vertex_count,
        rejected: false,
    }
}

/// Convert a triangle fan to a triangle list. Fan indices `[0, 1, 2, 3, 4]`
/// expand to triangles `(0,1,2), (0,2,3), (0,3,4)` — 3 × (n-2) host indices.
fn expand_fan(indices: Option<&[u32]>, vertex_count: u32) -> ProcessedPrimitive {
    if vertex_count < 3 {
        return ProcessedPrimitive {
            topology: HostTopology::TriangleList,
            rewritten_indices: Some(Vec::new()),
            host_vertex_count: 0,
            rejected: false,
        };
    }
    let mut out = Vec::with_capacity(3 * (vertex_count as usize - 2));
    let get = |i: u32| -> u32 {
        match indices {
            Some(buf) => buf[i as usize],
            None => i,
        }
    };
    let apex = get(0);
    for i in 1..vertex_count.saturating_sub(1) {
        out.push(apex);
        out.push(get(i));
        out.push(get(i + 1));
    }
    let host_vertex_count = out.len() as u32;
    ProcessedPrimitive {
        topology: HostTopology::TriangleList,
        rewritten_indices: Some(out),
        host_vertex_count,
        rejected: false,
    }
}

/// Convert a quad list (groups of 4) to a triangle list (groups of 6).
fn expand_quads(indices: Option<&[u32]>, vertex_count: u32) -> ProcessedPrimitive {
    let quad_count = vertex_count / 4;
    let mut out = Vec::with_capacity(6 * quad_count as usize);
    let get = |i: u32| -> u32 {
        match indices {
            Some(buf) => buf[i as usize],
            None => i,
        }
    };
    for q in 0..quad_count {
        let base = q * 4;
        let a = get(base);
        let b = get(base + 1);
        let c = get(base + 2);
        let d = get(base + 3);
        out.extend_from_slice(&[a, b, c, a, c, d]);
    }
    let host_vertex_count = out.len() as u32;
    ProcessedPrimitive {
        topology: HostTopology::TriangleList,
        rewritten_indices: Some(out),
        host_vertex_count,
        rejected: false,
    }
}

/// Rectangle lists: a Xenos-specific primitive where each group of 3
/// vertices defines a rectangle; the 4th corner is extrapolated as
/// `v3 = v0 + v2 - v1` (parallelogram completion). Canary expands this in a
/// host vertex-shader variant (`kRectangleListAsTriangleStrip`,
/// `primitive_processor.cc:389-456`): a 4-vertex triangle strip per rect with
/// the 4th corner synthesized *in the VS* from the host-vertex index.
///
/// We draw the full quad as a 6-vertex triangle list `[v0,v1,v2, v0,v2,v3]`
/// per rect (canary's tessellation). The synthesized `v3` (index `base+3`) has no backing vertex in
/// the guest window, so the translated `vs_main` computes its attributes as
/// `v0 + v2 - v1` when the host-vertex index maps to that corner (see the
/// RectangleList handling + `emit_vfetch` corner synthesis in `translator.rs`).
/// Emitting only the front triangle `(v0,v1,v2)` — as we used to — fills just
/// half the rect and leaves a diagonal seam (the movie's fullscreen YUV rect
/// showed exactly that).
fn expand_rectangles(indices: Option<&[u32]>, vertex_count: u32) -> ProcessedPrimitive {
    let rect_count = vertex_count / 3;
    let mut out = Vec::with_capacity(6 * rect_count as usize);
    let get = |i: u32| -> u32 {
        match indices {
            Some(buf) => buf[i as usize],
            None => i,
        }
    };
    for r in 0..rect_count {
        let base = r * 3;
        let (v0, v1, v2) = (get(base), get(base + 1), get(base + 2));
        // Front triangle (v0,v1,v2), then the completing triangle (v0,v2,v3)
        // with the synthesized 4th corner (index `base + 3`; the VS
        // extrapolates its attributes as v0+v2-v1).
        out.extend_from_slice(&[v0, v1, v2, v0, v2, base + 3]);
    }
    let host_vertex_count = out.len() as u32;
    metrics::counter!("gpu.primitive.expanded", "shape" => "rectangle_list").increment(1);
    ProcessedPrimitive {
        topology: HostTopology::TriangleList,
        rewritten_indices: Some(out),
        host_vertex_count,
        rejected: false,
    }
}

/// Widen a u16 index buffer to u32. The primitive processor normalizes to
/// u32 so downstream wgpu pipeline descriptors stay simple.
pub fn widen_indices(raw: &[u8], size: IndexSize, count: u32) -> Vec<u32> {
    let mut out = Vec::with_capacity(count as usize);
    match size {
        IndexSize::Sixteen => {
            for i in 0..count as usize {
                let off = i * 2;
                if off + 2 > raw.len() {
                    break;
                }
                // Xenos indices are big-endian on the wire.
                let be = u16::from_be_bytes([raw[off], raw[off + 1]]);
                out.push(be as u32);
            }
        }
        IndexSize::ThirtyTwo => {
            for i in 0..count as usize {
                let off = i * 4;
                if off + 4 > raw.len() {
                    break;
                }
                let be = u32::from_be_bytes([raw[off], raw[off + 1], raw[off + 2], raw[off + 3]]);
                out.push(be);
            }
        }
    }
    out
}

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

    #[test]
    fn triangle_list_passes_through() {
        let p = process(PrimitiveType::TriangleList, 6, None);
        assert_eq!(p.topology, HostTopology::TriangleList);
        assert!(p.rewritten_indices.is_none());
        assert_eq!(p.host_vertex_count, 6);
        assert!(!p.rejected);
    }

    #[test]
    fn fan_to_list_expands_correctly() {
        // Fan of 5 vertices → triangles (0,1,2), (0,2,3), (0,3,4)
        let p = process(PrimitiveType::TriangleFan, 5, None);
        let idx = p.rewritten_indices.unwrap();
        assert_eq!(idx, vec![0, 1, 2, 0, 2, 3, 0, 3, 4]);
        assert_eq!(p.topology, HostTopology::TriangleList);
        assert_eq!(p.host_vertex_count, 9);
    }

    #[test]
    fn quad_list_expansion() {
        let p = process(PrimitiveType::QuadList, 8, None);
        let idx = p.rewritten_indices.unwrap();
        assert_eq!(idx, vec![0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7]);
    }

    #[test]
    fn rectangle_list_expansion() {
        // 2 rects (6 verts) → full quad per rect: [v0,v1,v2, v2,v1,v3] where
        // v3 is the synthesized 4th corner (index base+3, extrapolated in the
        // VS). rect0 → base 0, rect1 → base 3.
        let p = process(PrimitiveType::RectangleList, 6, None);
        let idx = p.rewritten_indices.unwrap();
        assert_eq!(idx, vec![0, 1, 2, 0, 2, 3, 3, 4, 5, 3, 5, 6]);
        assert_eq!(p.topology, HostTopology::TriangleList);
        assert_eq!(p.host_vertex_count, 12);
        assert!(!p.rejected);
    }

    #[test]
    fn widen_u16_indices_big_endian() {
        // 3 indices [1, 2, 0x1234] in BE u16.
        let raw = [0, 1, 0, 2, 0x12, 0x34];
        let out = widen_indices(&raw, IndexSize::Sixteen, 3);
        assert_eq!(out, vec![1, 2, 0x1234]);
    }

    #[test]
    fn rejects_unknown_primitive() {
        let p = process(PrimitiveType::Unknown(0x2A), 3, None);
        assert!(p.rejected);
    }
}
