#![allow(clippy::cast_precision_loss)] // The benchmark grid is fixed at 240×240.

use std::alloc::System;
use std::hint::black_box;
use std::time::Instant;

use libremetaverse::Primitive;
use libremetaverse::import_export::{ModelFace, ModelPrim};
use libremetaverse::rendering::{DetailLevel, Vertex};
use libremetaverse_rendering_mesh_foundry::MeshFoundry;
use libremetaverse_types::{UUID, Vector2, Vector3};
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc};

#[global_allocator]
static ALLOCATOR: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;

fn fixture(side: usize) -> Vec<u8> {
    let mut model = ModelPrim::new().expect("model prim");
    let mut face = ModelFace::new().expect("model face");
    face.vertices.reserve(side * side);
    for y in 0..side {
        for x in 0..side {
            let u = x as f32 / (side - 1) as f32;
            let v = y as f32 / (side - 1) as f32;
            face.vertices.push(Vertex {
                position: Vector3 {
                    x: u - 0.5,
                    y: v - 0.5,
                    z: (u * std::f32::consts::TAU).sin() * 0.05,
                },
                normal: Vector3::unit_z(),
                tex_coord: Vector2 { x: u, y: v },
            });
        }
    }
    for y in 0..side - 1 {
        for x in 0..side - 1 {
            let a = u32::try_from(y * side + x).expect("bounded benchmark index");
            let b = a + 1;
            let c = a + u32::try_from(side).expect("bounded side");
            let d = c + 1;
            face.indices.extend_from_slice(&[a, b, c, b, d, c]);
        }
    }
    model.faces.push(face);
    model
        .create_asset(UUID::zero())
        .expect("bounded benchmark mesh asset");
    model.asset
}

fn main() {
    let asset = fixture(240);
    let renderer = MeshFoundry::new().expect("MeshFoundry constructor");
    let primitive = Primitive::new_with_constructor().expect("Primitive constructor");
    let allocation_region = Region::new(ALLOCATOR);
    let started = Instant::now();
    let mesh = renderer
        .generate_faceted_mesh_mesh_with_primitive_bytes_detail_level(
            primitive,
            asset.clone(),
            DetailLevel::Highest,
        )
        .expect("large fixture decode")
        .expect("large fixture geometry");
    let elapsed = started.elapsed();
    let allocation_stats = allocation_region.change();

    assert_eq!(mesh.faces.len(), 1);
    assert_eq!(mesh.faces[0].vertices.len(), 57_600);
    assert_eq!(mesh.faces[0].indices.len(), 342_726);
    black_box((&asset, &mesh));
    println!(
        "mesh decode: {} compressed bytes, {} vertices, {} indices in {elapsed:?}, {} allocations, {} reallocations, {} bytes allocated",
        asset.len(),
        mesh.faces[0].vertices.len(),
        mesh.faces[0].indices.len(),
        allocation_stats.allocations,
        allocation_stats.reallocations,
        allocation_stats.bytes_allocated,
    );
}
